月次レポート機能を実装し、経費の取り消し・復帰機能を追加。CSV取込画面にドラッグ&ドロップエリアを実装し、エラーメッセージ表示を追加。金額のカンマ区切り表示を全体に適用。faviconとapple-touch-iconを追加し、404エラーを回避。作業ログをdiary.mdに追記。
This commit is contained in:
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user