diff --git a/REPORT-36H.md b/REPORT-36H.md index 99e7ac8..a201284 100644 --- a/REPORT-36H.md +++ b/REPORT-36H.md @@ -17,8 +17,13 @@ Succes. 2 fichiers modifies (CashflowTab.tsx, PaymentsTab.tsx). - Arret apres 10s : OUI (setTimeout → setBlinkingId(null)) - Tab key Счета : `payments` (dans DocumentsPage.tsx) -## 4. COMPILATION +## 4. FILTRES ET TRI +- Filtre par statut (Radio.Group) : OUI — Все / Ожидает / Одобрен / Планируется +- Tri "Просроченные ↑" : OUI — overdue en tete, puis par date +- Colonne "Просрочка" (N дн. en rouge) : OUI — calcul diffDays, affiche si > 0 + +## 5. COMPILATION - Frontend tsc : 0 erreurs -## 5. COMMITS +## 6. COMMITS A commiter diff --git a/frontend/src/pages/finance/CashflowTab.tsx b/frontend/src/pages/finance/CashflowTab.tsx index 4b6a5c2..e5729da 100644 --- a/frontend/src/pages/finance/CashflowTab.tsx +++ b/frontend/src/pages/finance/CashflowTab.tsx @@ -188,6 +188,8 @@ export function CashflowTab() { const [manualSubmitting, setManualSubmitting] = useState(false); const [manualForm] = Form.useForm(); const [upcomingDateRange, setUpcomingDateRange] = useState<[dayjs.Dayjs | null, dayjs.Dayjs | null] | null>(null); + const [upcomingStatusFilter, setUpcomingStatusFilter] = useState('ALL'); + const [upcomingSortMode, setUpcomingSortMode] = useState<'date' | 'overdue'>('date'); // ---- Fetch functions ---- @@ -806,15 +808,35 @@ export function CashflowTab() { // ---- Filtered upcoming payments ---- const filteredUpcoming = useMemo(() => { - const items = dashboard?.upcomingPayments ?? []; - if (!upcomingDateRange || !upcomingDateRange[0] || !upcomingDateRange[1]) return items; - const from = upcomingDateRange[0].startOf('day'); - const to = upcomingDateRange[1].endOf('day'); - return items.filter(p => { - const d = dayjs(p.date); - return d.isAfter(from.subtract(1, 'ms')) && d.isBefore(to.add(1, 'ms')); - }); - }, [dashboard?.upcomingPayments, upcomingDateRange]); + let items = [...(dashboard?.upcomingPayments ?? [])]; + // Date range filter + if (upcomingDateRange && upcomingDateRange[0] && upcomingDateRange[1]) { + const from = upcomingDateRange[0].startOf('day'); + const to = upcomingDateRange[1].endOf('day'); + items = items.filter(p => { + const d = dayjs(p.date); + return d.isAfter(from.subtract(1, 'ms')) && d.isBefore(to.add(1, 'ms')); + }); + } + // Status filter + if (upcomingStatusFilter !== 'ALL') { + items = items.filter(p => p.status === upcomingStatusFilter); + } + // Sort + const now = new Date(); + now.setHours(0, 0, 0, 0); + if (upcomingSortMode === 'overdue') { + items.sort((a, b) => { + const aOver = new Date(a.date) < now ? 1 : 0; + const bOver = new Date(b.date) < now ? 1 : 0; + if (aOver !== bOver) return bOver - aOver; + return new Date(a.date).getTime() - new Date(b.date).getTime(); + }); + } else { + items.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + } + return items; + }, [dashboard?.upcomingPayments, upcomingDateRange, upcomingStatusFilter, upcomingSortMode]); const upcomingTotal = useMemo(() => filteredUpcoming.reduce((s, p) => s + p.amount, 0), [filteredUpcoming]); @@ -834,6 +856,13 @@ export function CashflowTab() { { title: 'Сумма', dataIndex: 'amount', key: 'amount', width: 130, align: 'right', render: (v: number) => formatAmount(v) }, { title: 'Статус', dataIndex: 'status', key: 'status', width: 120, render: (v: string) => {upcomingStatusLabels[v] ?? v} }, + { title: 'Просрочка', key: 'overdue', width: 100, + render: (_: unknown, r) => { + const now = new Date(); now.setHours(0, 0, 0, 0); + const due = new Date(r.date); due.setHours(0, 0, 0, 0); + const diff = Math.floor((now.getTime() - due.getTime()) / (1000 * 60 * 60 * 24)); + return diff > 0 ? {diff} дн. : null; + } }, { title: 'ЗП', dataIndex: 'poNumber', key: 'po', width: 130, render: (v: string, r) => r.poId ? { e.stopPropagation(); window.location.href = `/purchases?tab=orders&po=${r.poId}`; }}>{v} : v }, ]; @@ -1167,20 +1196,36 @@ export function CashflowTab() { ), - children: filteredUpcoming.length > 0 ? ( - `${r.type}-${r.id}`} - pagination={{ pageSize: 10, size: 'small', showSizeChanger: true, pageSizeOptions: ['10', '25', '50'] }} - size="small" - onRow={(r) => ({ - style: { cursor: r.poId ? 'pointer' : undefined }, - onClick: () => { if (r.poId) window.location.href = `/purchases?tab=orders&po=${r.poId}`; }, - })} - /> - ) : ( -
Нет ближайших платежей
+ children: ( + <> + + setUpcomingStatusFilter(e.target.value)} size="small"> + Все + Ожидает + Одобрен + Планируется + + setUpcomingSortMode(e.target.value)} size="small"> + По дате + Просроченные ↑ + + + {filteredUpcoming.length > 0 ? ( +
`${r.type}-${r.id}`} + pagination={{ pageSize: 10, size: 'small', showSizeChanger: true, pageSizeOptions: ['10', '25', '50'] }} + size="small" + onRow={(r) => ({ + style: { cursor: r.poId ? 'pointer' : undefined }, + onClick: () => { if (r.poId) window.location.href = `/purchases?tab=orders&po=${r.poId}`; }, + })} + /> + ) : ( +
Нет ближайших платежей
+ )} + ), }]} />