Files
mkd/components/finance/InvoiceRegistry.tsx
2026-02-04 00:17:04 +05:00

102 lines
6.7 KiB
TypeScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { Invoice, InvoiceStatus } from '../../types';
import { FileText, FileSearch, AlertCircle, FileCheck, Check, X, MessageSquareQuote, CalendarPlus } from 'lucide-react';
const StatusBadge: React.FC<{ status: InvoiceStatus }> = ({ status }) => {
const config: Record<InvoiceStatus, { label: string, color: string, bg: string }> = {
draft: { label: 'Черновик', color: 'text-slate-500', bg: 'bg-slate-100' },
pending_approval: { label: 'На согл.', color: 'text-amber-600', bg: 'bg-amber-50' },
clarification: { label: 'Уточнение', color: 'text-purple-600', bg: 'bg-purple-50' },
approved: { label: 'Согласован', color: 'text-blue-600', bg: 'bg-blue-50' },
scheduled: { label: 'В графике', color: 'text-indigo-600', bg: 'bg-indigo-50' },
paid: { label: 'Оплачен', color: 'text-emerald-600', bg: 'bg-emerald-50' },
rejected: { label: 'Отказ', color: 'text-red-600', bg: 'bg-red-50' },
overdue: { label: 'Просрочен', color: 'text-white', bg: 'bg-red-500' },
};
const s = config[status] || config.draft;
return (
<span className={`px-2 py-0.5 rounded text-[10px] font-black uppercase tracking-wider ${s.bg} ${s.color}`}>
{s.label}
</span>
);
};
export const InvoiceRegistry: React.FC<{ invoices: Invoice[], onUpdateStatus: (id: string, s: InvoiceStatus, extra?: any) => void }> = ({ invoices, onUpdateStatus }) => {
// Show only "inbox" invoices: draft, pending, clarification, approved
const registryInvoices = invoices.filter(i => ['draft', 'pending_approval', 'clarification', 'approved', 'rejected'].includes(i.status));
return (
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden animate-fade-in">
<div className="p-4 bg-slate-50 border-b border-slate-200 flex justify-between items-center">
<h3 className="font-black text-slate-700 text-[10px] uppercase tracking-widest">Реестр согласования счетов</h3>
<div className="flex gap-2">
<button className="p-1.5 bg-white border border-slate-200 rounded-lg text-slate-400"><FileSearch className="w-4 h-4"/></button>
</div>
</div>
<div className="divide-y divide-slate-100">
{registryInvoices.length === 0 && <div className="p-10 text-center text-slate-400 text-sm">Новых счетов нет</div>}
{registryInvoices.map(inv => (
<div key={inv.id} className="p-4 flex flex-col md:flex-row md:items-center justify-between hover:bg-slate-50 transition-colors group gap-4">
<div className="flex gap-4 items-center flex-1">
<div className={`p-3 rounded-xl ${inv.status === 'approved' ? 'bg-blue-50 text-blue-600' : 'bg-slate-50 text-slate-400'}`}>
<FileText className="w-5 h-5"/>
</div>
<div>
<div className="flex items-center gap-2 mb-1">
<StatusBadge status={inv.status}/>
{inv.priority === 'high' && <span className="text-[9px] font-black text-red-500 uppercase">Срочно</span>}
</div>
<p className="text-sm font-bold text-slate-800">{inv.contractorName}</p>
<p className="text-[10px] text-slate-500 font-medium">{inv.address} {inv.serviceName}</p>
</div>
</div>
<div className="flex items-center justify-between md:justify-end gap-6">
<div className="text-right">
<p className="text-sm font-black text-slate-900">{inv.amount.toLocaleString()} </p>
<p className="text-[10px] text-slate-400 font-medium">{inv.date}</p>
</div>
<div className="flex gap-2">
{inv.status === 'pending_approval' || inv.status === 'clarification' ? (
<>
<button
onClick={() => onUpdateStatus(inv.id, 'approved')}
className="p-2 bg-emerald-50 text-emerald-600 rounded-xl hover:bg-emerald-100 border border-emerald-100"
title="Согласовать"
>
<Check className="w-4 h-4"/>
</button>
<button
onClick={() => onUpdateStatus(inv.id, 'clarification')}
className="p-2 bg-purple-50 text-purple-600 rounded-xl hover:bg-purple-100 border border-purple-100"
title="На уточнение"
>
<MessageSquareQuote className="w-4 h-4"/>
</button>
<button
onClick={() => onUpdateStatus(inv.id, 'rejected')}
className="p-2 bg-red-50 text-red-600 rounded-xl hover:bg-red-100 border border-red-100"
title="Отказать"
>
<X className="w-4 h-4"/>
</button>
</>
) : inv.status === 'approved' ? (
<button
onClick={() => onUpdateStatus(inv.id, 'scheduled', { scheduledDate: new Date().toISOString().split('T')[0] })}
className="px-3 py-2 bg-indigo-600 text-white rounded-xl hover:bg-indigo-700 text-[10px] font-black uppercase flex items-center gap-1.5 shadow-lg shadow-indigo-500/20"
>
<CalendarPlus className="w-4 h-4"/> В график
</button>
) : null}
</div>
</div>
</div>
))}
</div>
</div>
);
};