This commit is contained in:
admin
2026-06-01 00:35:29 +09:00
parent ebc87f96f9
commit 8c66d820c8
34 changed files with 1775 additions and 77 deletions

View File

@@ -0,0 +1,768 @@
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="js/auth.js"></script>
<title>銀行明細照会</title>
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
<style>
body {
font-family: "MS Gothic", "Meiryo", sans-serif;
margin: 24px auto;
font-size: 14px;
max-width: 1200px;
padding: 0 16px;
}
h2 {
margin: 0 0 16px;
font-size: 20px;
}
/* ── ヘッダー ── */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
}
.page-header h2 { margin: 0; }
.header-btns { display: flex; gap: 8px; flex-wrap: wrap; }
/* ── 検索フォーム ── */
.search-form {
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
padding: 14px 18px;
margin-bottom: 16px;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.search-form label { font-weight: bold; white-space: nowrap; }
.search-form select,
.search-form input[type="number"] {
padding: 5px 10px;
border: 1px solid #999;
border-radius: 3px;
font-size: 14px;
font-family: inherit;
}
/* ── ボタン共通 ── */
.btn {
padding: 6px 16px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
font-family: inherit;
white-space: nowrap;
text-decoration: none;
display: inline-block;
}
.btn-primary { background: #4caf50; color: #fff; }
.btn-primary:hover { background: #43a047; }
.btn-blue { background: #2196f3; color: #fff; }
.btn-blue:hover { background: #1976d2; }
.btn-orange { background: #ff9800; color: #fff; }
.btn-orange:hover { background: #f57c00; }
.btn-danger { background: #e53935; color: #fff; }
.btn-danger:hover { background: #c62828; }
.btn-gray { background: #9e9e9e; color: #fff; }
.btn-gray:hover { background: #757575; }
/* ── 月別サマリー ── */
.monthly-summary {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 14px;
}
.month-chip {
padding: 5px 12px;
border: 1px solid #999;
border-radius: 16px;
background: #fff;
cursor: pointer;
font-size: 13px;
white-space: nowrap;
}
.month-chip:hover { background: #e3f2fd; border-color: #2196f3; }
.month-chip.active { background: #2196f3; color: #fff; border-color: #1976d2; }
/* ── 統計バー ── */
.stats-bar {
display: flex;
gap: 20px;
margin-bottom: 12px;
font-size: 13px;
flex-wrap: wrap;
}
.stat-item { display: flex; align-items: center; gap: 6px; }
.stat-label { color: #666; }
.stat-value { font-weight: bold; }
.stat-value.debit { color: #e53935; }
.stat-value.credit { color: #1565c0; }
/* ── テーブル ── */
table {
border-collapse: collapse;
width: 100%;
font-size: 13px;
}
th, td {
border: 1px solid #bbb;
padding: 6px 10px;
}
th {
background: #e8e8e8;
font-weight: bold;
text-align: center;
white-space: nowrap;
}
td { background: #fff; }
tr:nth-child(even) td { background: #fafafa; }
tr:hover td { background: #e3f2fd; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.text-left { text-align: left; }
td.debit-cell { color: #c62828; }
td.credit-cell { color: #1565c0; }
.no-data {
text-align: center;
color: #999;
padding: 30px;
font-size: 15px;
}
/* ── モーダル ── */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
z-index: 1000;
justify-content: center;
align-items: flex-start;
padding: 30px 16px;
overflow-y: auto;
}
.modal-overlay.active { display: flex; }
.modal-box {
background: #fff;
border-radius: 8px;
padding: 28px;
width: 100%;
max-width: 920px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.modal-box h3 { margin: 0 0 18px; font-size: 17px; }
.modal-form-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.modal-form-row label { font-weight: bold; white-space: nowrap; }
.modal-form-row input[type="text"] {
flex: 1;
min-width: 160px;
padding: 7px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
font-family: inherit;
}
/* ドラッグ&ドロップエリア */
.drop-zone {
border: 2px dashed #999;
border-radius: 6px;
padding: 30px;
text-align: center;
color: #666;
cursor: pointer;
margin-bottom: 16px;
transition: border-color 0.2s, background 0.2s;
}
.drop-zone:hover, .drop-zone.dragover {
border-color: #2196f3;
background: #e3f2fd;
color: #1565c0;
}
.drop-zone input[type="file"] { display: none; }
.drop-zone .drop-icon { font-size: 36px; display: block; margin-bottom: 8px; }
/* プレビュー結果 */
.preview-summary {
display: flex;
gap: 20px;
margin-bottom: 12px;
padding: 10px 16px;
background: #f5f5f5;
border-radius: 4px;
flex-wrap: wrap;
}
.preview-summary .ps-item { font-size: 14px; }
.ps-new { color: #2e7d32; font-weight: bold; }
.ps-dup { color: #e65100; }
.ps-total { font-weight: bold; }
.preview-table-wrap {
max-height: 360px;
overflow-y: auto;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 16px;
}
.preview-table-wrap table { font-size: 12px; }
.preview-table-wrap th { position: sticky; top: 0; z-index: 1; }
tr.row-new td { background: #e8f5e9 !important; }
tr.row-dup td { background: #fff3e0 !important; color: #999; }
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: bold;
}
.badge-new { background: #c8e6c9; color: #1b5e20; }
.badge-dup { background: #ffe0b2; color: #bf360c; }
.modal-btn-row {
display: flex;
gap: 10px;
justify-content: flex-end;
flex-wrap: wrap;
}
/* ── メッセージ ── */
.msg-bar {
padding: 10px 16px;
border-radius: 4px;
margin-bottom: 14px;
display: none;
font-size: 14px;
}
.msg-bar.info { background: #e3f2fd; color: #0d47a1; border: 1px solid #90caf9; }
.msg-bar.success { background: #e8f5e9; color: #1b5e20; border: 1px solid #a5d6a7; }
.msg-bar.error { background: #ffebee; color: #b71c1c; border: 1px solid #ef9a9a; }
/* ── ページネーション ── */
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-top: 12px;
flex-wrap: wrap;
padding: 8px 0;
}
.page-info { font-size: 13px; color: #333; }
.btn-page { background: #f0f0f0; color: #333; border: 1px solid #bbb; }
.btn-page:hover:not(:disabled) { background: #e0e0e0; }
.btn-page:disabled { opacity: 0.4; cursor: default; }
/* ── 印刷スタイル ── */
@media print {
.no-print { display: none !important; }
body { margin: 0; font-size: 12px; }
table { font-size: 11px; }
th, td { padding: 4px 6px; }
.monthly-summary, .stats-bar { margin-bottom: 6px; }
.month-chip { border: 1px solid #999 !important; color: #000 !important; background: #fff !important; }
}
</style>
</head>
<body>
<div class="page-header no-print">
<h2>🏦 銀行明細照会</h2>
<div class="header-btns">
<button class="btn btn-orange" onclick="openImportModal()">CSV取込</button>
<button class="btn btn-blue" onclick="window.print()">印刷</button>
<a class="btn btn-gray" href="index.html">← メニューへ戻る</a>
</div>
</div>
<div id="msgBar" class="msg-bar no-print"></div>
<!-- 検索フォーム -->
<div class="search-form no-print">
<label>年:</label>
<input type="number" id="filterYear" min="2000" max="2099" style="width:80px" />
<label>月:</label>
<select id="filterMonth">
<option value="">全月</option>
<option value="1">1月</option><option value="2">2月</option>
<option value="3">3月</option><option value="4">4月</option>
<option value="5">5月</option><option value="6">6月</option>
<option value="7">7月</option><option value="8">8月</option>
<option value="9">9月</option><option value="10">10月</option>
<option value="11">11月</option><option value="12">12月</option>
</select>
<label>銀行:</label>
<select id="filterBank">
<option value="">全銀行</option>
</select>
<button class="btn btn-primary" onclick="loadData()">検索</button>
</div>
<!-- 月別チップ -->
<div class="monthly-summary" id="monthlyChips"></div>
<!-- 統計バー -->
<div class="stats-bar" id="statsBar"></div>
<!-- 明細テーブル -->
<div id="tableWrap">
<div class="no-data">データを読み込み中…</div>
</div>
<div id="paginationBar" class="pagination no-print"></div>
<!-- ========== CSV取込モーダル ========== -->
<div class="modal-overlay no-print" id="importModal">
<div class="modal-box">
<h3>📂 銀行明細 CSV 取込</h3>
<!-- ステップ1: ファイル選択 -->
<div id="step1">
<div class="modal-form-row">
<label>銀行名:</label>
<input type="text" id="bankNameInput" placeholder="例三菱UFJ銀行 本店普通口座" />
</div>
<div class="drop-zone" id="dropZone" onclick="document.getElementById('csvFile').click()">
<span class="drop-icon">📄</span>
<div>CSVファイルをドラッグドロップ、またはクリックして選択</div>
<div style="font-size:12px;color:#999;margin-top:6px">対応形式: CSVUTF-8 / Shift-JIS</div>
<input type="file" id="csvFile" accept=".csv,.txt" onchange="onFileSelected(this)" />
</div>
<div id="step1msg" class="msg-bar"></div>
<div class="modal-btn-row">
<button class="btn btn-gray" onclick="closeImportModal()">キャンセル</button>
</div>
</div>
<!-- ステップ2: プレビュー -->
<div id="step2" style="display:none">
<div class="preview-summary" id="previewSummary"></div>
<div class="preview-table-wrap">
<table id="previewTable">
<thead>
<tr>
<th style="width:36px">状態</th>
<th>日付</th>
<th>摘要</th>
<th>出金</th>
<th>入金</th>
<th>残高</th>
<th>メモ</th>
</tr>
</thead>
<tbody id="previewBody"></tbody>
</table>
</div>
<div id="step2msg" class="msg-bar"></div>
<div class="modal-btn-row">
<button class="btn btn-gray" onclick="backToStep1()">← 戻る</button>
<button class="btn btn-primary" id="importConfirmBtn" onclick="confirmImport()">
新規データを取込む
</button>
</div>
</div>
<!-- ステップ3: 完了 -->
<div id="step3" style="display:none">
<div id="step3msg" class="msg-bar success" style="display:block"></div>
<div class="modal-btn-row" style="margin-top:16px">
<button class="btn btn-primary" onclick="closeImportModal(); loadData()">
明細を表示する
</button>
<button class="btn btn-orange" onclick="backToStep1()">続けて取込む</button>
</div>
</div>
</div>
</div>
<script>
// ── 初期化 ──────────────────────────────────────────────
let _previewRows = [];
let _bankName = '';
let _allRows = [];
let _currentPage = 1;
const PAGE_SIZE = 100;
document.addEventListener('DOMContentLoaded', () => {
loadBanks();
loadData();
initDropZone();
});
// ── メッセージ表示 ──────────────────────────────────────
function showMsg(id, text, type) {
const el = document.getElementById(id);
el.textContent = text;
el.className = 'msg-bar ' + type;
el.style.display = 'block';
}
function hideMsg(id) {
const el = document.getElementById(id);
el.style.display = 'none';
}
// ── 銀行名リスト読み込み ────────────────────────────────
async function loadBanks() {
try {
const res = await fetch('/bank-statements?limit=1');
if (!res.ok) return;
const data = await res.json();
const sel = document.getElementById('filterBank');
const cur = sel.value;
// 既存オプションを保持しつつ追加
const existing = Array.from(sel.options).map(o => o.value);
data.banks.forEach(b => {
if (!existing.includes(b)) {
const opt = document.createElement('option');
opt.value = b; opt.textContent = b;
sel.appendChild(opt);
}
});
if (cur) sel.value = cur;
} catch (_) {}
}
// ── 明細データ読み込み ──────────────────────────────────
async function loadData() {
const year = document.getElementById('filterYear').value;
const month = document.getElementById('filterMonth').value;
const bank = document.getElementById('filterBank').value;
hideMsg('msgBar');
const qp = [];
if (year) qp.push(`year=${year}`);
if (month) qp.push(`month=${month}`);
if (bank) qp.push(`bank_name=${encodeURIComponent(bank)}`);
const url = '/bank-statements' + (qp.length ? '?' + qp.join('&') : '');
try {
const res = await fetch(url);
if (!res.ok) throw new Error(await res.text());
const data = await res.json();
_allRows = data.rows;
_currentPage = 1;
renderMonthlyChips(data.monthly, year, month);
renderStats();
renderTable();
updateBankFilter(data.banks);
} catch (e) {
showMsg('msgBar', 'データ取得エラー: ' + e.message, 'error');
}
}
function updateBankFilter(banks) {
const sel = document.getElementById('filterBank');
const cur = sel.value;
// 全銀行以外のオプションをクリアして再構築
while (sel.options.length > 1) sel.remove(1);
banks.forEach(b => {
const opt = document.createElement('option');
opt.value = b; opt.textContent = b;
sel.appendChild(opt);
});
if (cur) sel.value = cur;
}
// ── 月別チップ ──────────────────────────────────────────
function renderMonthlyChips(monthly, selYear, selMonth) {
const wrap = document.getElementById('monthlyChips');
wrap.innerHTML = '';
if (!monthly.length) return;
// 「全月」チップ
const allChip = document.createElement('span');
allChip.className = 'month-chip no-print' + (!selMonth ? ' active' : '');
allChip.textContent = '全月';
allChip.onclick = () => {
document.getElementById('filterMonth').value = '';
loadData();
};
wrap.appendChild(allChip);
monthly.forEach(m => {
const [y, mo] = m.ym.split('-');
const chip = document.createElement('span');
const isActive = (String(selYear) === y && String(selMonth) === String(parseInt(mo)));
chip.className = 'month-chip no-print' + (isActive ? ' active' : '');
chip.innerHTML = `${parseInt(mo)}月 <small style="color:inherit;opacity:.8">${
(m.count)}件</small>`;
chip.title =
`出金 ${fmt(m.total_debit)} / 入金 ${fmt(m.total_credit)}`;
chip.onclick = () => {
document.getElementById('filterYear').value = y;
document.getElementById('filterMonth').value = parseInt(mo);
loadData();
};
wrap.appendChild(chip);
});
}
// ── 統計バー ────────────────────────────────────────────
function renderStats() {
const bar = document.getElementById('statsBar');
if (!_allRows.length) { bar.innerHTML = ''; return; }
const totalDebit = _allRows.reduce((s, r) => s + (r.debit || 0), 0);
const totalCredit = _allRows.reduce((s, r) => s + (r.credit || 0), 0);
bar.innerHTML = `
<div class="stat-item">
<span class="stat-label">総件数:</span>
<span class="stat-value">${_allRows.length.toLocaleString()} 件</span>
</div>
<div class="stat-item">
<span class="stat-label">出金合計:</span>
<span class="stat-value debit">${fmt(totalDebit)}</span>
</div>
<div class="stat-item">
<span class="stat-label">入金合計:</span>
<span class="stat-value credit">${fmt(totalCredit)}</span>
</div>
<div class="stat-item">
<span class="stat-label">差引:</span>
<span class="stat-value">${fmt(totalCredit - totalDebit)}</span>
</div>
`;
}
// ── 明細テーブル ─────────────────────────────────────────
function renderTable() {
const wrap = document.getElementById('tableWrap');
if (!_allRows.length) {
wrap.innerHTML = '<div class="no-data">該当する明細データがありません</div>';
renderPagination(0);
return;
}
const start = (_currentPage - 1) * PAGE_SIZE;
const pageRows = _allRows.slice(start, start + PAGE_SIZE);
let html = `
<table>
<thead>
<tr>
<th class="text-center">日付</th>
<th class="text-left">摘要</th>
<th class="text-right">出金</th>
<th class="text-right">入金</th>
<th class="text-right">残高</th>
<th class="text-left">メモ</th>
<th class="text-center no-print">銀行</th>
</tr>
</thead>
<tbody>
`;
pageRows.forEach(r => {
html += `<tr>
<td class="text-center">${r.tx_date}</td>
<td class="text-left">${esc(r.description)}</td>
<td class="text-right debit-cell">${r.debit ? fmt(r.debit) : ''}</td>
<td class="text-right credit-cell">${r.credit ? fmt(r.credit) : ''}</td>
<td class="text-right">${r.balance != null ? fmt(r.balance) : ''}</td>
<td class="text-left">${esc(r.memo || '')}</td>
<td class="text-center no-print">${esc(r.bank_name || '')}</td>
</tr>`;
});
html += '</tbody></table>';
wrap.innerHTML = html;
renderPagination(_allRows.length);
}
function renderPagination(total) {
const bar = document.getElementById('paginationBar');
if (!bar) return;
const totalPages = Math.ceil(total / PAGE_SIZE);
if (totalPages <= 1) { bar.innerHTML = ''; return; }
const start = (_currentPage - 1) * PAGE_SIZE + 1;
const end = Math.min(_currentPage * PAGE_SIZE, total);
bar.innerHTML = `
<button class="btn btn-page" onclick="changePage(${_currentPage - 1})" ${_currentPage <= 1 ? 'disabled' : ''}>◀ 前へ</button>
<span class="page-info">${_currentPage} / ${totalPages} ページ(${start}${end}件 / 全${total}件)</span>
<button class="btn btn-page" onclick="changePage(${_currentPage + 1})" ${_currentPage >= totalPages ? 'disabled' : ''}>次へ ▶</button>
`;
}
function changePage(page) {
const totalPages = Math.ceil(_allRows.length / PAGE_SIZE);
if (page < 1 || page > totalPages) return;
_currentPage = page;
renderTable();
window.scrollTo(0, 0);
}
// ── CSV取込モーダル ──────────────────────────────────────
function openImportModal() {
backToStep1();
document.getElementById('importModal').classList.add('active');
}
function closeImportModal() {
document.getElementById('importModal').classList.remove('active');
}
function backToStep1() {
document.getElementById('step1').style.display = '';
document.getElementById('step2').style.display = 'none';
document.getElementById('step3').style.display = 'none';
document.getElementById('csvFile').value = '';
hideMsg('step1msg');
hideMsg('step2msg');
_previewRows = [];
}
// ドラッグ&ドロップ初期化
function initDropZone() {
const zone = document.getElementById('dropZone');
zone.addEventListener('dragover', e => {
e.preventDefault();
zone.classList.add('dragover');
});
zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
zone.addEventListener('drop', e => {
e.preventDefault();
zone.classList.remove('dragover');
const f = e.dataTransfer.files[0];
if (f) uploadAndPreview(f);
});
}
function onFileSelected(input) {
if (input.files[0]) uploadAndPreview(input.files[0]);
}
async function uploadAndPreview(file) {
if (!file.name.match(/\.(csv|txt)$/i)) {
showMsg('step1msg', 'CSVファイルを選択してください.csv または .txt', 'error');
return;
}
showMsg('step1msg', '解析中...', 'info');
const fd = new FormData();
fd.append('file', file);
fd.append('bank_name', document.getElementById('bankNameInput').value);
try {
const res = await fetch('/bank-statements/preview', { method: 'POST', body: fd });
const data = await res.json();
if (!res.ok) {
showMsg('step1msg', data.detail || 'エラーが発生しました', 'error');
return;
}
hideMsg('step1msg');
_previewRows = data.rows;
// 銀行名: 入力欄が空でAPIがヒントを返した場合は自動セット
if (data.bank_name && !document.getElementById('bankNameInput').value.trim()) {
document.getElementById('bankNameInput').value = data.bank_name;
}
_bankName = document.getElementById('bankNameInput').value;
renderPreview(data);
document.getElementById('step1').style.display = 'none';
document.getElementById('step2').style.display = '';
} catch (e) {
showMsg('step1msg', 'アップロードエラー: ' + e.message, 'error');
}
}
function renderPreview(data) {
// サマリー
document.getElementById('previewSummary').innerHTML = `
<div class="ps-item ps-total">合計: ${data.total} 件</div>
<div class="ps-item ps-new">🆕 新規: ${data.new} 件(取込対象)</div>
<div class="ps-item ps-dup">⚠️ 重複: ${data.duplicates} 件(スキップ)</div>
`;
// 取込ボタンの状態
const btn = document.getElementById('importConfirmBtn');
if (data.new === 0) {
btn.disabled = true;
btn.textContent = '新規データなし(取込不要)';
btn.style.opacity = '0.5';
} else {
btn.disabled = false;
btn.textContent = `新規 ${data.new} 件を取込む`;
btn.style.opacity = '1';
}
// プレビューテーブル
const tbody = document.getElementById('previewBody');
tbody.innerHTML = '';
data.rows.forEach(r => {
const tr = document.createElement('tr');
tr.className = r.status === 'new' ? 'row-new' : 'row-dup';
tr.innerHTML = `
<td class="text-center">
<span class="badge badge-${r.status === 'new' ? 'new' : 'dup'}">
${r.status === 'new' ? '新規' : '重複'}
</span>
</td>
<td>${r.tx_date}</td>
<td>${esc(r.description)}</td>
<td class="text-right">${r.debit ? fmt(r.debit) : ''}</td>
<td class="text-right">${r.credit ? fmt(r.credit) : ''}</td>
<td class="text-right">${r.balance != null ? fmt(r.balance) : ''}</td>
<td>${esc(r.memo || '')}</td>
`;
tbody.appendChild(tr);
});
}
async function confirmImport() {
const newRows = _previewRows.filter(r => r.status === 'new');
if (!newRows.length) return;
const btn = document.getElementById('importConfirmBtn');
btn.disabled = true;
btn.textContent = '取込中...';
hideMsg('step2msg');
try {
const res = await fetch('/bank-statements/import', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bank_name: _bankName, rows: newRows }),
});
const data = await res.json();
if (!res.ok) {
showMsg('step2msg', data.detail || '取込エラー', 'error');
btn.disabled = false;
btn.textContent = `新規 ${newRows.length} 件を取込む`;
return;
}
// 完了画面
document.getElementById('step2').style.display = 'none';
document.getElementById('step3').style.display = '';
document.getElementById('step3msg').textContent =
`✅ 取込完了:${data.inserted} 件を登録しました` +
(data.skipped ? `${data.skipped} 件スキップ)` : '');
loadBanks();
} catch (e) {
showMsg('step2msg', '通信エラー: ' + e.message, 'error');
btn.disabled = false;
btn.textContent = `新規 ${newRows.length} 件を取込む`;
}
}
// ── ユーティリティ ──────────────────────────────────────
function fmt(v) {
if (v == null || v === '') return '';
return Number(v).toLocaleString('ja-JP');
}
function esc(s) {
return String(s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
</script>
</body>
</html>

62
frontend/chat/Caddyfile Normal file
View File

@@ -0,0 +1,62 @@
chat.njts.co.jp:2015 {
tls /certs/chat.njts.co.jp/fullchain.pem /certs/chat.njts.co.jp/key.pem
reverse_proxy rocketchat:3000
}
nas.njts.co.jp:2015 {
tls /certs/nas.njts.co.jp/fullchain.pem /certs/nas.njts.co.jp/key.pem
reverse_proxy 192.168.0.61:5666
}
synas.njts.co.jp:2015 {
tls /certs/synas.njts.co.jp/fullchain.pem /certs/synas.njts.co.jp/key.pem
reverse_proxy 192.168.0.193:5000
}
meet.njts.co.jp:2015 {
tls /certs/meet.njts.co.jp/fullchain.pem /certs/meet.njts.co.jp/key.pem
reverse_proxy jitsi-web-1:80
}
matrix.njts.co.jp:2015 {
tls /certs/matrix.njts.co.jp/fullchain.pem /certs/matrix.njts.co.jp/key.pem
reverse_proxy matrix-synapse:8008
}
element.njts.co.jp:2015 {
tls /certs/element.njts.co.jp/fullchain.pem /certs/element.njts.co.jp/key.pem
reverse_proxy element-web:80
}
router.njts.co.jp:2015 {
tls /certs/router.njts.co.jp/fullchain.pem /certs/router.njts.co.jp/key.pem
reverse_proxy 192.168.0.1:80
}
cmr.njts.co.jp:2015 {
basicauth {
zhangxianghe $2a$14$a00NihP7NRLzd3j5EvFa2uFYXHJ.NoWyCcgKA3NKpUG22607HN/Ri
}
tls /certs/cmr.njts.co.jp/fullchain.pem /certs/cmr.njts.co.jp/key.pem
reverse_proxy 192.168.0.61:5000
}
openclaw.njts.co.jp:2015 {
tls /certs/openclaw.njts.co.jp/fullchain.pem /certs/openclaw.njts.co.jp/key.pem
reverse_proxy 192.168.0.61:18789
}
chatai.njts.co.jp:2015 {
tls /certs/chatai.njts.co.jp/fullchain.pem /certs/chatai.njts.co.jp/key.pem
reverse_proxy 192.168.0.61:3000
}
sys.njts.co.jp:2015 {
tls /certs/sys.njts.co.jp/fullchain.pem /certs/sys.njts.co.jp/key.pem
reverse_proxy 192.168.0.61:18000
}

View File

@@ -0,0 +1 @@
{"apps":{"http":{"servers":{"srv0":{"listen":[":8443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"rocketchat:3000"}]}]}]}],"match":[{"host":["chat.njts.co.jp"]}],"terminal":true}]}}}}}

View File

@@ -0,0 +1,5 @@
{
"status": "valid",
"termsOfServiceAgreed": true,
"location": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/254799123"
}

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKj+HhAaYqX1lWm4mLP2Et8b5rxVmCuGgMZpfvT/KEN4oAoGCCqGSM49
AwEHoUQDQgAED9JJ499KtFgQS78wE5cxUs3iQoBq75s3my0knVRJah0H3VwwOtJq
vKMmgyX4vUT0fDTYfgrvrLALcJ9QT4Bx9Q==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,5 @@
{
"status": "valid",
"termsOfServiceAgreed": true,
"location": "https://acme-v02.api.letsencrypt.org/acme/acct/2926552736"
}

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDs4tIRZUhyrIoIgZSO6ZBAYg+HzW0l0j2OlVoMzJY6ioAoGCCqGSM49
AwEHoUQDQgAEv375la4g5tzk4LddVNaLZ7jBelpGzkADYERdZzELLLFL0NEciAa1
Lus7P6OxGoc30AsCw34co3SXMhWEp3gsAg==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1 @@
268cd375-73e5-4ccb-88ca-c0051a74b1f2

View File

@@ -0,0 +1 @@
{"tls":{"timestamp":"2026-01-01T11:51:10.149840886Z","instance_id":"268cd375-73e5-4ccb-88ca-c0051a74b1f2"}}

View File

@@ -0,0 +1,68 @@
-----BEGIN CERTIFICATE-----
MIID9jCCA3ygAwIBAgIQcfzZIhLrRJHPhJLzeOAN5DAKBggqhkjOPQQDAzBLMQsw
CQYDVQQGEwJBVDEQMA4GA1UEChMHWmVyb1NTTDEqMCgGA1UEAxMhWmVyb1NTTCBF
Q0MgRG9tYWluIFNlY3VyZSBTaXRlIENBMB4XDTI1MTIzMTAwMDAwMFoXDTI2MDMz
MTIzNTk1OVowGjEYMBYGA1UEAxMPY2hhdC5uanRzLmNvLmpwMFkwEwYHKoZIzj0C
AQYIKoZIzj0DAQcDQgAEOp7tnlqOPWi6GswskLGaQ7J8pjU5YQjlNgPXVYr93VWT
Wp5oBt+Q1EPZoLSErJ05xzVgYTzzPf5Z92jk+crZsqOCAnEwggJtMB8GA1UdIwQY
MBaAFA9r5kvOOUeu9n6QHnnwMJGSyF+jMB0GA1UdDgQWBBQsF22yoIoKX6aCGL6f
2veSYHlq1TAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAK
BggrBgEFBQcDATBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgJOMCUwIwYIKwYBBQUH
AgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBiAYIKwYBBQUH
AQEEfDB6MEsGCCsGAQUFBzAChj9odHRwOi8vemVyb3NzbC5jcnQuc2VjdGlnby5j
b20vWmVyb1NTTEVDQ0RvbWFpblNlY3VyZVNpdGVDQS5jcnQwKwYIKwYBBQUHMAGG
H2h0dHA6Ly96ZXJvc3NsLm9jc3Auc2VjdGlnby5jb20wggEEBgorBgEEAdZ5AgQC
BIH1BIHyAPAAdgAOV5S8866pPjMbLJkHs/eQ35vCPXEyJd0hqSWsYcVOIQAAAZtz
uvUJAAAEAwBHMEUCIHjB8YtQ9aDlxSRut60eoDQ/h7fraLoPxLOEsBB/Hu/AAiEA
5USOxiL99VrBdF4HTcJoM2AomuA+MvmpfVcsr1bXUqQAdgDRbqmlaAd+ZjWgPzel
3bwDpTxBEhTUiBj16TGzI8uVBAAAAZtzuvXXAAAEAwBHMEUCIBGuQL/+LnZdckCW
jGHUMPwztijW/8gfzcK8j3ULmm6FAiEAzI2YGnTwt5rYKIVaVU9nZGuATFlx9gtB
iiz2Roppz9UwGgYDVR0RBBMwEYIPY2hhdC5uanRzLmNvLmpwMAoGCCqGSM49BAMD
A2gAMGUCMQDb3BnY/YT49vWBxF1/h4eX26Ayn+5AEzn7ZcDpSVl20pAwJsqRjn7f
hodKgvtO3Y0CMDAkUJFzHKY4VP2oVhtldYIgfveAauS3YYGFEY25NVnVI224NSt6
CiUqiJf/0Pjpdw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDhTCCAwygAwIBAgIQI7dt48G7KxpRlh4I6rdk6DAKBggqhkjOPQQDAzCBiDEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjAwMTMw
MDAwMDAwWhcNMzAwMTI5MjM1OTU5WjBLMQswCQYDVQQGEwJBVDEQMA4GA1UEChMH
WmVyb1NTTDEqMCgGA1UEAxMhWmVyb1NTTCBFQ0MgRG9tYWluIFNlY3VyZSBTaXRl
IENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAENkFhFytTJe2qypTk1tpIV+9QuoRk
gte7BRvWHwYk9qUznYzn8QtVaGOCMBBfjWXsqqivl8q1hs4wAYl03uNOXgFu7iZ7
zFP6I6T3RB0+TR5fZqathfby47yOCZiAJI4go4IBdTCCAXEwHwYDVR0jBBgwFoAU
OuEJhtTPGcKWdnRJdtzgNcZjY5owHQYDVR0OBBYEFA9r5kvOOUeu9n6QHnnwMJGS
yF+jMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQW
MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAiBgNVHSAEGzAZMA0GCysGAQQBsjEBAgJO
MAgGBmeBDAECATBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVz
dC5jb20vVVNFUlRydXN0RUNDQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdgYI
KwYBBQUHAQEEajBoMD8GCCsGAQUFBzAChjNodHRwOi8vY3J0LnVzZXJ0cnVzdC5j
b20vVVNFUlRydXN0RUNDQWRkVHJ1c3RDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6
Ly9vY3NwLnVzZXJ0cnVzdC5jb20wCgYIKoZIzj0EAwMDZwAwZAIwJHBUDwHJQN3I
VNltVMrICMqYQ3TYP/TXqV9t8mG5cAomG2MwqIsxnL937Gewf6WIAjAlrauksO6N
UuDdDXyd330druJcZJx0+H5j5cFOYBaGsKdeGW7sCMaR2PsDFKGllas=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID0zCCArugAwIBAgIQVmcdBOpPmUxvEIFHWdJ1lDANBgkqhkiG9w0BAQwFADB7
MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE
AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4
MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5
MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO
ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgRUNDIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEGqxUWqn5aCPnetUkb1PGWthL
q8bVttHmc3Gu3ZzWDGH926CJA7gFFOxXzu5dP+Ihs8731Ip54KODfi2X0GHE8Znc
JZFjq38wo7Rw4sehM5zzvy5cU7Ffs30yf4o043l5o4HyMIHvMB8GA1UdIwQYMBaA
FKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1
xmNjmjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAI
MAYGBFUdIAAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5j
b20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQG
CCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEM
BQADggEBABns652JLCALBIAdGN5CmXKZFjK9Dpx1WywV4ilAbe7/ctvbq5AfjJXy
ij0IckKJUAfiORVsAYfZFhr1wHUrxeZWEQff2Ji8fJ8ZOd+LygBkc7xGEJuTI42+
FsMuCIKchjN0djsoTI0DQoWz4rIjQtUfenVqGtF8qmchxDM6OW1TyaLtYiKou+JV
bJlsQ2uRl9EMC5MCHdK8aXdJ5htN978UeAOwproLtOGFfy/cQjutdAFI3tZs4RmY
CV4Ks2dH/hzg1cEo70qLRDEmBDeNiXQ2Lu+lIg+DdEmSx/cQwgwp+7e9un/jX9Wf
8qn0dNW44bOwgeThpWOjzOoEeJBuv/c=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIPjpQxKBjFFc8UvRnWDDKX2OP+ypSHXGYZ2cpRgPBP3koAoGCCqGSM49
AwEHoUQDQgAEOp7tnlqOPWi6GswskLGaQ7J8pjU5YQjlNgPXVYr93VWTWp5oBt+Q
1EPZoLSErJ05xzVgYTzzPf5Z92jk+crZsg==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,62 @@
version: "3.8"
services:
rocketchat:
image: registry.rocket.chat/rocketchat/rocket.chat:8.2.1
container_name: rocketchat
restart: unless-stopped
environment:
MONGO_URL: mongodb://rocketchat-mongo:27017/rocketchat?replicaSet=rs0
MONGO_OPLOG_URL: mongodb://rocketchat-mongo:27017/local?replicaSet=rs0
ROOT_URL: https://chat.njts.co.jp
PORT: 3000
depends_on:
- mongo
networks:
- chat
mongo:
image: mongo:8.0.13
container_name: rocketchat-mongo
restart: unless-stopped
command: mongod --replSet rs0 --oplogSize 128
volumes:
- mongo-data:/data/db
networks:
- chat
mongo-init:
image: mongo:8.0.13
restart: "no"
depends_on:
- mongo
command: >
mongosh --host mongo:27017 --eval
"rs.initiate({_id:'rs0',members:[{_id:0,host:'mongo:27017'}]})"
networks:
- chat
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- "2015:2015"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- /etc/caddy/certs:/certs:ro
- caddy_data:/data
- caddy_config:/config
networks:
- chat
- shared_net
volumes:
mongo-data:
caddy_data:
caddy_config:
networks:
chat:
shared_net:
external: true

View File

@@ -323,4 +323,58 @@ html {
width: 100% !important;
box-sizing: border-box !important;
}
/* ── split-layout: PC は左右分割、モバイルは上下積み ── */
/* 左リスト・右明細を縦方向に並べ直す */
.split-layout {
flex-direction: column !important;
}
.split-left {
flex: none !important;
width: 100% !important;
max-width: none !important;
min-width: 0 !important;
max-height: none !important;
overflow-y: visible !important;
position: static !important;
}
.split-right {
width: 100% !important;
min-width: 0 !important;
border-left: none !important;
border-top: 2px solid #e0e8f0 !important;
padding-left: 0 !important;
padding-top: 16px !important;
margin-top: 8px !important;
max-height: none !important;
overflow-y: visible !important;
position: static !important;
}
/* ── タブ(給与計算・賞与計算・帳票出力)を折り返し ── */
.nav-tabs-calc {
flex-wrap: wrap !important;
gap: 6px !important;
border-bottom: none !important;
}
.nav-tab-calc {
padding: 8px 14px !important;
font-size: 0.9rem !important;
border-radius: 6px !important;
}
/* ── 新規計算モーダルinline style の width:800px を上書き) ── */
#calculateModal,
#bonusModal {
width: 92vw !important;
left: 4vw !important;
transform: none !important;
max-width: 92vw !important;
padding: 20px 16px !important;
box-sizing: border-box !important;
}
}

View File

@@ -284,7 +284,7 @@
<body>
<header>
<h1>🏛 e-Gov 電子送達</h1>
<a class="back-link" href="index.html">← メニュー戻る</a>
<a class="back-link" href="index.html" style="display:inline-block;padding:6px 16px;background:#757575;color:#fff;border-radius:4px;text-decoration:none;font-size:14px;">← メニュー戻る</a>
</header>
<!-- ログインカード -->

View File

@@ -3,6 +3,8 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<title>NJTS 会計システム - メイン</title>
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="/css/mobile.css" />
@@ -190,6 +192,16 @@
<p><a class="btn" href="payroll.html">給与システムを開く</a></p>
</section>
<section class="card">
<h2>🏦 銀行明細照会</h2>
<p>
銀行のCSVデータを取込み、明細を管理します。重複チェック付きで安全に取込め、月別・銀行別の検索・印刷が可能です。
</p>
<p>
<a class="btn" href="bank-statement.html" style="background: #1565c0">銀行明細を開く</a>
</p>
</section>
<section class="card">
<h2>🏛 e-Gov 電子送達</h2>
<p>
@@ -263,18 +275,6 @@
}
}
// dropdown toggle
const toggle = document.getElementById("accountingToggle");
const menu = document.getElementById("accountingMenu");
toggle.addEventListener("click", (e) => {
menu.style.display = menu.style.display === "block" ? "none" : "block";
});
// click outside to close
document.addEventListener("click", (e) => {
if (!toggle.contains(e.target) && !menu.contains(e.target)) {
menu.style.display = "none";
}
});
</script>
</body>
</html>

View File

@@ -257,22 +257,7 @@
</style>
</head>
<body>
<button
onclick="location.href = 'index.html'"
class="back-button"
style="
margin: 0 0 20px 0;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← 首ページに戻る
</button>
<a href="index.html" style="display:inline-block;margin:0 0 20px 0;padding:6px 16px;background:#757575;color:#fff;border-radius:4px;text-decoration:none;font-size:14px;">← メニューへ戻る</a>
<h2>仕訳入力</h2>
<div class="row" style="margin-bottom: 12px">

View File

@@ -56,21 +56,7 @@
>
保存
</button>
<button
onclick="location.href = 'index.html'"
style="
margin: 0 0 20px 0;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← メインメニューに戻る
</button>
<a href="index.html" style="display:inline-block;margin:0 0 20px 0;padding:6px 16px;background:#757575;color:#fff;border-radius:4px;text-decoration:none;font-size:14px;">← メニューへ戻る</a>
<script src="js/api.js"></script>
<script src="js/opening_balance.js"></script>

View File

@@ -1237,6 +1237,7 @@
? ((_child / _stdRemun) * 100).toFixed(3)
: null;
const _socialSub = _health + _care + _pension + _emp + _child;
const _healthCareChild = _health + _care + _child;
const _taxable = _totalPay - _socialSub;
const html = `
@@ -1328,6 +1329,10 @@
: "令和8年5月支払分4月分保険料より控除開始 → 当月は対象外"
}
</div>
<div class="calc-note" style="background:#eef6ee; border-left:3px solid #28a745; padding:6px 10px; margin:6px 0; font-size:0.88em; color:#1a5c1a; line-height:1.7">
健康保険 ¥${_health.toLocaleString()} 介護保険 ¥${_care.toLocaleString()} 子ども・子育て支援金 ¥${_child.toLocaleString()} <strong>¥${_healthCareChild.toLocaleString()}</strong><br>
¥${_healthCareChild.toLocaleString()}(上記合計) 厚生年金 ¥${_pension.toLocaleString()}${' '}${_emp > 0 ? ` 雇用保険 ¥${_emp.toLocaleString()}` : ''} <strong>社会保険小計 ¥${_socialSub.toLocaleString()}</strong>
</div>
<div class="detail-row"><span style="font-weight:bold">社会保険小計:</span><span style="font-weight:bold">¥${_socialSub.toLocaleString()}</span></div>
<div class="detail-row">
<span>所得税 <small style="color:#666;">(甲欄${dependentCount}人)</small>:</span>
@@ -1372,6 +1377,10 @@
const splitRightSalary = document.getElementById("splitRightSalary");
if (splitRightSalary) splitRightSalary.style.display = "block";
document.getElementById("payrollDetail").style.display = "block";
// モバイルでは明細パネルへ自動スクロール
if (window.innerWidth <= 767 && splitRightSalary) {
setTimeout(function() { splitRightSalary.scrollIntoView({ behavior: "smooth", block: "start" }); }, 50);
}
} catch (error) {
alert("給与明細の取得に失敗しました");
console.error(error);
@@ -1387,7 +1396,7 @@
// 編集フォームを表示
const html = `
<div style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); width: 800px; max-height: 90vh; overflow-y: auto; background: white; padding: 30px; border: 2px solid #007bff; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); z-index: 1000;">
<div style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); width: min(800px, 92vw); max-height: 90vh; overflow-y: auto; background: white; padding: 30px; border: 2px solid #007bff; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); z-index: 1000; box-sizing: border-box;">
<h2>給与データ編集</h2>
<form id="editPayrollForm" onsubmit="savePayrollEdit(event, ${payrollId})">
<div class="detail-section">
@@ -1842,6 +1851,7 @@
? ((_bChild / _bTotal) * 100).toFixed(4)
: null;
const _bSocialSub = _bHealth + _bCare + _bPension + _bEmp + _bChild;
const _bHealthCareChild = _bHealth + _bCare + _bChild;
const _bTaxable = _bTotal - _bSocialSub;
const html = `
@@ -1906,6 +1916,10 @@
: "令和8年5月支払分4月分保険料より控除開始 → 当賞与は対象外"
}
</div>
<div class="calc-note" style="background:#eef6ee; border-left:3px solid #28a745; padding:6px 10px; margin:6px 0; font-size:0.88em; color:#1a5c1a; line-height:1.7">
健康保険 ¥${_bHealth.toLocaleString()} 介護保険 ¥${_bCare.toLocaleString()} 子ども・子育て支援金 ¥${_bChild.toLocaleString()} <strong>¥${_bHealthCareChild.toLocaleString()}</strong><br>
¥${_bHealthCareChild.toLocaleString()}(上記合計) 厚生年金 ¥${_bPension.toLocaleString()}${' '}${_bEmp > 0 ? ` 雇用保険 ¥${_bEmp.toLocaleString()}` : ''} <strong>社会保険小計 ¥${_bSocialSub.toLocaleString()}</strong>
</div>
<div class="detail-row"><span style="font-weight:bold">社会保険小計:</span><span style="font-weight:bold">¥${_bSocialSub.toLocaleString()}</span></div>
<div class="detail-row">
<span>所得税 <small style="color:#666;">(甲欄${bDependentCount}人)</small>:</span>
@@ -1948,6 +1962,10 @@
const splitRightBonus = document.getElementById("splitRightBonus");
if (splitRightBonus) splitRightBonus.style.display = "block";
document.getElementById("bonusDetail").style.display = "block";
// モバイルでは明細パネルへ自動スクロール
if (window.innerWidth <= 767 && splitRightBonus) {
setTimeout(function() { splitRightBonus.scrollIntoView({ behavior: "smooth", block: "start" }); }, 50);
}
} catch (error) {
alert("賞与明細の取得に失敗しました");
console.error(error);
@@ -1960,7 +1978,7 @@
const bonus = await response.json();
const html = `
<div style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); width: 600px; max-height: 90vh; overflow-y: auto; background: white; padding: 30px; border: 2px solid #007bff; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); z-index: 1000;">
<div style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); width: min(600px, 92vw); max-height: 90vh; overflow-y: auto; background: white; padding: 30px; border: 2px solid #007bff; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); z-index: 1000; box-sizing: border-box;">
<h2>賞与データ編集</h2>
<form id="editBonusForm" onsubmit="saveBonusEdit(event, ${bonusId})">
<div class="detail-section">

View File

@@ -56,21 +56,7 @@
</head>
<body>
<div class="container">
<button
onclick="location.href = '../index.html'"
class="back-button"
style="
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← メインメニューに戻る
</button>
<a href="index.html" style="display:inline-block;padding:6px 16px;background:#757575;color:#fff;border-radius:4px;text-decoration:none;font-size:14px;">← メニューへ戻る</a>
<h1>給与管理システム</h1>
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>

View File

@@ -267,22 +267,7 @@
</style>
</head>
<body>
<button
onclick="location.href = 'index.html'"
class="back-button"
style="
margin: 0 0 20px 0;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← 首ページに戻る
</button>
<a href="index.html" style="display:inline-block;margin:0 0 20px 0;padding:6px 16px;background:#757575;color:#fff;border-radius:4px;text-decoration:none;font-size:14px;">← メニューへ戻る</a>
<h2>残高試算表(貸借・損益)</h2>