月次レポート機能を実装し、経費の取り消し・復帰機能を追加。CSV取込画面にドラッグ&ドロップエリアを実装し、エラーメッセージ表示を追加。金額のカンマ区切り表示を全体に適用。faviconとapple-touch-iconを追加し、404エラーを回避。作業ログをdiary.mdに追記。

This commit is contained in:
president
2025-12-21 16:36:39 +09:00
parent d301ddcbfb
commit 7ae367cd66
17 changed files with 682 additions and 21 deletions

View File

@@ -1,15 +1,26 @@
{% extends 'base.html' %}
{% load humanize %}
{% block title %}明細編集{% endblock %}
{% block content %}
<section>
<h2>明細編集</h2>
<style>
.is-canceled {
text-decoration: line-through;
color: #666;
}
</style>
{% if imported %}
<p>取込件数: {{ imported }} / 重複件数: {{ duplicated }} / 文字コード: {{ encoding }}</p>
{% endif %}
<form>
{% csrf_token %}
<label>
<input type="checkbox" id="js-include-canceled" {% if include_canceled %}checked{% endif %}>
取り消し済みを表示
</label>
</form>
<table>
<thead>
@@ -17,6 +28,7 @@
<th>利用日</th>
<th>利用先</th>
<th>金額</th>
<th>取消</th>
<th>店舗区分</th>
<th>経費区分</th>
<th>区分</th>
@@ -25,10 +37,15 @@
</thead>
<tbody>
{% for expense in expenses %}
<tr data-expense-id="{{ expense.id }}">
<tr data-expense-id="{{ expense.id }}" class="{% if expense.is_canceled %}is-canceled{% endif %}">
<td>{{ expense.use_date }}</td>
<td>{{ expense.description }}</td>
<td>{{ expense.amount }}</td>
<td class="amount">{{ expense.amount|intcomma }}</td>
<td>
<button type="button" class="js-expense-cancel">
{% if expense.is_canceled %}復帰{% else %}取消{% endif %}
</button>
</td>
<td>
<select class="js-expense-field" data-field="store_id">
<option value="">未設定</option>
@@ -73,7 +90,7 @@
</tr>
{% empty %}
<tr>
<td colspan="7">データがありません。</td>
<td colspan="8">データがありません。</td>
</tr>
{% endfor %}
</tbody>
@@ -96,6 +113,21 @@
return response.json();
}
async function setCanceled(expenseId, isCanceled) {
const response = await fetch(`/expenses/${expenseId}/cancel/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify({ is_canceled: isCanceled }),
});
if (!response.ok) {
throw new Error('取消更新に失敗しました');
}
return response.json();
}
function coerceValue(field, value) {
return value;
}
@@ -117,6 +149,41 @@
}
});
});
document.querySelectorAll('.js-expense-cancel').forEach((button) => {
button.addEventListener('click', async (event) => {
const row = event.target.closest('tr');
if (!row) {
return;
}
const expenseId = row.dataset.expenseId;
const isCanceled = event.target.textContent.trim() === '取消';
try {
await setCanceled(expenseId, isCanceled);
if (isCanceled && !document.querySelector('#js-include-canceled')?.checked) {
row.remove();
return;
}
row.classList.toggle('is-canceled', isCanceled);
event.target.textContent = isCanceled ? '復帰' : '取消';
} catch (error) {
alert(error.message);
}
});
});
const includeToggle = document.querySelector('#js-include-canceled');
if (includeToggle) {
includeToggle.addEventListener('change', (event) => {
const url = new URL(window.location.href);
if (event.target.checked) {
url.searchParams.set('include_canceled', '1');
} else {
url.searchParams.delete('include_canceled');
}
window.location.href = url.toString();
});
}
</script>
</section>
{% endblock %}