Edit payment

This commit is contained in:
Rahaf
2026-07-04 02:05:13 +03:00
parent 3b66bb53f1
commit 2d2e2427a2

View File

@ -26,7 +26,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import toast, { Toaster } from 'react-hot-toast'; import toast, { Toaster } from 'react-hot-toast';
import AuthService from '@/app/services/AuthService'; import AuthService from '@/app/services/AuthService';
import { payDeposit, getMyTransaction } from '@/app/utils/api'; import { payDeposit, getMyTransaction, getPaymentTypes } from '@/app/utils/api';
const STATUS_MAP = ['pending', 'ownerConfirmed', 'depositPaid', 'depositConfirmed', 'completed', 'cancelled']; const STATUS_MAP = ['pending', 'ownerConfirmed', 'depositPaid', 'depositConfirmed', 'completed', 'cancelled'];
@ -67,6 +67,8 @@ export default function PaymentsPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [payingId, setPayingId] = useState(null); const [payingId, setPayingId] = useState(null);
const [isGuest, setIsGuest] = useState(null); const [isGuest, setIsGuest] = useState(null);
const [paymentMethods, setPaymentMethods] = useState([]);
const [loadingPaymentMethods, setLoadingPaymentMethods] = useState(true);
const [selectedPayment, setSelectedPayment] = useState('cash'); const [selectedPayment, setSelectedPayment] = useState('cash');
const loadReservations = useCallback(async () => { const loadReservations = useCallback(async () => {
@ -109,6 +111,24 @@ export default function PaymentsPage() {
} }
}, [t]); }, [t]);
const loadPaymentMethods = useCallback(async () => {
setLoadingPaymentMethods(true);
try {
const data = await getPaymentTypes();
const methods = Array.isArray(data) ? data : [];
setPaymentMethods(methods);
const firstActive = methods.find((method) => method?.isActive === true || method?.active === true);
if (firstActive) {
setSelectedPayment(firstActive.id ?? firstActive.name ?? selectedPayment);
}
} catch (err) {
console.error('[Payments] failed to load payment methods', err);
setPaymentMethods([]);
} finally {
setLoadingPaymentMethods(false);
}
}, [selectedPayment]);
useEffect(() => { useEffect(() => {
if (AuthService.isGuest()) { if (AuthService.isGuest()) {
setIsGuest(true); setIsGuest(true);
@ -119,10 +139,25 @@ export default function PaymentsPage() {
loadReservations(); loadReservations();
}, [loadReservations]); }, [loadReservations]);
const handlePayDeposit = async (reservation) => { useEffect(() => {
loadPaymentMethods();
}, [loadPaymentMethods]);
const resolvePaymentTypeId = (paymentKey) => {
const method = paymentMethods.find(
(m) => String(m.id) === String(paymentKey) || String(m.name) === String(paymentKey),
);
return method?.id ?? paymentKey;
};
const handlePayDeposit = async (reservation, paymentKey) => {
setPayingId(reservation.id); setPayingId(reservation.id);
const paymentTypeId = resolvePaymentTypeId(paymentKey);
try { try {
await payDeposit({ reservationId: reservation.id }); await payDeposit({
reservationId: reservation.id,
paymentTypeId,
});
toast.success(t('payments.depositPaidSuccess')); toast.success(t('payments.depositPaidSuccess'));
loadReservations(); loadReservations();
} catch (err) { } catch (err) {
@ -196,6 +231,8 @@ export default function PaymentsPage() {
key={r.id || i} key={r.id || i}
reservation={r} reservation={r}
payingId={payingId} payingId={payingId}
paymentMethods={paymentMethods}
loadingPaymentMethods={loadingPaymentMethods}
selectedPayment={selectedPayment} selectedPayment={selectedPayment}
onSelectPayment={setSelectedPayment} onSelectPayment={setSelectedPayment}
onPay={handlePayDeposit} onPay={handlePayDeposit}
@ -262,10 +299,11 @@ export default function PaymentsPage() {
} }
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment, onPay }) { function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMethods, selectedPayment, onSelectPayment, onPay }) {
const r = reservation; const r = reservation;
const amount = r.depositAmount || r.totalPrice || 0; const amount = r.depositAmount || r.totalPrice || 0;
const [showCashDialog, setShowCashDialog] = useState(false); const [showCashDialog, setShowCashDialog] = useState(false);
const methods = paymentMethods.length > 0 ? paymentMethods : getPaymentMethods(t);
return ( return (
<> <>
@ -332,49 +370,64 @@ export default function PaymentsPage() {
<div className="px-6 py-5 border-b border-gray-100"> <div className="px-6 py-5 border-b border-gray-100">
<p className="text-sm font-bold text-gray-700 mb-3">{t('payments.paymentMethodLabel')}</p> <p className="text-sm font-bold text-gray-700 mb-3">{t('payments.paymentMethodLabel')}</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{getPaymentMethods(t).map((method) => { {loadingPaymentMethods ? (
const isSelected = selectedPayment === method.id; <div className="col-span-full rounded-2xl border border-dashed border-gray-200 bg-gray-50 p-4 text-sm text-gray-600">
const Icon = method.icon; {t('payments.loadingPaymentMethods', { defaultValue: 'جاري تحميل طرق الدفع...' })}
return ( </div>
<button ) : methods.length === 0 ? (
key={method.id} <div className="col-span-full rounded-2xl border border-dashed border-gray-200 bg-gray-50 p-4 text-sm text-gray-600">
type="button" {t('payments.noPaymentMethodsAvailable', { defaultValue: 'لا توجد طرق دفع متاحة حالياً.' })}
disabled={!method.active} </div>
onClick={() => method.active && onSelectPayment(method.id)} ) : (
className={`relative flex items-start gap-3 p-4 rounded-2xl border-2 text-right transition-all ${ methods.map((method, index) => {
!method.active const optionId = method.id ?? method.name ?? `payment-method-${index}`;
? 'border-gray-100 bg-gray-50 opacity-50 cursor-not-allowed' const isActive = method?.isActive === true || method?.active === true;
: isSelected const isSelected = String(selectedPayment) === String(optionId);
? 'border-amber-500 bg-amber-50 shadow-sm' const Icon = method.icon || CreditCard;
: 'border-gray-200 bg-white hover:border-amber-300 cursor-pointer'
}`} return (
> <button
<div className={`w-10 h-10 rounded-xl flex items-center justify-center shrink-0 ${ key={optionId}
isSelected ? 'bg-amber-500 text-white' : 'bg-gray-100 text-gray-500' type="button"
}`}> disabled={!isActive}
<Icon className="w-5 h-5" /> onClick={() => isActive && onSelectPayment(optionId)}
</div> className={`relative flex items-start gap-3 p-4 rounded-2xl border-2 text-right transition-all ${
<div className="min-w-0 flex-1"> !isActive
<p className={`text-sm font-bold ${isSelected ? 'text-amber-900' : 'text-gray-800'}`}> ? 'border-gray-100 bg-gray-50 opacity-50 cursor-not-allowed'
{method.label} : isSelected
</p> ? 'border-amber-500 bg-amber-50 shadow-sm'
<p className="text-xs text-gray-500 mt-0.5 leading-relaxed"> : 'border-gray-200 bg-white hover:border-amber-300 cursor-pointer'
{method.desc} }`}
</p> >
{!method.active && ( <div className={`w-10 h-10 rounded-xl flex items-center justify-center shrink-0 ${
<span className="inline-block mt-1 text-[10px] font-medium text-gray-400 bg-gray-200 px-2 py-0.5 rounded-full"> isSelected ? 'bg-amber-500 text-white' : 'bg-gray-100 text-gray-500'
{t('validation.notAvailable', { defaultValue: 'غير متاح' })} }`}>
</span> <Icon className="w-5 h-5" />
)}
</div>
{isSelected && (
<div className="absolute top-2 left-2 w-5 h-5 bg-amber-500 rounded-full flex items-center justify-center">
<Check className="w-3 h-3 text-white" />
</div> </div>
)} <div className="min-w-0 flex-1">
</button> <p className={`text-sm font-bold ${isSelected ? 'text-amber-900' : 'text-gray-800'}`}>
); {method.name || method.label || t('paymentMethod', { defaultValue: 'طريقة الدفع' })}
})} </p>
{method.description && (
<p className="text-xs text-gray-500 mt-0.5 leading-relaxed">
{method.description}
</p>
)}
<span className={`inline-block mt-1 text-[10px] font-medium px-2 py-0.5 rounded-full ${
isActive ? 'bg-amber-100 text-amber-700' : 'bg-gray-200 text-gray-500'
}`}>
{isActive ? t('active', { defaultValue: 'نشط' }) : t('inactive', { defaultValue: 'غير نشط' })}
</span>
</div>
{isSelected && (
<div className="absolute top-2 left-2 w-5 h-5 bg-amber-500 rounded-full flex items-center justify-center">
<Check className="w-3 h-3 text-white" />
</div>
)}
</button>
);
})
)}
</div> </div>
</div> </div>
@ -382,7 +435,7 @@ export default function PaymentsPage() {
<div className="px-6 py-5 border-b border-gray-100"> <div className="px-6 py-5 border-b border-gray-100">
<motion.button <motion.button
type="button" type="button"
onClick={() => onPay(r)} onClick={() => onPay(r, selectedPayment)}
disabled={payingId === r.id} disabled={payingId === r.id}
whileHover={{ scale: 1.01 }} whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }} whileTap={{ scale: 0.99 }}