This commit is contained in:
admin
2026-03-03 00:01:43 +09:00
parent 3b028b8fc0
commit 068903b933
31 changed files with 3204 additions and 889 deletions

View File

@@ -44,6 +44,7 @@
.account-search-container {
position: relative;
width: 100%;
box-sizing: border-box;
}
.account-search-input {
@@ -52,6 +53,7 @@
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.account-search-input:focus {
@@ -84,6 +86,9 @@
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
font-size: 14px;
text-align: left;
display: block;
width: 100%;
}
.account-option:hover,
@@ -94,11 +99,14 @@
.account-option-strong {
font-weight: bold;
color: #333;
display: inline;
margin-right: 6px;
}
.account-option-light {
color: #999;
font-size: 12px;
color: #555;
font-size: 14px;
display: inline;
}
</style>
</head>
@@ -241,43 +249,81 @@
const container = inputElement.closest(".account-search-container");
const dropdown = container.querySelector(".account-dropdown");
inputElement.addEventListener("input", (e) => {
const searchText = e.target.value.toLowerCase().trim();
// 科目選択履歴を保存journal-entry.htmlと共通のlocalStorageキー
function saveAccountToRecentEdit(accountId) {
if (!accountId) return;
let recent = JSON.parse(localStorage.getItem("recentAccounts") || "[]");
recent = recent.filter((id) => id !== accountId);
recent.unshift(accountId);
if (recent.length > 20) recent = recent.slice(0, 20);
localStorage.setItem("recentAccounts", JSON.stringify(recent));
}
if (!searchText) {
dropdown.classList.remove("active");
return;
}
function makeLbl(text) {
const lbl = document.createElement("div");
lbl.style.cssText = "padding:3px 12px;font-size:11px;color:#888;background:#f5f5f5;font-weight:bold;border-bottom:1px solid #e0e0e0;";
lbl.textContent = text;
return lbl;
}
// 过滤账户
const filtered = accounts.filter((acc) => {
const code = acc.account_code.toLowerCase();
const name = acc.account_name.toLowerCase();
return code.includes(searchText) || name.includes(searchText);
});
// 生成下拉建议
dropdown.innerHTML = "";
if (filtered.length === 0) {
dropdown.innerHTML = `<div style="padding: 8px 12px; color: #999;">該当する科目がありません</div>`;
} else {
filtered.slice(0, 20).forEach((acc) => {
const option = document.createElement("div");
option.className = "account-option";
option.innerHTML = `
<span class="account-option-strong">${acc.account_code}</span>
<span class="account-option-light">${acc.account_name}</span>
`;
option.addEventListener("click", () => {
inputElement.value = `${acc.account_code} ${acc.account_name}`;
inputElement.dataset.accountId = acc.account_id;
dropdown.classList.remove("active");
});
dropdown.appendChild(option);
// 显示科目下拉列表的函数
function showAccountList(searchText = "") {
const isFiltered = searchText.length > 0;
let filtered = accounts;
if (isFiltered) {
filtered = accounts.filter((acc) => {
const code = acc.account_code.toLowerCase();
const name = acc.account_name.toLowerCase();
return code.includes(searchText) || name.includes(searchText);
});
}
dropdown.innerHTML = "";
function appendOpt(acc) {
const option = document.createElement("div");
option.className = "account-option";
option.innerHTML = `<span class="account-option-strong">${acc.account_code}</span><span class="account-option-light">${acc.account_name}</span>`;
option.addEventListener("click", () => {
inputElement.value = `${acc.account_code} ${acc.account_name}`;
inputElement.dataset.accountId = acc.account_id;
dropdown.classList.remove("active");
saveAccountToRecentEdit(acc.account_id);
});
dropdown.appendChild(option);
}
if (filtered.length === 0) {
dropdown.innerHTML = `<div style="padding: 8px 12px; color: #999;">該当する科目がありません</div>`;
} else if (isFiltered) {
const sorted = filtered.slice().sort((a, b) => a.account_code.localeCompare(b.account_code));
sorted.slice(0, 50).forEach(appendOpt);
} else {
const recentIds = JSON.parse(localStorage.getItem("recentAccounts") || "[]");
const recentItems = recentIds.map((id) => filtered.find((a) => a.account_id === id)).filter(Boolean);
const recentSet = new Set(recentIds);
const otherItems = filtered
.filter((a) => !recentSet.has(a.account_id))
.sort((a, b) => a.account_code.localeCompare(b.account_code));
if (recentItems.length > 0) {
dropdown.appendChild(makeLbl("⭐ 最近使った科目"));
recentItems.forEach(appendOpt);
const sep = document.createElement("div");
sep.style.cssText = "border-top:1px solid #e8e8e8;margin:2px 0;";
dropdown.appendChild(sep);
}
dropdown.appendChild(makeLbl("📋 すべての科目"));
otherItems.slice(0, 50).forEach(appendOpt);
}
dropdown.classList.add("active");
}
// 入力時:フィルター
inputElement.addEventListener("input", (e) => {
const searchText = e.target.value.toLowerCase().trim();
showAccountList(searchText);
});
// クリック以外の場所のドロップダウンを非表示
@@ -287,11 +333,9 @@
}
});
// フォーカス時にドロップダウンを表示(既存値がある場合)
// フォーカス時:すべての科目を表示
inputElement.addEventListener("focus", () => {
if (inputElement.value) {
dropdown.classList.add("active");
}
showAccountList(inputElement.value.toLowerCase().trim());
});
}
@@ -386,13 +430,20 @@
if (line) {
// Find the account and populate the input
const account = accounts.find(a => a.account_id === line.account_id);
const account = accounts.find(
(a) => a.account_id === line.account_id,
);
if (account) {
accountInput.value = `${account.account_code} ${account.account_name}`;
accountInput.dataset.accountId = account.account_id;
}
tr.querySelectorAll("input")[1].value = line.debit || "";
tr.querySelectorAll("input")[2].value = line.credit || "";
// type="number" のみを取得
const numberInputs = tr.querySelectorAll("input[type='number']");
// Decimal 値を数値に変換してから代入
const debitVal = Number(line.debit) || 0;
const creditVal = Number(line.credit) || 0;
numberInputs[0].value = debitVal > 0 ? String(debitVal) : "";
numberInputs[1].value = creditVal > 0 ? String(creditVal) : "";
}
// 入力時に合計を自動更新
@@ -414,8 +465,26 @@
let d = 0,
c = 0;
document.querySelectorAll("#linesTable tbody tr").forEach((tr) => {
d += Number(tr.querySelectorAll("input")[0].value || 0);
c += Number(tr.querySelectorAll("input")[1].value || 0);
// type="number" のみを取得(科目検索入力框を除外)
const numberInputs = tr.querySelectorAll("input[type='number']");
if (numberInputs.length >= 2) {
const debitStr = numberInputs[0]?.value;
const creditStr = numberInputs[1]?.value;
// 空または 0 を含む場合は 0、そうでなければ数値に変換
const debitVal =
debitStr && debitStr.trim()
? Number(debitStr.toString().replace(/,/g, ""))
: 0;
const creditVal =
creditStr && creditStr.trim()
? Number(creditStr.toString().replace(/,/g, ""))
: 0;
// NaN チェック
if (!isNaN(debitVal)) d += debitVal;
if (!isNaN(creditVal)) c += creditVal;
}
});
document.getElementById("debitTotal").textContent = d.toLocaleString();
document.getElementById("creditTotal").textContent = c.toLocaleString();
@@ -429,13 +498,28 @@
result.textContent = "";
result.className = "";
const entryDate = document.getElementById("entryDate").value;
const entryDateInput = document.getElementById("entryDate");
const entryDate = entryDateInput.value;
const desc = document.getElementById("description").value;
if (!entryDate || !desc) {
if (entryDateInput.validity.badInput) {
showError(
"日付が正しくありません。存在しない日付が入力されています11月31日は無効です",
);
return;
}
if (!entryDate && !desc) {
showError("日付と摘要は必須です");
return;
}
if (!entryDate) {
showError("日付を入力してください");
return;
}
if (!desc) {
showError("摘要を入力してください");
return;
}
const lines = [];
let d = 0,
@@ -444,8 +528,9 @@
document.querySelectorAll("#linesTable tbody tr").forEach((tr) => {
const accountInput = tr.querySelector(".account-search-input");
const accountId = Number(accountInput.dataset.accountId || 0);
const debit = Number(tr.querySelectorAll("input")[1].value || 0);
const credit = Number(tr.querySelectorAll("input")[2].value || 0);
const numberInputs = tr.querySelectorAll("input[type='number']");
const debit = Number(numberInputs[0]?.value || 0);
const credit = Number(numberInputs[1]?.value || 0);
if (debit === 0 && credit === 0) return;
lines.push({ account_id: accountId, debit, credit });
d += debit;