170 lines
6.6 KiB
TypeScript
170 lines
6.6 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|||
|
|
import { X, Wallet, Calendar, Ban, Clock } from 'lucide-react';
|
|||
|
|
|
|||
|
|
export type PaymentStatusAction = 'paid' | 'postponed' | 'cancelled';
|
|||
|
|
|
|||
|
|
export interface PaymentStatusModalPayload {
|
|||
|
|
paymentDate?: string;
|
|||
|
|
paymentRef?: string;
|
|||
|
|
isCash?: boolean;
|
|||
|
|
postponedDate?: string;
|
|||
|
|
cancelReason?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface PaymentStatusModalProps {
|
|||
|
|
action: PaymentStatusAction;
|
|||
|
|
invoiceLabel?: string;
|
|||
|
|
/** По умолчанию отметить «Оплата наличными» */
|
|||
|
|
defaultIsCash?: boolean;
|
|||
|
|
onConfirm: (payload: PaymentStatusModalPayload) => void;
|
|||
|
|
onCancel: () => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const PaymentStatusModal: React.FC<PaymentStatusModalProps> = ({
|
|||
|
|
action,
|
|||
|
|
invoiceLabel,
|
|||
|
|
defaultIsCash = false,
|
|||
|
|
onConfirm,
|
|||
|
|
onCancel
|
|||
|
|
}) => {
|
|||
|
|
const today = new Date().toISOString().split('T')[0];
|
|||
|
|
const [paymentDate, setPaymentDate] = useState(today);
|
|||
|
|
const [paymentRef, setPaymentRef] = useState('');
|
|||
|
|
const [isCash, setIsCash] = useState(defaultIsCash);
|
|||
|
|
const [postponedDate, setPostponedDate] = useState('');
|
|||
|
|
const [cancelReason, setCancelReason] = useState('');
|
|||
|
|
|
|||
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
if (action === 'paid') {
|
|||
|
|
onConfirm({ paymentDate, paymentRef: paymentRef.trim() || undefined, isCash });
|
|||
|
|
} else if (action === 'postponed') {
|
|||
|
|
if (!postponedDate) {
|
|||
|
|
alert('Укажите дату переноса');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
onConfirm({ postponedDate });
|
|||
|
|
} else {
|
|||
|
|
if (!cancelReason.trim()) {
|
|||
|
|
alert('Укажите причину отмены');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
onConfirm({ cancelReason: cancelReason.trim() });
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const title =
|
|||
|
|
action === 'paid'
|
|||
|
|
? 'Оплата счета'
|
|||
|
|
: action === 'postponed'
|
|||
|
|
? 'Перенос на другую дату'
|
|||
|
|
: 'Отмена / отказ';
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50">
|
|||
|
|
<div className="bg-white rounded-2xl shadow-xl max-w-md w-full">
|
|||
|
|
<div className="p-4 border-b border-slate-200 flex justify-between items-center">
|
|||
|
|
<h3 className="font-bold text-slate-800">{title}</h3>
|
|||
|
|
<button type="button" onClick={onCancel} className="p-2 rounded-lg hover:bg-slate-100 text-slate-500">
|
|||
|
|
<X className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
<form onSubmit={handleSubmit} className="p-4 space-y-4">
|
|||
|
|
{invoiceLabel && (
|
|||
|
|
<p className="text-sm text-slate-600">{invoiceLabel}</p>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{action === 'paid' && (
|
|||
|
|
<>
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Дата оплаты</label>
|
|||
|
|
<div className="relative">
|
|||
|
|
<Calendar className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|||
|
|
<input
|
|||
|
|
type="date"
|
|||
|
|
value={paymentDate}
|
|||
|
|
onChange={(e) => setPaymentDate(e.target.value)}
|
|||
|
|
className="w-full pl-10 pr-3 py-2 border border-slate-200 rounded-xl text-sm"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Номер платежки</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
value={paymentRef}
|
|||
|
|
onChange={(e) => setPaymentRef(e.target.value)}
|
|||
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm"
|
|||
|
|
placeholder="Номер платёжного поручения"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<label className="flex items-center gap-2 cursor-pointer">
|
|||
|
|
<input
|
|||
|
|
type="checkbox"
|
|||
|
|
checked={isCash}
|
|||
|
|
onChange={(e) => setIsCash(e.target.checked)}
|
|||
|
|
className="rounded border-slate-300"
|
|||
|
|
/>
|
|||
|
|
<span className="text-sm font-medium text-slate-700">Оплата наличными</span>
|
|||
|
|
</label>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{action === 'postponed' && (
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Перенос на дату *</label>
|
|||
|
|
<div className="relative">
|
|||
|
|
<Clock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|||
|
|
<input
|
|||
|
|
type="date"
|
|||
|
|
value={postponedDate}
|
|||
|
|
onChange={(e) => setPostponedDate(e.target.value)}
|
|||
|
|
className="w-full pl-10 pr-3 py-2 border border-slate-200 rounded-xl text-sm"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{action === 'cancelled' && (
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Причина отмены / отказа *</label>
|
|||
|
|
<textarea
|
|||
|
|
value={cancelReason}
|
|||
|
|
onChange={(e) => setCancelReason(e.target.value)}
|
|||
|
|
rows={3}
|
|||
|
|
className="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm resize-none"
|
|||
|
|
placeholder="Укажите причину"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<div className="flex gap-2 pt-2">
|
|||
|
|
<button
|
|||
|
|
type="submit"
|
|||
|
|
className={`flex items-center gap-2 px-4 py-2 rounded-xl font-medium text-sm ${
|
|||
|
|
action === 'paid'
|
|||
|
|
? 'bg-emerald-600 text-white hover:bg-emerald-700'
|
|||
|
|
: action === 'postponed'
|
|||
|
|
? 'bg-amber-600 text-white hover:bg-amber-700'
|
|||
|
|
: 'bg-red-600 text-white hover:bg-red-700'
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{action === 'paid' && <Wallet className="w-4 h-4" />}
|
|||
|
|
{action === 'postponed' && <Clock className="w-4 h-4" />}
|
|||
|
|
{action === 'cancelled' && <Ban className="w-4 h-4" />}
|
|||
|
|
{action === 'paid' ? 'Оплатить' : action === 'postponed' ? 'Перенести' : 'Отменить'}
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={onCancel}
|
|||
|
|
className="px-4 py-2 border border-slate-200 rounded-xl font-medium text-sm text-slate-600 hover:bg-slate-50"
|
|||
|
|
>
|
|||
|
|
Отмена
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|