This commit is contained in:
@ -61,6 +61,10 @@ function formatDate(date) {
|
|||||||
return d.toLocaleDateString('en-GB');
|
return d.toLocaleDateString('en-GB');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeText(value) {
|
||||||
|
return String(value ?? '').trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
export default function PaymentsPage() {
|
export default function PaymentsPage() {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const [reservations, setReservations] = useState([]);
|
const [reservations, setReservations] = useState([]);
|
||||||
@ -69,7 +73,7 @@ export default function PaymentsPage() {
|
|||||||
const [isGuest, setIsGuest] = useState(null);
|
const [isGuest, setIsGuest] = useState(null);
|
||||||
const [paymentMethods, setPaymentMethods] = useState([]);
|
const [paymentMethods, setPaymentMethods] = useState([]);
|
||||||
const [loadingPaymentMethods, setLoadingPaymentMethods] = useState(true);
|
const [loadingPaymentMethods, setLoadingPaymentMethods] = useState(true);
|
||||||
const [selectedPayment, setSelectedPayment] = useState('cash');
|
const [selectedPayment, setSelectedPayment] = useState(null);
|
||||||
|
|
||||||
const loadReservations = useCallback(async () => {
|
const loadReservations = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@ -117,9 +121,10 @@ export default function PaymentsPage() {
|
|||||||
const data = await getPaymentTypes();
|
const data = await getPaymentTypes();
|
||||||
const methods = Array.isArray(data) ? data : [];
|
const methods = Array.isArray(data) ? data : [];
|
||||||
setPaymentMethods(methods);
|
setPaymentMethods(methods);
|
||||||
|
|
||||||
const firstActive = methods.find((method) => method?.isActive === true || method?.active === true);
|
const firstActive = methods.find((method) => method?.isActive === true || method?.active === true);
|
||||||
if (firstActive) {
|
if (firstActive) {
|
||||||
setSelectedPayment(firstActive.id ?? firstActive.name ?? selectedPayment);
|
setSelectedPayment(firstActive.name ?? firstActive.id ?? null);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Payments] failed to load payment methods', err);
|
console.error('[Payments] failed to load payment methods', err);
|
||||||
@ -127,7 +132,7 @@ export default function PaymentsPage() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoadingPaymentMethods(false);
|
setLoadingPaymentMethods(false);
|
||||||
}
|
}
|
||||||
}, [selectedPayment]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (AuthService.isGuest()) {
|
if (AuthService.isGuest()) {
|
||||||
@ -150,13 +155,25 @@ export default function PaymentsPage() {
|
|||||||
return method?.id ?? paymentKey;
|
return method?.id ?? paymentKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePayDeposit = async (reservation, paymentKey) => {
|
const handlePayDeposit = async (reservation, paymentKey, paymentImageFile) => {
|
||||||
setPayingId(reservation.id);
|
|
||||||
const paymentTypeId = resolvePaymentTypeId(paymentKey);
|
const paymentTypeId = resolvePaymentTypeId(paymentKey);
|
||||||
|
const selectedMethod = paymentMethods.find(
|
||||||
|
(m) => String(m.id) === String(paymentTypeId) || String(m.name) === String(paymentTypeId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const isHaram = normalizeText(selectedMethod?.name).includes('haram');
|
||||||
|
|
||||||
|
if (isHaram && !paymentImageFile) {
|
||||||
|
toast.error(t('payments.uploadReceiptRequired', { defaultValue: 'يرجى رفع صورة الوصل' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPayingId(reservation.id);
|
||||||
try {
|
try {
|
||||||
await payDeposit({
|
await payDeposit({
|
||||||
reservationId: reservation.id,
|
reservationId: reservation.id,
|
||||||
paymentTypeId,
|
paymentTypeId,
|
||||||
|
paymentImage: paymentImageFile,
|
||||||
});
|
});
|
||||||
toast.success(t('payments.depositPaidSuccess'));
|
toast.success(t('payments.depositPaidSuccess'));
|
||||||
loadReservations();
|
loadReservations();
|
||||||
@ -300,11 +317,29 @@ export default function PaymentsPage() {
|
|||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMethods, selectedPayment, onSelectPayment, onPay }) {
|
function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMethods, selectedPayment, onSelectPayment, onPay }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
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 [localPaymentImage, setLocalPaymentImage] = useState(null);
|
||||||
|
|
||||||
const methods = paymentMethods.length > 0 ? paymentMethods : getPaymentMethods(t);
|
const methods = paymentMethods.length > 0 ? paymentMethods : getPaymentMethods(t);
|
||||||
|
|
||||||
|
const selectedMethod = methods.find((m) => {
|
||||||
|
const methodName = normalizeText(m?.name);
|
||||||
|
const methodLabel = normalizeText(m?.label);
|
||||||
|
const methodId = normalizeText(m?.id);
|
||||||
|
const paymentKey = normalizeText(selectedPayment);
|
||||||
|
|
||||||
|
return methodName === paymentKey || methodLabel === paymentKey || methodId === paymentKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isHaramPayment =
|
||||||
|
normalizeText(selectedPayment).includes('haram') ||
|
||||||
|
normalizeText(selectedMethod?.name).includes('haram') ||
|
||||||
|
normalizeText(selectedMethod?.label).includes('haram') ||
|
||||||
|
normalizeText(selectedMethod?.id).includes('haram');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -380,7 +415,7 @@ export default function PaymentsPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
methods.map((method, index) => {
|
methods.map((method, index) => {
|
||||||
const optionId = method.id ?? method.name ?? `payment-method-${index}`;
|
const optionId = method.name ?? method.id ?? `payment-method-${index}`;
|
||||||
const isActive = method?.isActive === true || method?.active === true;
|
const isActive = method?.isActive === true || method?.active === true;
|
||||||
const isSelected = String(selectedPayment) === String(optionId);
|
const isSelected = String(selectedPayment) === String(optionId);
|
||||||
const Icon = method.icon || CreditCard;
|
const Icon = method.icon || CreditCard;
|
||||||
@ -399,9 +434,11 @@ export default function PaymentsPage() {
|
|||||||
: 'border-gray-200 bg-white hover:border-amber-300 cursor-pointer'
|
: 'border-gray-200 bg-white hover:border-amber-300 cursor-pointer'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className={`w-10 h-10 rounded-xl flex items-center justify-center shrink-0 ${
|
<div
|
||||||
|
className={`w-10 h-10 rounded-xl flex items-center justify-center shrink-0 ${
|
||||||
isSelected ? 'bg-amber-500 text-white' : 'bg-gray-100 text-gray-500'
|
isSelected ? 'bg-amber-500 text-white' : 'bg-gray-100 text-gray-500'
|
||||||
}`}>
|
}`}
|
||||||
|
>
|
||||||
<Icon className="w-5 h-5" />
|
<Icon className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
@ -413,9 +450,11 @@ export default function PaymentsPage() {
|
|||||||
{method.description}
|
{method.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<span className={`inline-block mt-1 text-[10px] font-medium px-2 py-0.5 rounded-full ${
|
<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 ? 'bg-amber-100 text-amber-700' : 'bg-gray-200 text-gray-500'
|
||||||
}`}>
|
}`}
|
||||||
|
>
|
||||||
{isActive ? t('active', { defaultValue: 'نشط' }) : t('inactive', { defaultValue: 'غير نشط' })}
|
{isActive ? t('active', { defaultValue: 'نشط' }) : t('inactive', { defaultValue: 'غير نشط' })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -431,11 +470,26 @@ export default function PaymentsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Upload receipt only for Haram */}
|
||||||
|
{isHaramPayment && (
|
||||||
|
<div className="px-6 py-5 border-b border-gray-100">
|
||||||
|
<label className="block text-sm font-bold text-gray-700 mb-3">
|
||||||
|
{t('payments.uploadReceipt', { defaultValue: 'صورة الوصل' })}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(e) => setLocalPaymentImage(e.target.files?.[0] || null)}
|
||||||
|
className="block w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-sm text-gray-700 file:mr-4 file:rounded-xl file:border-0 file:bg-amber-500 file:px-4 file:py-2 file:text-white hover:file:bg-amber-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Pay button */}
|
{/* Pay button */}
|
||||||
<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, selectedPayment)}
|
onClick={() => onPay(r, selectedPayment, localPaymentImage)}
|
||||||
disabled={payingId === r.id}
|
disabled={payingId === r.id}
|
||||||
whileHover={{ scale: 1.01 }}
|
whileHover={{ scale: 1.01 }}
|
||||||
whileTap={{ scale: 0.99 }}
|
whileTap={{ scale: 0.99 }}
|
||||||
|
|||||||
@ -672,15 +672,36 @@ export async function ownerConfirmReservation(id) {
|
|||||||
|
|
||||||
// ─── Payments ───
|
// ─── Payments ───
|
||||||
|
|
||||||
|
|
||||||
export async function payDeposit(data) {
|
export async function payDeposit(data) {
|
||||||
return apiFetch("/Reservations/PayDeposit/pay-deposit", {
|
const formData = new FormData();
|
||||||
method: "POST",
|
|
||||||
body: data,
|
formData.append(
|
||||||
});
|
"ReservationId",
|
||||||
|
String(data.reservationId ?? data.ReservationId ?? "")
|
||||||
|
);
|
||||||
|
|
||||||
|
formData.append(
|
||||||
|
"PaymentTypeId",
|
||||||
|
String(data.paymentTypeId ?? data.PaymentTypeId ?? "")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.comment) {
|
||||||
|
formData.append("Comment", data.comment);
|
||||||
|
} else {
|
||||||
|
formData.append("Comment", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMyTransaction() {
|
// صورة الوصل لطريقة الدفع Haram
|
||||||
return apiFetch("/Customer/GetMyTransaction");
|
if (data.paymentImage) {
|
||||||
|
formData.append("TransactionType", data.paymentImage);
|
||||||
|
formData.append("paymentImage", data.paymentImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiFetch("/Reservations/PayDeposit/pay-deposit", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Owner Contact & Stats ───
|
// ─── Owner Contact & Stats ───
|
||||||
|
|||||||
Reference in New Issue
Block a user