Files
CRM_Montzar/apps/web/src/views/ChequesView.vue
T

153 lines
5.2 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { RouterLink } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import { formatMoney } from '@/lib/money';
import { formatJalaliNumeric } from '@/lib/jalali';
import UiButton from '@/components/ui/UiButton.vue';
import type { ChequeDto, ChequeStatus } from '@mento/shared';
const loading = ref(false);
const error = ref('');
const items = ref<ChequeDto[]>([]);
const status = ref('');
const q = ref('');
const savingId = ref('');
const statusLabel: Record<ChequeStatus, string> = {
pending: 'در جریان',
cleared: 'وصول شد',
bounced: 'برگشت',
};
const filtered = computed(() => items.value);
async function load() {
loading.value = true;
error.value = '';
try {
const { data } = await api.get<ChequeDto[]>('/cheques', {
params: { status: status.value || undefined, q: q.value || undefined },
});
items.value = data;
} catch (e) {
error.value = axios.isAxiosError(e)
? 'بارگذاری چک‌ها ناموفق بود.'
: 'بارگذاری چک‌ها ناموفق بود.';
} finally {
loading.value = false;
}
}
async function setStatus(row: ChequeDto, next: ChequeStatus) {
if (row.status === next) return;
savingId.value = row.id;
try {
const { data } = await api.patch<ChequeDto>(`/cheques/${row.id}`, {
status: next,
});
items.value = items.value.map((item) => (item.id === row.id ? data : item));
} catch {
error.value = 'تغییر وضعیت چک ناموفق بود.';
} finally {
savingId.value = '';
}
}
onMounted(load);
</script>
<template>
<section class="page" dir="rtl">
<header class="head">
<div>
<h1>چک‌ها</h1>
<p class="sub">دفتر چک‌های دریافتی وصل به فاکتور؛ وضعیت فقط برای پیگیری است</p>
</div>
<UiButton variant="ghost" type="button" :disabled="loading" @click="load">
بروزرسانی
</UiButton>
</header>
<div class="toolbar">
<input v-model="q" type="search" placeholder="شماره چک، بانک یا فاکتور" @keyup.enter="load" />
<select v-model="status" @change="load">
<option value="">همه وضعیت‌ها</option>
<option value="pending">در جریان</option>
<option value="cleared">وصول شد</option>
<option value="bounced">برگشت</option>
</select>
<UiButton type="button" @click="load">اعمال</UiButton>
</div>
<p v-if="error" class="error">{{ error }}</p>
<p v-else-if="loading && !items.length" class="muted">در حال بارگذاری…</p>
<p v-else-if="!filtered.length" class="muted">چکی ثبت نشده.</p>
<div v-else class="table-wrap">
<table>
<thead>
<tr>
<th>شماره</th>
<th>بانک</th>
<th>سررسید</th>
<th>مبلغ</th>
<th>فاکتور</th>
<th>طرف‌حساب</th>
<th>وضعیت</th>
</tr>
</thead>
<tbody>
<tr v-for="row in filtered" :key="row.id">
<td>{{ row.number }}</td>
<td>{{ row.bank }}</td>
<td>{{ formatJalaliNumeric(new Date(row.dueDate)) }}</td>
<td>{{ formatMoney(row.amount, row.currency) }}</td>
<td>
<RouterLink :to="{ name: 'invoice-detail', params: { id: row.invoiceId } }">
{{ row.invoiceNumber }}
</RouterLink>
</td>
<td>{{ row.personName || '—' }}</td>
<td>
<select
:value="row.status"
:disabled="savingId === row.id"
@change="setStatus(row, ($event.target as HTMLSelectElement).value as ChequeStatus)"
>
<option value="pending">{{ statusLabel.pending }}</option>
<option value="cleared">{{ statusLabel.cleared }}</option>
<option value="bounced">{{ statusLabel.bounced }}</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
</section>
</template>
<style scoped>
.page { display: grid; gap: 1rem; }
.head { display: flex; justify-content: space-between; gap: 1rem; align-items: flex-start; }
h1 { margin: 0; font-size: 1.25rem; }
.sub, .muted { margin: 0.25rem 0 0; color: var(--muted); font-size: 0.88rem; }
.error { color: var(--danger, #dc2626); font-weight: 600; }
.toolbar {
display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
}
.toolbar input, .toolbar select {
min-height: var(--control-h); border: 1px solid var(--line); border-radius: var(--radius);
padding: 0 0.75rem; background: #fff;
}
.table-wrap { overflow: auto; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); }
table { width: 100%; border-collapse: collapse; min-width: 720px; }
th, td { padding: 0.7rem 0.65rem; border-bottom: 1px solid #eef2f7; text-align: right; }
th { background: #f8fafc; color: #64748b; font-size: 0.82rem; }
select {
min-height: var(--control-h); border: 1px solid var(--line); border-radius: var(--radius);
padding: 0 0.55rem; background: #fff;
}
a { color: #0f766e; font-weight: 700; }
</style>