Files
Card-data-sorting/templates/expenses/expense_list.html

190 lines
6.3 KiB
HTML

{% 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>
<tr>
<th>利用日</th>
<th>利用先</th>
<th>金額</th>
<th>取消</th>
<th>店舗区分</th>
<th>経費区分</th>
<th>区分</th>
<th>備考</th>
</tr>
</thead>
<tbody>
{% for expense in expenses %}
<tr data-expense-id="{{ expense.id }}" class="{% if expense.is_canceled %}is-canceled{% endif %}">
<td>{{ expense.use_date }}</td>
<td>{{ expense.description }}</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>
{% for store in stores %}
<option value="{{ store.id }}" {% if expense.store_id == store.id %}selected{% endif %}>
{{ store.name }}
</option>
{% endfor %}
</select>
</td>
<td>
<select class="js-expense-field" data-field="expense_category_id">
<option value="">未設定</option>
{% for category in categories %}
<option value="{{ category.id }}" {% if expense.expense_category_id == category.id %}selected{% endif %}>
{{ category.name }}
</option>
{% endfor %}
</select>
</td>
<td>
<select class="js-expense-field" data-field="owner_type">
<option value="pending" {% if expense.owner_type == "pending" %}selected{% endif %}>
未設定
</option>
<option value="company" {% if expense.owner_type == "company" %}selected{% endif %}>
会社
</option>
<option value="personal" {% if expense.owner_type == "personal" %}selected{% endif %}>
家計
</option>
</select>
</td>
<td>
<input
class="js-expense-field"
data-field="note"
type="text"
value="{{ expense.note }}"
>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8">データがありません。</td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value;
async function saveExpense(expenseId, payload) {
const response = await fetch(`/expenses/${expenseId}/update/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error('保存に失敗しました');
}
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;
}
document.querySelectorAll('.js-expense-field').forEach((element) => {
const eventName = element.tagName === 'INPUT' ? 'change' : 'change';
element.addEventListener(eventName, async (event) => {
const row = event.target.closest('tr');
if (!row) {
return;
}
const expenseId = row.dataset.expenseId;
const field = event.target.dataset.field;
const value = coerceValue(field, event.target.value);
try {
await saveExpense(expenseId, { [field]: value });
} catch (error) {
alert(error.message);
}
});
});
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 %}