102 lines
6.6 KiB
TypeScript
102 lines
6.6 KiB
TypeScript
|
|
|
|||
|
|
import React from 'react';
|
|||
|
|
import { Invoice, InvoiceStatus } from '../../types';
|
|||
|
|
import { Calendar, Clock, CheckCircle2, X } from 'lucide-react';
|
|||
|
|
|
|||
|
|
export const PaymentSchedule: React.FC<{
|
|||
|
|
approvedInvoices: Invoice[],
|
|||
|
|
scheduledInvoices: Invoice[],
|
|||
|
|
onUpdateStatus: (id: string, s: InvoiceStatus, extra?: any) => void,
|
|||
|
|
currentBalance: number
|
|||
|
|
}> = ({ approvedInvoices, scheduledInvoices, onUpdateStatus, currentBalance }) => {
|
|||
|
|
return (
|
|||
|
|
<div className="space-y-6 animate-fade-in">
|
|||
|
|
{/* Approved but not scheduled */}
|
|||
|
|
{approvedInvoices.length > 0 && (
|
|||
|
|
<div className="bg-white p-5 rounded-2xl border-2 border-dashed border-primary-200">
|
|||
|
|
<h3 className="font-bold text-primary-700 text-sm mb-4 flex items-center gap-2">
|
|||
|
|
<Clock className="w-5 h-5"/> Очередь на оплату ({approvedInvoices.length})
|
|||
|
|
</h3>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
{approvedInvoices.map(inv => (
|
|||
|
|
<div key={inv.id} className="flex items-center justify-between p-3 bg-primary-50/50 rounded-xl border border-primary-100">
|
|||
|
|
<div className="flex-1 min-w-0 pr-4">
|
|||
|
|
<p className="text-xs font-bold text-slate-800 truncate">{inv.contractorName}</p>
|
|||
|
|
<p className="text-[10px] text-slate-500">{inv.address}</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center gap-3">
|
|||
|
|
<span className="text-xs font-black text-primary-700">{inv.amount.toLocaleString()}₽</span>
|
|||
|
|
<button
|
|||
|
|
onClick={() => onUpdateStatus(inv.id, 'scheduled', { scheduledDate: new Date().toISOString().split('T')[0] })}
|
|||
|
|
className="bg-primary-600 text-white px-3 py-1.5 rounded-lg text-[10px] font-black uppercase hover:bg-primary-700 transition-colors flex items-center gap-1"
|
|||
|
|
>
|
|||
|
|
<Calendar className="w-3 h-3"/> В график
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* The Calendar List */}
|
|||
|
|
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden">
|
|||
|
|
<div className="p-4 bg-slate-900 text-white flex justify-between items-center">
|
|||
|
|
<div className="flex items-center gap-3">
|
|||
|
|
<div className="p-2 bg-white/10 rounded-lg"><Calendar className="w-5 h-5"/></div>
|
|||
|
|
<div>
|
|||
|
|
<h3 className="font-bold leading-none">График платежей</h3>
|
|||
|
|
<p className="text-[10px] text-slate-400 mt-1 uppercase font-bold tracking-widest">Июнь 2024</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-right">
|
|||
|
|
<p className="text-lg font-black">{scheduledInvoices.reduce((s, i) => s + i.amount, 0).toLocaleString()} ₽</p>
|
|||
|
|
<p className="text-[10px] text-slate-400 uppercase font-bold">К оплате</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="divide-y divide-slate-100">
|
|||
|
|
{scheduledInvoices.length === 0 && <div className="p-12 text-center text-slate-400 italic">График выплат пуст</div>}
|
|||
|
|
{scheduledInvoices.map(inv => (
|
|||
|
|
<div key={inv.id} className="p-4 flex items-center gap-4 hover:bg-slate-50 transition-colors group">
|
|||
|
|
<div className={`p-2 rounded-xl text-center min-w-[50px] ${inv.status === 'overdue' ? 'bg-red-50 text-red-600' : 'bg-slate-50 text-slate-500'}`}>
|
|||
|
|
<p className="text-xs font-black leading-none">{inv.scheduledDate?.split('-')[2] || '??'}</p>
|
|||
|
|
<p className="text-[9px] font-bold uppercase mt-1">июн</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex-1 min-w-0">
|
|||
|
|
<div className="flex items-center gap-2 mb-0.5">
|
|||
|
|
<p className="text-sm font-bold text-slate-800 truncate">{inv.contractorName}</p>
|
|||
|
|
{inv.status === 'overdue' && <span className="bg-red-500 text-white text-[8px] font-black px-1.5 rounded-full uppercase animate-pulse">Просрочен</span>}
|
|||
|
|
</div>
|
|||
|
|
<p className="text-[10px] text-slate-500 truncate">{inv.address} • {inv.serviceName}</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-right flex items-center gap-4">
|
|||
|
|
<div>
|
|||
|
|
<p className="text-sm font-black text-slate-900">{inv.amount.toLocaleString()} ₽</p>
|
|||
|
|
<p className="text-[9px] text-slate-400 font-bold uppercase">Баланс: {(currentBalance/1000).toFixed(0)}k</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex gap-1">
|
|||
|
|
<button
|
|||
|
|
onClick={() => onUpdateStatus(inv.id, 'paid')}
|
|||
|
|
className="p-2 bg-emerald-50 text-emerald-600 rounded-lg hover:bg-emerald-100 shadow-sm transition-colors border border-emerald-100"
|
|||
|
|
title="Оплачено"
|
|||
|
|
>
|
|||
|
|
<CheckCircle2 className="w-5 h-5"/>
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={() => onUpdateStatus(inv.id, 'approved', { scheduledDate: undefined })}
|
|||
|
|
className="p-2 bg-slate-50 text-slate-400 rounded-lg hover:bg-red-50 hover:text-red-500 transition-colors"
|
|||
|
|
title="Убрать"
|
|||
|
|
>
|
|||
|
|
<X className="w-5 h-5"/>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|