修正
This commit is contained in:
@@ -28,8 +28,14 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.page-header h2 { margin: 0; }
|
.page-header h2 {
|
||||||
.header-btns { display: flex; gap: 8px; flex-wrap: wrap; }
|
margin: 0;
|
||||||
|
}
|
||||||
|
.header-btns {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── 検索フォーム ── */
|
/* ── 検索フォーム ── */
|
||||||
.search-form {
|
.search-form {
|
||||||
@@ -43,7 +49,10 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.search-form label { font-weight: bold; white-space: nowrap; }
|
.search-form label {
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.search-form select,
|
.search-form select,
|
||||||
.search-form input[type="number"] {
|
.search-form input[type="number"] {
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
@@ -65,16 +74,41 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.btn-primary { background: #4caf50; color: #fff; }
|
.btn-primary {
|
||||||
.btn-primary:hover { background: #43a047; }
|
background: #4caf50;
|
||||||
.btn-blue { background: #2196f3; color: #fff; }
|
color: #fff;
|
||||||
.btn-blue:hover { background: #1976d2; }
|
}
|
||||||
.btn-orange { background: #ff9800; color: #fff; }
|
.btn-primary:hover {
|
||||||
.btn-orange:hover { background: #f57c00; }
|
background: #43a047;
|
||||||
.btn-danger { background: #e53935; color: #fff; }
|
}
|
||||||
.btn-danger:hover { background: #c62828; }
|
.btn-blue {
|
||||||
.btn-gray { background: #9e9e9e; color: #fff; }
|
background: #2196f3;
|
||||||
.btn-gray:hover { background: #757575; }
|
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 {
|
.monthly-summary {
|
||||||
@@ -92,8 +126,15 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.month-chip:hover { background: #e3f2fd; border-color: #2196f3; }
|
.month-chip:hover {
|
||||||
.month-chip.active { background: #2196f3; color: #fff; border-color: #1976d2; }
|
background: #e3f2fd;
|
||||||
|
border-color: #2196f3;
|
||||||
|
}
|
||||||
|
.month-chip.active {
|
||||||
|
background: #2196f3;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── 統計バー ── */
|
/* ── 統計バー ── */
|
||||||
.stats-bar {
|
.stats-bar {
|
||||||
@@ -103,11 +144,23 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.stat-item { display: flex; align-items: center; gap: 6px; }
|
.stat-item {
|
||||||
.stat-label { color: #666; }
|
display: flex;
|
||||||
.stat-value { font-weight: bold; }
|
align-items: center;
|
||||||
.stat-value.debit { color: #e53935; }
|
gap: 6px;
|
||||||
.stat-value.credit { color: #1565c0; }
|
}
|
||||||
|
.stat-label {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.stat-value.debit {
|
||||||
|
color: #e53935;
|
||||||
|
}
|
||||||
|
.stat-value.credit {
|
||||||
|
color: #1565c0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── テーブル ── */
|
/* ── テーブル ── */
|
||||||
table {
|
table {
|
||||||
@@ -115,7 +168,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
th, td {
|
th,
|
||||||
|
td {
|
||||||
border: 1px solid #bbb;
|
border: 1px solid #bbb;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
}
|
}
|
||||||
@@ -125,14 +179,30 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
td { background: #fff; }
|
td {
|
||||||
tr:nth-child(even) td { background: #fafafa; }
|
background: #fff;
|
||||||
tr:hover td { background: #e3f2fd; }
|
}
|
||||||
.text-right { text-align: right; }
|
tr:nth-child(even) td {
|
||||||
.text-center { text-align: center; }
|
background: #fafafa;
|
||||||
.text-left { text-align: left; }
|
}
|
||||||
td.debit-cell { color: #c62828; }
|
tr:hover td {
|
||||||
td.credit-cell { color: #1565c0; }
|
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 {
|
.no-data {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #999;
|
color: #999;
|
||||||
@@ -152,7 +222,9 @@
|
|||||||
padding: 30px 16px;
|
padding: 30px 16px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.modal-overlay.active { display: flex; }
|
.modal-overlay.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.modal-box {
|
.modal-box {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -161,7 +233,10 @@
|
|||||||
max-width: 920px;
|
max-width: 920px;
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
.modal-box h3 { margin: 0 0 18px; font-size: 17px; }
|
.modal-box h3 {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
.modal-form-row {
|
.modal-form-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -169,7 +244,10 @@
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.modal-form-row label { font-weight: bold; white-space: nowrap; }
|
.modal-form-row label {
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.modal-form-row input[type="text"] {
|
.modal-form-row input[type="text"] {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
@@ -188,15 +266,24 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
transition: border-color 0.2s, background 0.2s;
|
transition:
|
||||||
|
border-color 0.2s,
|
||||||
|
background 0.2s;
|
||||||
}
|
}
|
||||||
.drop-zone:hover, .drop-zone.dragover {
|
.drop-zone:hover,
|
||||||
|
.drop-zone.dragover {
|
||||||
border-color: #2196f3;
|
border-color: #2196f3;
|
||||||
background: #e3f2fd;
|
background: #e3f2fd;
|
||||||
color: #1565c0;
|
color: #1565c0;
|
||||||
}
|
}
|
||||||
.drop-zone input[type="file"] { display: none; }
|
.drop-zone input[type="file"] {
|
||||||
.drop-zone .drop-icon { font-size: 36px; display: block; margin-bottom: 8px; }
|
display: none;
|
||||||
|
}
|
||||||
|
.drop-zone .drop-icon {
|
||||||
|
font-size: 36px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* プレビュー結果 */
|
/* プレビュー結果 */
|
||||||
.preview-summary {
|
.preview-summary {
|
||||||
@@ -208,10 +295,19 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.preview-summary .ps-item { font-size: 14px; }
|
.preview-summary .ps-item {
|
||||||
.ps-new { color: #2e7d32; font-weight: bold; }
|
font-size: 14px;
|
||||||
.ps-dup { color: #e65100; }
|
}
|
||||||
.ps-total { font-weight: bold; }
|
.ps-new {
|
||||||
|
color: #2e7d32;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.ps-dup {
|
||||||
|
color: #e65100;
|
||||||
|
}
|
||||||
|
.ps-total {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.preview-table-wrap {
|
.preview-table-wrap {
|
||||||
max-height: 360px;
|
max-height: 360px;
|
||||||
@@ -220,10 +316,21 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
.preview-table-wrap table { font-size: 12px; }
|
.preview-table-wrap table {
|
||||||
.preview-table-wrap th { position: sticky; top: 0; z-index: 1; }
|
font-size: 12px;
|
||||||
tr.row-new td { background: #e8f5e9 !important; }
|
}
|
||||||
tr.row-dup td { background: #fff3e0 !important; color: #999; }
|
.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 {
|
.badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
@@ -231,8 +338,14 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.badge-new { background: #c8e6c9; color: #1b5e20; }
|
.badge-new {
|
||||||
.badge-dup { background: #ffe0b2; color: #bf360c; }
|
background: #c8e6c9;
|
||||||
|
color: #1b5e20;
|
||||||
|
}
|
||||||
|
.badge-dup {
|
||||||
|
background: #ffe0b2;
|
||||||
|
color: #bf360c;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-btn-row {
|
.modal-btn-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -249,9 +362,21 @@
|
|||||||
display: none;
|
display: none;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.msg-bar.info { background: #e3f2fd; color: #0d47a1; border: 1px solid #90caf9; }
|
.msg-bar.info {
|
||||||
.msg-bar.success { background: #e8f5e9; color: #1b5e20; border: 1px solid #a5d6a7; }
|
background: #e3f2fd;
|
||||||
.msg-bar.error { background: #ffebee; color: #b71c1c; border: 1px solid #ef9a9a; }
|
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 {
|
.pagination {
|
||||||
@@ -263,19 +388,48 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
.page-info { font-size: 13px; color: #333; }
|
.page-info {
|
||||||
.btn-page { background: #f0f0f0; color: #333; border: 1px solid #bbb; }
|
font-size: 13px;
|
||||||
.btn-page:hover:not(:disabled) { background: #e0e0e0; }
|
color: #333;
|
||||||
.btn-page:disabled { opacity: 0.4; cursor: default; }
|
}
|
||||||
|
.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 {
|
@media print {
|
||||||
.no-print { display: none !important; }
|
.no-print {
|
||||||
body { margin: 0; font-size: 12px; }
|
display: none !important;
|
||||||
table { font-size: 11px; }
|
}
|
||||||
th, td { padding: 4px 6px; }
|
body {
|
||||||
.monthly-summary, .stats-bar { margin-bottom: 6px; }
|
margin: 0;
|
||||||
.month-chip { border: 1px solid #999 !important; color: #000 !important; background: #fff !important; }
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -283,7 +437,9 @@
|
|||||||
<div class="page-header no-print">
|
<div class="page-header no-print">
|
||||||
<h2>🏦 銀行明細照会</h2>
|
<h2>🏦 銀行明細照会</h2>
|
||||||
<div class="header-btns">
|
<div class="header-btns">
|
||||||
<button class="btn btn-orange" onclick="openImportModal()">CSV取込</button>
|
<button class="btn btn-orange" onclick="openImportModal()">
|
||||||
|
CSV取込
|
||||||
|
</button>
|
||||||
<button class="btn btn-blue" onclick="window.print()">印刷</button>
|
<button class="btn btn-blue" onclick="window.print()">印刷</button>
|
||||||
<a class="btn btn-gray" href="index.html">← メニューへ戻る</a>
|
<a class="btn btn-gray" href="index.html">← メニューへ戻る</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -294,16 +450,28 @@
|
|||||||
<!-- 検索フォーム -->
|
<!-- 検索フォーム -->
|
||||||
<div class="search-form no-print">
|
<div class="search-form no-print">
|
||||||
<label>年:</label>
|
<label>年:</label>
|
||||||
<input type="number" id="filterYear" min="2000" max="2099" style="width:80px" />
|
<input
|
||||||
|
type="number"
|
||||||
|
id="filterYear"
|
||||||
|
min="2000"
|
||||||
|
max="2099"
|
||||||
|
style="width: 80px"
|
||||||
|
/>
|
||||||
<label>月:</label>
|
<label>月:</label>
|
||||||
<select id="filterMonth">
|
<select id="filterMonth">
|
||||||
<option value="">全月</option>
|
<option value="">全月</option>
|
||||||
<option value="1">1月</option><option value="2">2月</option>
|
<option value="1">1月</option>
|
||||||
<option value="3">3月</option><option value="4">4月</option>
|
<option value="2">2月</option>
|
||||||
<option value="5">5月</option><option value="6">6月</option>
|
<option value="3">3月</option>
|
||||||
<option value="7">7月</option><option value="8">8月</option>
|
<option value="4">4月</option>
|
||||||
<option value="9">9月</option><option value="10">10月</option>
|
<option value="5">5月</option>
|
||||||
<option value="11">11月</option><option value="12">12月</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>
|
</select>
|
||||||
<label>銀行:</label>
|
<label>銀行:</label>
|
||||||
<select id="filterBank">
|
<select id="filterBank">
|
||||||
@@ -333,17 +501,34 @@
|
|||||||
<div id="step1">
|
<div id="step1">
|
||||||
<div class="modal-form-row">
|
<div class="modal-form-row">
|
||||||
<label>銀行名:</label>
|
<label>銀行名:</label>
|
||||||
<input type="text" id="bankNameInput" placeholder="例:三菱UFJ銀行 本店普通口座" />
|
<input
|
||||||
|
type="text"
|
||||||
|
id="bankNameInput"
|
||||||
|
placeholder="例:三菱UFJ銀行 本店普通口座"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="drop-zone" id="dropZone" onclick="document.getElementById('csvFile').click()">
|
<div
|
||||||
|
class="drop-zone"
|
||||||
|
id="dropZone"
|
||||||
|
onclick="document.getElementById('csvFile').click()"
|
||||||
|
>
|
||||||
<span class="drop-icon">📄</span>
|
<span class="drop-icon">📄</span>
|
||||||
<div>CSVファイルをドラッグ&ドロップ、またはクリックして選択</div>
|
<div>CSVファイルをドラッグ&ドロップ、またはクリックして選択</div>
|
||||||
<div style="font-size:12px;color:#999;margin-top:6px">対応形式: CSV(UTF-8 / Shift-JIS)</div>
|
<div style="font-size: 12px; color: #999; margin-top: 6px">
|
||||||
<input type="file" id="csvFile" accept=".csv,.txt" onchange="onFileSelected(this)" />
|
対応形式: CSV(UTF-8 / Shift-JIS)
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="csvFile"
|
||||||
|
accept=".csv,.txt"
|
||||||
|
onchange="onFileSelected(this)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="step1msg" class="msg-bar"></div>
|
<div id="step1msg" class="msg-bar"></div>
|
||||||
<div class="modal-btn-row">
|
<div class="modal-btn-row">
|
||||||
<button class="btn btn-gray" onclick="closeImportModal()">キャンセル</button>
|
<button class="btn btn-gray" onclick="closeImportModal()">
|
||||||
|
キャンセル
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -369,7 +554,11 @@
|
|||||||
<div id="step2msg" class="msg-bar"></div>
|
<div id="step2msg" class="msg-bar"></div>
|
||||||
<div class="modal-btn-row">
|
<div class="modal-btn-row">
|
||||||
<button class="btn btn-gray" onclick="backToStep1()">← 戻る</button>
|
<button class="btn btn-gray" onclick="backToStep1()">← 戻る</button>
|
||||||
<button class="btn btn-primary" id="importConfirmBtn" onclick="confirmImport()">
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
id="importConfirmBtn"
|
||||||
|
onclick="confirmImport()"
|
||||||
|
>
|
||||||
新規データを取込む
|
新規データを取込む
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -377,12 +566,24 @@
|
|||||||
|
|
||||||
<!-- ステップ3: 完了 -->
|
<!-- ステップ3: 完了 -->
|
||||||
<div id="step3" style="display: none">
|
<div id="step3" style="display: none">
|
||||||
<div id="step3msg" class="msg-bar success" style="display:block"></div>
|
<div
|
||||||
|
id="step3msg"
|
||||||
|
class="msg-bar success"
|
||||||
|
style="display: block"
|
||||||
|
></div>
|
||||||
<div class="modal-btn-row" style="margin-top: 16px">
|
<div class="modal-btn-row" style="margin-top: 16px">
|
||||||
<button class="btn btn-primary" onclick="closeImportModal(); loadData()">
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
onclick="
|
||||||
|
closeImportModal();
|
||||||
|
loadData();
|
||||||
|
"
|
||||||
|
>
|
||||||
明細を表示する
|
明細を表示する
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-orange" onclick="backToStep1()">続けて取込む</button>
|
<button class="btn btn-orange" onclick="backToStep1()">
|
||||||
|
続けて取込む
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -391,12 +592,12 @@
|
|||||||
<script>
|
<script>
|
||||||
// ── 初期化 ──────────────────────────────────────────────
|
// ── 初期化 ──────────────────────────────────────────────
|
||||||
let _previewRows = [];
|
let _previewRows = [];
|
||||||
let _bankName = '';
|
let _bankName = "";
|
||||||
let _allRows = [];
|
let _allRows = [];
|
||||||
let _currentPage = 1;
|
let _currentPage = 1;
|
||||||
const PAGE_SIZE = 100;
|
const PAGE_SIZE = 100;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
loadBanks();
|
loadBanks();
|
||||||
loadData();
|
loadData();
|
||||||
initDropZone();
|
initDropZone();
|
||||||
@@ -406,28 +607,29 @@
|
|||||||
function showMsg(id, text, type) {
|
function showMsg(id, text, type) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
el.textContent = text;
|
el.textContent = text;
|
||||||
el.className = 'msg-bar ' + type;
|
el.className = "msg-bar " + type;
|
||||||
el.style.display = 'block';
|
el.style.display = "block";
|
||||||
}
|
}
|
||||||
function hideMsg(id) {
|
function hideMsg(id) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
el.style.display = 'none';
|
el.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 銀行名リスト読み込み ────────────────────────────────
|
// ── 銀行名リスト読み込み ────────────────────────────────
|
||||||
async function loadBanks() {
|
async function loadBanks() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/bank-statements?limit=1');
|
const res = await fetch("/bank-statements?limit=1");
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const sel = document.getElementById('filterBank');
|
const sel = document.getElementById("filterBank");
|
||||||
const cur = sel.value;
|
const cur = sel.value;
|
||||||
// 既存オプションを保持しつつ追加
|
// 既存オプションを保持しつつ追加
|
||||||
const existing = Array.from(sel.options).map(o => o.value);
|
const existing = Array.from(sel.options).map((o) => o.value);
|
||||||
data.banks.forEach(b => {
|
data.banks.forEach((b) => {
|
||||||
if (!existing.includes(b)) {
|
if (!existing.includes(b)) {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement("option");
|
||||||
opt.value = b; opt.textContent = b;
|
opt.value = b;
|
||||||
|
opt.textContent = b;
|
||||||
sel.appendChild(opt);
|
sel.appendChild(opt);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -437,17 +639,17 @@
|
|||||||
|
|
||||||
// ── 明細データ読み込み ──────────────────────────────────
|
// ── 明細データ読み込み ──────────────────────────────────
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
const year = document.getElementById('filterYear').value;
|
const year = document.getElementById("filterYear").value;
|
||||||
const month = document.getElementById('filterMonth').value;
|
const month = document.getElementById("filterMonth").value;
|
||||||
const bank = document.getElementById('filterBank').value;
|
const bank = document.getElementById("filterBank").value;
|
||||||
|
|
||||||
hideMsg('msgBar');
|
hideMsg("msgBar");
|
||||||
|
|
||||||
const qp = [];
|
const qp = [];
|
||||||
if (year) qp.push(`year=${year}`);
|
if (year) qp.push(`year=${year}`);
|
||||||
if (month) qp.push(`month=${month}`);
|
if (month) qp.push(`month=${month}`);
|
||||||
if (bank) qp.push(`bank_name=${encodeURIComponent(bank)}`);
|
if (bank) qp.push(`bank_name=${encodeURIComponent(bank)}`);
|
||||||
const url = '/bank-statements' + (qp.length ? '?' + qp.join('&') : '');
|
const url = "/bank-statements" + (qp.length ? "?" + qp.join("&") : "");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
@@ -461,18 +663,19 @@
|
|||||||
renderTable();
|
renderTable();
|
||||||
updateBankFilter(data.banks);
|
updateBankFilter(data.banks);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showMsg('msgBar', 'データ取得エラー: ' + e.message, 'error');
|
showMsg("msgBar", "データ取得エラー: " + e.message, "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateBankFilter(banks) {
|
function updateBankFilter(banks) {
|
||||||
const sel = document.getElementById('filterBank');
|
const sel = document.getElementById("filterBank");
|
||||||
const cur = sel.value;
|
const cur = sel.value;
|
||||||
// 全銀行以外のオプションをクリアして再構築
|
// 全銀行以外のオプションをクリアして再構築
|
||||||
while (sel.options.length > 1) sel.remove(1);
|
while (sel.options.length > 1) sel.remove(1);
|
||||||
banks.forEach(b => {
|
banks.forEach((b) => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement("option");
|
||||||
opt.value = b; opt.textContent = b;
|
opt.value = b;
|
||||||
|
opt.textContent = b;
|
||||||
sel.appendChild(opt);
|
sel.appendChild(opt);
|
||||||
});
|
});
|
||||||
if (cur) sel.value = cur;
|
if (cur) sel.value = cur;
|
||||||
@@ -480,32 +683,34 @@
|
|||||||
|
|
||||||
// ── 月別チップ ──────────────────────────────────────────
|
// ── 月別チップ ──────────────────────────────────────────
|
||||||
function renderMonthlyChips(monthly, selYear, selMonth) {
|
function renderMonthlyChips(monthly, selYear, selMonth) {
|
||||||
const wrap = document.getElementById('monthlyChips');
|
const wrap = document.getElementById("monthlyChips");
|
||||||
wrap.innerHTML = '';
|
wrap.innerHTML = "";
|
||||||
if (!monthly.length) return;
|
if (!monthly.length) return;
|
||||||
|
|
||||||
// 「全月」チップ
|
// 「全月」チップ
|
||||||
const allChip = document.createElement('span');
|
const allChip = document.createElement("span");
|
||||||
allChip.className = 'month-chip no-print' + (!selMonth ? ' active' : '');
|
allChip.className =
|
||||||
allChip.textContent = '全月';
|
"month-chip no-print" + (!selMonth ? " active" : "");
|
||||||
|
allChip.textContent = "全月";
|
||||||
allChip.onclick = () => {
|
allChip.onclick = () => {
|
||||||
document.getElementById('filterMonth').value = '';
|
document.getElementById("filterMonth").value = "";
|
||||||
loadData();
|
loadData();
|
||||||
};
|
};
|
||||||
wrap.appendChild(allChip);
|
wrap.appendChild(allChip);
|
||||||
|
|
||||||
monthly.forEach(m => {
|
monthly.forEach((m) => {
|
||||||
const [y, mo] = m.ym.split('-');
|
const [y, mo] = m.ym.split("-");
|
||||||
const chip = document.createElement('span');
|
const chip = document.createElement("span");
|
||||||
const isActive = (String(selYear) === y && String(selMonth) === String(parseInt(mo)));
|
const isActive =
|
||||||
chip.className = 'month-chip no-print' + (isActive ? ' active' : '');
|
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">${
|
chip.innerHTML = `${parseInt(mo)}月 <small style="color:inherit;opacity:.8">${
|
||||||
(m.count)}件</small>`;
|
m.count
|
||||||
chip.title =
|
}件</small>`;
|
||||||
`出金 ${fmt(m.total_debit)} / 入金 ${fmt(m.total_credit)}`;
|
chip.title = `出金 ${fmt(m.total_debit)} / 入金 ${fmt(m.total_credit)}`;
|
||||||
chip.onclick = () => {
|
chip.onclick = () => {
|
||||||
document.getElementById('filterYear').value = y;
|
document.getElementById("filterYear").value = y;
|
||||||
document.getElementById('filterMonth').value = parseInt(mo);
|
document.getElementById("filterMonth").value = parseInt(mo);
|
||||||
loadData();
|
loadData();
|
||||||
};
|
};
|
||||||
wrap.appendChild(chip);
|
wrap.appendChild(chip);
|
||||||
@@ -514,8 +719,11 @@
|
|||||||
|
|
||||||
// ── 統計バー ────────────────────────────────────────────
|
// ── 統計バー ────────────────────────────────────────────
|
||||||
function renderStats() {
|
function renderStats() {
|
||||||
const bar = document.getElementById('statsBar');
|
const bar = document.getElementById("statsBar");
|
||||||
if (!_allRows.length) { bar.innerHTML = ''; return; }
|
if (!_allRows.length) {
|
||||||
|
bar.innerHTML = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
const totalDebit = _allRows.reduce((s, r) => s + (r.debit || 0), 0);
|
const totalDebit = _allRows.reduce((s, r) => s + (r.debit || 0), 0);
|
||||||
const totalCredit = _allRows.reduce((s, r) => s + (r.credit || 0), 0);
|
const totalCredit = _allRows.reduce((s, r) => s + (r.credit || 0), 0);
|
||||||
bar.innerHTML = `
|
bar.innerHTML = `
|
||||||
@@ -540,9 +748,10 @@
|
|||||||
|
|
||||||
// ── 明細テーブル ─────────────────────────────────────────
|
// ── 明細テーブル ─────────────────────────────────────────
|
||||||
function renderTable() {
|
function renderTable() {
|
||||||
const wrap = document.getElementById('tableWrap');
|
const wrap = document.getElementById("tableWrap");
|
||||||
if (!_allRows.length) {
|
if (!_allRows.length) {
|
||||||
wrap.innerHTML = '<div class="no-data">該当する明細データがありません</div>';
|
wrap.innerHTML =
|
||||||
|
'<div class="no-data">該当する明細データがありません</div>';
|
||||||
renderPagination(0);
|
renderPagination(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -563,33 +772,36 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
`;
|
`;
|
||||||
pageRows.forEach(r => {
|
pageRows.forEach((r) => {
|
||||||
html += `<tr>
|
html += `<tr>
|
||||||
<td class="text-center">${r.tx_date}</td>
|
<td class="text-center">${r.tx_date}</td>
|
||||||
<td class="text-left">${esc(r.description)}</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 debit-cell">${r.debit ? fmt(r.debit) : ""}</td>
|
||||||
<td class="text-right credit-cell">${r.credit ? fmt(r.credit) : ''}</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-right">${r.balance != null ? fmt(r.balance) : ""}</td>
|
||||||
<td class="text-left">${esc(r.memo || '')}</td>
|
<td class="text-left">${esc(r.memo || "")}</td>
|
||||||
<td class="text-center no-print">${esc(r.bank_name || '')}</td>
|
<td class="text-center no-print">${esc(r.bank_name || "")}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
});
|
});
|
||||||
html += '</tbody></table>';
|
html += "</tbody></table>";
|
||||||
wrap.innerHTML = html;
|
wrap.innerHTML = html;
|
||||||
renderPagination(_allRows.length);
|
renderPagination(_allRows.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPagination(total) {
|
function renderPagination(total) {
|
||||||
const bar = document.getElementById('paginationBar');
|
const bar = document.getElementById("paginationBar");
|
||||||
if (!bar) return;
|
if (!bar) return;
|
||||||
const totalPages = Math.ceil(total / PAGE_SIZE);
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
||||||
if (totalPages <= 1) { bar.innerHTML = ''; return; }
|
if (totalPages <= 1) {
|
||||||
|
bar.innerHTML = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
const start = (_currentPage - 1) * PAGE_SIZE + 1;
|
const start = (_currentPage - 1) * PAGE_SIZE + 1;
|
||||||
const end = Math.min(_currentPage * PAGE_SIZE, total);
|
const end = Math.min(_currentPage * PAGE_SIZE, total);
|
||||||
bar.innerHTML = `
|
bar.innerHTML = `
|
||||||
<button class="btn btn-page" onclick="changePage(${_currentPage - 1})" ${_currentPage <= 1 ? 'disabled' : ''}>◀ 前へ</button>
|
<button class="btn btn-page" onclick="changePage(${_currentPage - 1})" ${_currentPage <= 1 ? "disabled" : ""}>◀ 前へ</button>
|
||||||
<span class="page-info">${_currentPage} / ${totalPages} ページ(${start}〜${end}件 / 全${total}件)</span>
|
<span class="page-info">${_currentPage} / ${totalPages} ページ(${start}〜${end}件 / 全${total}件)</span>
|
||||||
<button class="btn btn-page" onclick="changePage(${_currentPage + 1})" ${_currentPage >= totalPages ? 'disabled' : ''}>次へ ▶</button>
|
<button class="btn btn-page" onclick="changePage(${_currentPage + 1})" ${_currentPage >= totalPages ? "disabled" : ""}>次へ ▶</button>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,32 +816,34 @@
|
|||||||
// ── CSV取込モーダル ──────────────────────────────────────
|
// ── CSV取込モーダル ──────────────────────────────────────
|
||||||
function openImportModal() {
|
function openImportModal() {
|
||||||
backToStep1();
|
backToStep1();
|
||||||
document.getElementById('importModal').classList.add('active');
|
document.getElementById("importModal").classList.add("active");
|
||||||
}
|
}
|
||||||
function closeImportModal() {
|
function closeImportModal() {
|
||||||
document.getElementById('importModal').classList.remove('active');
|
document.getElementById("importModal").classList.remove("active");
|
||||||
}
|
}
|
||||||
function backToStep1() {
|
function backToStep1() {
|
||||||
document.getElementById('step1').style.display = '';
|
document.getElementById("step1").style.display = "";
|
||||||
document.getElementById('step2').style.display = 'none';
|
document.getElementById("step2").style.display = "none";
|
||||||
document.getElementById('step3').style.display = 'none';
|
document.getElementById("step3").style.display = "none";
|
||||||
document.getElementById('csvFile').value = '';
|
document.getElementById("csvFile").value = "";
|
||||||
hideMsg('step1msg');
|
hideMsg("step1msg");
|
||||||
hideMsg('step2msg');
|
hideMsg("step2msg");
|
||||||
_previewRows = [];
|
_previewRows = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ドラッグ&ドロップ初期化
|
// ドラッグ&ドロップ初期化
|
||||||
function initDropZone() {
|
function initDropZone() {
|
||||||
const zone = document.getElementById('dropZone');
|
const zone = document.getElementById("dropZone");
|
||||||
zone.addEventListener('dragover', e => {
|
zone.addEventListener("dragover", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
zone.classList.add('dragover');
|
zone.classList.add("dragover");
|
||||||
});
|
});
|
||||||
zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
|
zone.addEventListener("dragleave", () =>
|
||||||
zone.addEventListener('drop', e => {
|
zone.classList.remove("dragover"),
|
||||||
|
);
|
||||||
|
zone.addEventListener("drop", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
zone.classList.remove('dragover');
|
zone.classList.remove("dragover");
|
||||||
const f = e.dataTransfer.files[0];
|
const f = e.dataTransfer.files[0];
|
||||||
if (f) uploadAndPreview(f);
|
if (f) uploadAndPreview(f);
|
||||||
});
|
});
|
||||||
@@ -641,111 +855,121 @@
|
|||||||
|
|
||||||
async function uploadAndPreview(file) {
|
async function uploadAndPreview(file) {
|
||||||
if (!file.name.match(/\.(csv|txt)$/i)) {
|
if (!file.name.match(/\.(csv|txt)$/i)) {
|
||||||
showMsg('step1msg', 'CSVファイルを選択してください(.csv または .txt)', 'error');
|
showMsg(
|
||||||
|
"step1msg",
|
||||||
|
"CSVファイルを選択してください(.csv または .txt)",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showMsg('step1msg', '解析中...', 'info');
|
showMsg("step1msg", "解析中...", "info");
|
||||||
|
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
fd.append('file', file);
|
fd.append("file", file);
|
||||||
fd.append('bank_name', document.getElementById('bankNameInput').value);
|
fd.append("bank_name", document.getElementById("bankNameInput").value);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/bank-statements/preview', { method: 'POST', body: fd });
|
const res = await fetch("/bank-statements/preview", {
|
||||||
|
method: "POST",
|
||||||
|
body: fd,
|
||||||
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
showMsg('step1msg', data.detail || 'エラーが発生しました', 'error');
|
showMsg("step1msg", data.detail || "エラーが発生しました", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hideMsg('step1msg');
|
hideMsg("step1msg");
|
||||||
_previewRows = data.rows;
|
_previewRows = data.rows;
|
||||||
// 銀行名: 入力欄が空でAPIがヒントを返した場合は自動セット
|
// 銀行名: 入力欄が空でAPIがヒントを返した場合は自動セット
|
||||||
if (data.bank_name && !document.getElementById('bankNameInput').value.trim()) {
|
if (
|
||||||
document.getElementById('bankNameInput').value = data.bank_name;
|
data.bank_name &&
|
||||||
|
!document.getElementById("bankNameInput").value.trim()
|
||||||
|
) {
|
||||||
|
document.getElementById("bankNameInput").value = data.bank_name;
|
||||||
}
|
}
|
||||||
_bankName = document.getElementById('bankNameInput').value;
|
_bankName = document.getElementById("bankNameInput").value;
|
||||||
renderPreview(data);
|
renderPreview(data);
|
||||||
document.getElementById('step1').style.display = 'none';
|
document.getElementById("step1").style.display = "none";
|
||||||
document.getElementById('step2').style.display = '';
|
document.getElementById("step2").style.display = "";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showMsg('step1msg', 'アップロードエラー: ' + e.message, 'error');
|
showMsg("step1msg", "アップロードエラー: " + e.message, "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPreview(data) {
|
function renderPreview(data) {
|
||||||
// サマリー
|
// サマリー
|
||||||
document.getElementById('previewSummary').innerHTML = `
|
document.getElementById("previewSummary").innerHTML = `
|
||||||
<div class="ps-item ps-total">合計: ${data.total} 件</div>
|
<div class="ps-item ps-total">合計: ${data.total} 件</div>
|
||||||
<div class="ps-item ps-new">🆕 新規: ${data.new} 件(取込対象)</div>
|
<div class="ps-item ps-new">🆕 新規: ${data.new} 件(取込対象)</div>
|
||||||
<div class="ps-item ps-dup">⚠️ 重複: ${data.duplicates} 件(スキップ)</div>
|
<div class="ps-item ps-dup">⚠️ 重複: ${data.duplicates} 件(スキップ)</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// 取込ボタンの状態
|
// 取込ボタンの状態
|
||||||
const btn = document.getElementById('importConfirmBtn');
|
const btn = document.getElementById("importConfirmBtn");
|
||||||
if (data.new === 0) {
|
if (data.new === 0) {
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = '新規データなし(取込不要)';
|
btn.textContent = "新規データなし(取込不要)";
|
||||||
btn.style.opacity = '0.5';
|
btn.style.opacity = "0.5";
|
||||||
} else {
|
} else {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = `新規 ${data.new} 件を取込む`;
|
btn.textContent = `新規 ${data.new} 件を取込む`;
|
||||||
btn.style.opacity = '1';
|
btn.style.opacity = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
// プレビューテーブル
|
// プレビューテーブル
|
||||||
const tbody = document.getElementById('previewBody');
|
const tbody = document.getElementById("previewBody");
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = "";
|
||||||
data.rows.forEach(r => {
|
data.rows.forEach((r) => {
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement("tr");
|
||||||
tr.className = r.status === 'new' ? 'row-new' : 'row-dup';
|
tr.className = r.status === "new" ? "row-new" : "row-dup";
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<span class="badge badge-${r.status === 'new' ? 'new' : 'dup'}">
|
<span class="badge badge-${r.status === "new" ? "new" : "dup"}">
|
||||||
${r.status === 'new' ? '新規' : '重複'}
|
${r.status === "new" ? "新規" : "重複"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>${r.tx_date}</td>
|
<td>${r.tx_date}</td>
|
||||||
<td>${esc(r.description)}</td>
|
<td>${esc(r.description)}</td>
|
||||||
<td class="text-right">${r.debit ? fmt(r.debit) : ''}</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.credit ? fmt(r.credit) : ""}</td>
|
||||||
<td class="text-right">${r.balance != null ? fmt(r.balance) : ''}</td>
|
<td class="text-right">${r.balance != null ? fmt(r.balance) : ""}</td>
|
||||||
<td>${esc(r.memo || '')}</td>
|
<td>${esc(r.memo || "")}</td>
|
||||||
`;
|
`;
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmImport() {
|
async function confirmImport() {
|
||||||
const newRows = _previewRows.filter(r => r.status === 'new');
|
const newRows = _previewRows.filter((r) => r.status === "new");
|
||||||
if (!newRows.length) return;
|
if (!newRows.length) return;
|
||||||
|
|
||||||
const btn = document.getElementById('importConfirmBtn');
|
const btn = document.getElementById("importConfirmBtn");
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = '取込中...';
|
btn.textContent = "取込中...";
|
||||||
hideMsg('step2msg');
|
hideMsg("step2msg");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/bank-statements/import', {
|
const res = await fetch("/bank-statements/import", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ bank_name: _bankName, rows: newRows }),
|
body: JSON.stringify({ bank_name: _bankName, rows: newRows }),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
showMsg('step2msg', data.detail || '取込エラー', 'error');
|
showMsg("step2msg", data.detail || "取込エラー", "error");
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = `新規 ${newRows.length} 件を取込む`;
|
btn.textContent = `新規 ${newRows.length} 件を取込む`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 完了画面
|
// 完了画面
|
||||||
document.getElementById('step2').style.display = 'none';
|
document.getElementById("step2").style.display = "none";
|
||||||
document.getElementById('step3').style.display = '';
|
document.getElementById("step3").style.display = "";
|
||||||
document.getElementById('step3msg').textContent =
|
document.getElementById("step3msg").textContent =
|
||||||
`✅ 取込完了:${data.inserted} 件を登録しました` +
|
`✅ 取込完了:${data.inserted} 件を登録しました` +
|
||||||
(data.skipped ? `(${data.skipped} 件スキップ)` : '');
|
(data.skipped ? `(${data.skipped} 件スキップ)` : "");
|
||||||
loadBanks();
|
loadBanks();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showMsg('step2msg', '通信エラー: ' + e.message, 'error');
|
showMsg("step2msg", "通信エラー: " + e.message, "error");
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = `新規 ${newRows.length} 件を取込む`;
|
btn.textContent = `新規 ${newRows.length} 件を取込む`;
|
||||||
}
|
}
|
||||||
@@ -753,15 +977,15 @@
|
|||||||
|
|
||||||
// ── ユーティリティ ──────────────────────────────────────
|
// ── ユーティリティ ──────────────────────────────────────
|
||||||
function fmt(v) {
|
function fmt(v) {
|
||||||
if (v == null || v === '') return '';
|
if (v == null || v === "") return "";
|
||||||
return Number(v).toLocaleString('ja-JP');
|
return Number(v).toLocaleString("ja-JP");
|
||||||
}
|
}
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
return String(s)
|
return String(s)
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, "&")
|
||||||
.replace(/</g, '<')
|
.replace(/</g, "<")
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, ">")
|
||||||
.replace(/"/g, '"');
|
.replace(/"/g, """);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -284,7 +284,20 @@
|
|||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>🏛 e-Gov 電子送達</h1>
|
<h1>🏛 e-Gov 電子送達</h1>
|
||||||
<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>
|
<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>
|
</header>
|
||||||
|
|
||||||
<!-- ログインカード -->
|
<!-- ログインカード -->
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<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="Cache-Control"
|
||||||
|
content="no-cache, no-store, must-revalidate"
|
||||||
|
/>
|
||||||
<meta http-equiv="Pragma" content="no-cache" />
|
<meta http-equiv="Pragma" content="no-cache" />
|
||||||
<title>NJTS 会計システム - メイン</title>
|
<title>NJTS 会計システム - メイン</title>
|
||||||
<link rel="stylesheet" href="css/style.css" />
|
<link rel="stylesheet" href="css/style.css" />
|
||||||
@@ -92,7 +95,9 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.change-pw-btn:hover { background: #545b62; }
|
.change-pw-btn:hover {
|
||||||
|
background: #545b62;
|
||||||
|
}
|
||||||
/* パスワード変更モーダル */
|
/* パスワード変更モーダル */
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -103,7 +108,9 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.modal-overlay.active { display: flex; }
|
.modal-overlay.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.modal-box {
|
.modal-box {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@@ -111,22 +118,71 @@
|
|||||||
width: 360px;
|
width: 360px;
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
.modal-box h3 { margin: 0 0 20px; }
|
.modal-box h3 {
|
||||||
.modal-box .form-group { margin-bottom: 14px; }
|
margin: 0 0 20px;
|
||||||
.modal-box label { display: block; margin-bottom: 5px; font-size: 13px; font-weight: 600; color: #333; }
|
}
|
||||||
.modal-box input[type="password"] {
|
.modal-box .form-group {
|
||||||
width: 100%; padding: 10px; border: 1px solid #ddd;
|
margin-bottom: 14px;
|
||||||
border-radius: 5px; font-size: 14px; box-sizing: border-box;
|
}
|
||||||
|
.modal-box label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.modal-box input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.modal-box .btn-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.modal-box .btn-row button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.modal-box .btn-submit {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.modal-box .btn-submit:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
.modal-box .btn-cancel {
|
||||||
|
background: #e9ecef;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.modal-box .btn-cancel:hover {
|
||||||
|
background: #d0d5db;
|
||||||
|
}
|
||||||
|
.modal-msg {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: none;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.modal-msg.error {
|
||||||
|
background: #fee;
|
||||||
|
color: #c33;
|
||||||
|
border: 1px solid #fcc;
|
||||||
|
}
|
||||||
|
.modal-msg.success {
|
||||||
|
background: #efe;
|
||||||
|
color: #3a3;
|
||||||
|
border: 1px solid #cfc;
|
||||||
}
|
}
|
||||||
.modal-box .btn-row { display: flex; gap: 10px; margin-top: 20px; }
|
|
||||||
.modal-box .btn-row button { flex: 1; padding: 10px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; }
|
|
||||||
.modal-box .btn-submit { background: #007bff; color: white; }
|
|
||||||
.modal-box .btn-submit:hover { background: #0056b3; }
|
|
||||||
.modal-box .btn-cancel { background: #e9ecef; color: #333; }
|
|
||||||
.modal-box .btn-cancel:hover { background: #d0d5db; }
|
|
||||||
.modal-msg { padding: 8px 12px; border-radius: 4px; margin-bottom: 12px; display: none; font-size: 13px; }
|
|
||||||
.modal-msg.error { background: #fee; color: #c33; border: 1px solid #fcc; }
|
|
||||||
.modal-msg.success { background: #efe; color: #3a3; border: 1px solid #cfc; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -134,7 +190,9 @@
|
|||||||
<h1>NJTS</h1>
|
<h1>NJTS</h1>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<span>ユーザー: <strong id="username">読み込み中...</strong></span>
|
<span>ユーザー: <strong id="username">読み込み中...</strong></span>
|
||||||
<button class="change-pw-btn" onclick="openChangePw()">パスワード変更</button>
|
<button class="change-pw-btn" onclick="openChangePw()">
|
||||||
|
パスワード変更
|
||||||
|
</button>
|
||||||
<button class="logout-btn" onclick="logout()">ログアウト</button>
|
<button class="logout-btn" onclick="logout()">ログアウト</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -146,7 +204,11 @@
|
|||||||
<div class="modal-msg" id="changePwMsg"></div>
|
<div class="modal-msg" id="changePwMsg"></div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>現在のパスワード</label>
|
<label>現在のパスワード</label>
|
||||||
<input type="password" id="cpCurrent" placeholder="現在のパスワード" />
|
<input
|
||||||
|
type="password"
|
||||||
|
id="cpCurrent"
|
||||||
|
placeholder="現在のパスワード"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>新しいパスワード(8文字以上)</label>
|
<label>新しいパスワード(8文字以上)</label>
|
||||||
@@ -157,8 +219,12 @@
|
|||||||
<input type="password" id="cpConfirm" placeholder="もう一度入力" />
|
<input type="password" id="cpConfirm" placeholder="もう一度入力" />
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-submit" onclick="submitChangePw()">変更する</button>
|
<button class="btn-submit" onclick="submitChangePw()">
|
||||||
<button class="btn-cancel" onclick="closeChangePw()">キャンセル</button>
|
変更する
|
||||||
|
</button>
|
||||||
|
<button class="btn-cancel" onclick="closeChangePw()">
|
||||||
|
キャンセル
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,7 +264,9 @@
|
|||||||
銀行のCSVデータを取込み、明細を管理します。重複チェック付きで安全に取込め、月別・銀行別の検索・印刷が可能です。
|
銀行のCSVデータを取込み、明細を管理します。重複チェック付きで安全に取込め、月別・銀行別の検索・印刷が可能です。
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="btn" href="bank-statement.html" style="background: #1565c0">銀行明細を開く</a>
|
<a class="btn" href="bank-statement.html" style="background: #1565c0"
|
||||||
|
>銀行明細を開く</a
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -249,24 +317,36 @@
|
|||||||
const confirm = document.getElementById("cpConfirm").value;
|
const confirm = document.getElementById("cpConfirm").value;
|
||||||
const user = getCurrentUser();
|
const user = getCurrentUser();
|
||||||
if (!current || !newPw || !confirm) {
|
if (!current || !newPw || !confirm) {
|
||||||
showChangePwMsg("すべての項目を入力してください", true); return;
|
showChangePwMsg("すべての項目を入力してください", true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (newPw.length < 8) {
|
if (newPw.length < 8) {
|
||||||
showChangePwMsg("新しいパスワードは8文字以上にしてください", true); return;
|
showChangePwMsg("新しいパスワードは8文字以上にしてください", true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (newPw !== confirm) {
|
if (newPw !== confirm) {
|
||||||
showChangePwMsg("新しいパスワードが一致しません", true); return;
|
showChangePwMsg("新しいパスワードが一致しません", true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/users/change-password", {
|
const res = await fetch("/api/users/change-password", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ username: user.username, current_password: current, new_password: newPw })
|
body: JSON.stringify({
|
||||||
|
username: user.username,
|
||||||
|
current_password: current,
|
||||||
|
new_password: newPw,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showChangePwMsg("パスワードを変更しました。再ログインしてください", false);
|
showChangePwMsg(
|
||||||
setTimeout(() => { logout(); }, 2000);
|
"パスワードを変更しました。再ログインしてください",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
logout();
|
||||||
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
showChangePwMsg(data.detail || "変更に失敗しました", true);
|
showChangePwMsg(data.detail || "変更に失敗しました", true);
|
||||||
}
|
}
|
||||||
@@ -274,7 +354,6 @@
|
|||||||
showChangePwMsg("エラー: " + e.message, true);
|
showChangePwMsg("エラー: " + e.message, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -257,7 +257,20 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<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>
|
<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>
|
<h2>仕訳入力</h2>
|
||||||
|
|
||||||
<div class="row" style="margin-bottom: 12px">
|
<div class="row" style="margin-bottom: 12px">
|
||||||
|
|||||||
@@ -56,7 +56,20 @@
|
|||||||
>
|
>
|
||||||
保存
|
保存
|
||||||
</button>
|
</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>
|
<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/api.js"></script>
|
||||||
<script src="js/opening_balance.js"></script>
|
<script src="js/opening_balance.js"></script>
|
||||||
|
|||||||
@@ -56,7 +56,19 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<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>
|
<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>
|
<h1>給与管理システム</h1>
|
||||||
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>
|
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>
|
||||||
|
|||||||
@@ -267,7 +267,20 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<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>
|
<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>
|
<h2>残高試算表(貸借・損益)</h2>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user