add payment for SaleProprite ,add commsion,fixed error handling fiexd final things
All checks were successful
Build frontend / build (push) Successful in 1m46s

This commit is contained in:
hamzaobed7
2026-08-18 01:24:18 +03:00
parent 5592d9fbd2
commit 3472dc1175
10 changed files with 793 additions and 1070 deletions

View File

@ -256,16 +256,7 @@ export default function ClientLayout({ children }) {
)}
</div>
{/* <motion.button
whileHover={{ scale: 1.1, rotate: 360 }}
whileTap={{ scale: 0.9 }}
onClick={() =>
changeLanguage(currentLanguage === "en" ? "ar" : "en")
}
className="flex items-center justify-center w-10 h-10 bg-gray-100 hover:bg-gray-200 rounded-full transition-all duration-200 ml-4"
>
<Globe className="w-5 h-5 text-gray-700" />
</motion.button> */}
{user && (
<div className="relative" ref={menuRef}>

View File

@ -383,7 +383,7 @@ export default function OwnerBookingsPage() {
const fetchData = async () => {
try {
const [response, rentProps] = await Promise.all([getOwnerReservationsByStatuses([0, 1, 2, 3, 4]), getRentProperties().catch(() => [])]);
const [response, rentProps] = await Promise.all([getOwnerReservationsByStatuses([0, 1, 2, 3, 4, 5]), getRentProperties().catch(() => [])]);
let rawData = response;
@ -419,8 +419,6 @@ export default function OwnerBookingsPage() {
propMap[rp.propertyInformation?.id] = propertyData;
});
console.log("PROP MAP:", propMap);
// ربط الـ reservation بالعقار عن طريق propertyId
const enriched = list.map((item) => {
if (item.propertyId && propMap[item.propertyId]) {
@ -430,8 +428,6 @@ export default function OwnerBookingsPage() {
return item;
});
console.log("ENRICHED BOOKINGS:", enriched);
const mappedBookings = enriched.map((item) => {
const property = item._prop || {};
@ -450,7 +446,8 @@ export default function OwnerBookingsPage() {
1: "pending",
2: "confirmed",
3: "completed",
4: "cancelled",
4: "منتهية",
5: "cancelled",
};
const statusKey = statusMap[item?.status] || "pending";

View File

@ -1731,7 +1731,6 @@ export default function AddPropertyPage() {
<div>
<h3 className="text-lg font-bold text-gray-900 mb-4">{t("addProperty.imagesTitle")}</h3>
<div
onClick={() => fileInputRef.current?.click()}
className={`border-2 border-dashed rounded-xl p-8 text-center cursor-pointer transition-all ${

View File

@ -18,30 +18,16 @@ import {
Trash2,
Eye,
Calendar,
TrendingUp,
Users,
FileText,
Image as ImageIcon,
Layers,
Sofa,
DoorOpen,
ImageIcon,
CheckCircle,
XCircle,
AlertCircle,
ChevronRight,
ChevronLeft,
Loader2,
Clock,
Wifi,
Zap,
Flame,
Droplets,
Cigarette,
Dog,
Music,
Warehouse,
Layers,
Sofa,
DoorOpen,
Wind,
Pencil,
Save,
X,
Star,
@ -52,10 +38,23 @@ import {
Store,
GraduationCap,
TreePine,
CreditCard,
Banknote, // تم إضافة Banknote هنا
} from "lucide-react";
import toast, { Toaster } from "react-hot-toast";
import AuthService from "../../services/AuthService";
import { buildRentPropertyPayload, getMyRentListings, getMySaleListings, editRentProperty, editSaleProperty, updateRentPropertyStatus, updateSalePropertyStatus, uploadPicture } from "../../utils/api";
import {
buildRentPropertyPayload,
getMyRentListings,
getMySaleListings,
editRentProperty,
editSaleProperty,
updateRentPropertyStatus,
updateSalePropertyStatus,
uploadPicture,
getPaymentTypes,
OwnerPaymentForSaleProperite, // تم إضافة استيراد الدالة هنا
} from "../../utils/api";
import { PropertyService } from "../../enums/PropertyService";
import { PropertyTerm } from "../../enums/PropertyTerm";
import Loading from "@/app/loading";
@ -97,6 +96,249 @@ const proximityI18nKeys = {
BusStation: "proximity.busStation",
};
const CommissionPaymentDialog = ({
isOpen,
property,
onClose,
selectedPayment,
onSelectPayment,
onConfirmPay,
payingId,
paymentMethods,
loadingPaymentMethods,
receiptImage,
onSetReceiptImage,
commissionDetails,
setCommissionDetails,
}) => {
const { t } = useTranslation();
if (!isOpen || !property) return null;
const commission = Number(property.commission || 0);
const selectedMethod = paymentMethods?.find((m) => String(m?.id ?? m?.name) === String(selectedPayment));
const isHaram = selectedMethod?.name?.toLowerCase?.() === "haram";
const hasSelectedMethod = !!selectedMethod;
const hasHaramDetails = commissionDetails?.fullName?.trim() && commissionDetails?.governorate?.trim() && commissionDetails?.phoneNumber?.trim();
const canPay = payingId !== property.id && hasSelectedMethod && (!isHaram || (!!receiptImage && !!hasHaramDetails));
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 px-4 py-8" onClick={onClose}>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="w-full max-w-2xl rounded-3xl bg-white shadow-2xl border border-gray-200 overflow-hidden"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="p-6 border-b border-gray-100">
<div className="flex items-start gap-4">
<div className="w-14 h-14 bg-amber-100 rounded-2xl flex items-center justify-center">
<DollarSign className="w-7 h-7 text-amber-600" />
</div>
<div className="flex-1">
<h3 className="text-xl font-bold text-gray-900">{property.title}</h3>
<p className="text-sm text-gray-500 mt-1">{t("commission")}</p>
</div>
</div>
</div>
{/* Commission amount */}
<div className="px-6 py-5 border-b border-gray-100">
<div className="text-center">
<p className="text-sm text-gray-500 mb-1">{t("commission")}</p>
<p className="text-4xl font-bold text-amber-600">
{commission.toLocaleString()} {t("syp")}
</p>
</div>
</div>
{/* Payment methods */}
<div className="px-6 py-5 border-b border-gray-100">
<p className="text-sm font-bold text-gray-700 mb-3">{t("paymentMethod")}</p>
{loadingPaymentMethods ? (
<div className="rounded-2xl border border-dashed border-gray-200 bg-gray-50 p-4 text-sm text-gray-600">
{t("loadingPaymentMethods", {
defaultValue: "جاري تحميل طرق الدفع...",
})}
</div>
) : !Array.isArray(paymentMethods) || paymentMethods.length === 0 ? (
<div className="rounded-2xl border border-dashed border-gray-200 bg-gray-50 p-4 text-sm text-gray-600">
{t("noPaymentMethodsAvailable", {
defaultValue: "لا توجد طرق دفع متاحة حالياً.",
})}
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{paymentMethods.map((method, index) => {
const isActive = method?.isActive === true || method?.active === true;
const optionId = method?.id ?? method?.name ?? `payment-method-${index}`;
const label =
method?.name ||
t("paymentMethod", {
defaultValue: "طريقة الدفع",
});
const isSelected = selectedPayment === optionId;
return (
<button
key={optionId}
type="button"
disabled={!isActive}
onClick={() => isActive && onSelectPayment(optionId)}
className={`relative flex items-start gap-3 p-4 rounded-2xl border-2 text-right transition-all ${
!isActive
? "border-gray-100 opacity-50 cursor-not-allowed"
: isSelected
? "border-amber-500 bg-amber-600 shadow-sm"
: "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 ${isSelected ? "bg-amber-500 text-white" : "bg-gray-100 text-gray-500"}`}>
<CreditCard className="w-5 h-5" />
</div>
<div className="min-w-0 flex-1">
<p className="text-sm font-bold text-gray-800">{label}</p>
<p className="text-xs text-gray-800 mt-0.5">
{isActive
? t("paymentMethodAvailable", {
defaultValue: "متاحة للاستعمال",
})
: t("paymentMethodUnavailable", {
defaultValue: "غير متاحة حالياً",
})}
</p>
</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>
{/* Haram payment details */}
{isHaram && (
<div className="px-6 py-5 border-b border-gray-100">
<p className="text-sm font-bold text-gray-700 mb-3">بيانات الدفع</p>
<div className="space-y-2 text-[20px]">
{commissionDetails.split("\n").map((line, idx) => (
<div key={idx}>{line}</div>
))}
</div>
</div>
)}
{/* Receipt */}
{isHaram && (
<div className="px-6 py-5 border-b border-gray-100">
<p className="text-sm font-bold text-gray-700 mb-3">
{t("uploadReceipt", {
defaultValue: "إيصال الدفع",
})}
</p>
{receiptImage ? (
<div className="relative rounded-2xl overflow-hidden border-2 border-amber-200 bg-amber-50">
<img src={URL.createObjectURL(receiptImage)} alt="receipt" className="w-full h-48 object-contain" />
<button type="button" onClick={() => onSetReceiptImage(null)} className="absolute top-2 left-2 w-8 h-8 bg-red-500 text-white rounded-full flex items-center justify-center">
<X className="w-4 h-4" />
</button>
<div className="p-3 text-center">
<p className="text-xs text-gray-500 truncate">{receiptImage.name}</p>
</div>
</div>
) : (
<label className="flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-300 rounded-2xl bg-gray-50 cursor-pointer">
<ImageIcon className="w-10 h-10 text-gray-400 mb-2" />
<p className="text-sm text-gray-500">
{t("tapToUploadReceipt", {
defaultValue: "اضغط لرفع صورة الإيصال",
})}
</p>
<input
type="file"
accept="image/jpeg,image/png,image/jpg"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024) {
toast.error(
t("fileTooLarge", {
defaultValue: "الصورة كبيرة جداً، الحد الأقصى 5 ميجا",
}),
);
return;
}
onSetReceiptImage(file);
}}
/>
</label>
)}
</div>
)}
{/* Pay */}
<div className="px-6 py-5">
<motion.button
type="button"
onClick={() => onConfirmPay(property)}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
className="w-full bg-amber-500 hover:bg-amber-600 disabled:bg-amber-300 text-white font-bold py-4 px-6 rounded-2xl text-lg transition-all flex items-center justify-center gap-3"
>
{payingId === property.id ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
{t("processingPayment")}
</>
) : (
<>
<Banknote className="w-5 h-5" />
{t("payCommission", {
defaultValue: "دفع الكمسيون",
})}
</>
)}
</motion.button>
</div>
<div className="p-4 flex justify-end">
<button type="button" onClick={onClose} className="px-5 py-2.5 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100">
{t("close")}
</button>
</div>
</motion.div>
</div>
);
};
const DeleteConfirmationModal = ({ isOpen, onClose, onConfirm, propertyTitle }) => {
const { t } = useTranslation();
if (!isOpen) return null;
@ -503,9 +745,11 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
nearbyUniversity: "",
nearbyPark: "",
nearbyMall: "",
nearbyMosque: "",
purpose: "rent",
currencyId: 1,
dailyPrice: 0,
commission: "",
monthlyPrice: 0,
deposit: 0,
rentType: "monthly",
@ -591,6 +835,7 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
serviceDetails,
terms,
customTerms: details.customTerms || [],
nearbyMosque: prox.nearbyMosque || prox.neaebymosque || "",
nearbySchool: prox.School ?? prox.school ?? prox.nearbySchool ?? "",
nearbyHospital: prox.Hospital ?? prox.hospital ?? prox.nearbyHospital ?? "",
nearbyRestaurant: prox.Restaurant ?? prox.restaurant ?? prox.nearbyRestaurant ?? "",
@ -599,6 +844,7 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
nearbyMall: prox.Mall ?? prox.mall ?? prox.nearbyMall ?? "",
purpose: property.purpose || "rent",
currencyId: property.currencyId || 1,
commission: property.commission || prox.commission || "",
...(property.purpose === "rent"
? {
dailyPrice: property.dailyPrice || 0,
@ -791,7 +1037,7 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
<select
value={formData.propertyType}
onChange={(e) => handleChange("propertyType", e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
className="w-full px-3 bg-[#0f172a] text-white py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
>
<option value="apartment">{t("buildingType.apartment")}</option>
<option value="villa">{t("buildingType.villa")}</option>
@ -1025,6 +1271,7 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
{ id: "nearbyUniversity", label: t("university") },
{ id: "nearbyPark", label: t("park") },
{ id: "nearbyMall", label: t("mall") },
{ id: "nearbyMosque", label: t("neaebymosque") },
].map((field) => (
<div key={field.id}>
<label className="block text-sm font-medium text-gray-700 mb-1">{field.label}</label>
@ -1077,12 +1324,23 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">{t("commission")}</label>
<input
type="number"
min="0"
value={formData.commission}
onChange={(e) => handleChange("commission", Number(e.target.value))}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">{t("rentType")}</label>
<select
value={formData.rentType}
onChange={(e) => handleChange("rentType", e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
className="w-full px-3 bg-[#0f172a] text-white py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
>
<option value="daily">{t("daily")}</option>
<option value="monthly">{t("monthly")}</option>
@ -1094,7 +1352,7 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
<select
value={formData.allowedPaymentPeriod}
onChange={(e) => handleChange("allowedPaymentPeriod", e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
className="w-full px-3 py-2 border bg-[#0f172a] text-white border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
>
<option value="">{t("selectPeriod")}</option>
<option value="1.00:00:00">{t("daily")}</option>
@ -1155,7 +1413,17 @@ export default function OwnerPropertiesPage() {
const [isLoading, setIsLoading] = useState(true);
const [showAddMenu, setShowAddMenu] = useState(false);
const [activeTab, setActiveTab] = useState("rent");
const [commissionPayment, setCommissionPayment] = useState({
open: false,
property: null,
});
const [selectedCommissionPayment, setSelectedCommissionPayment] = useState(null);
const [commissionPaymentMethods, setCommissionPaymentMethods] = useState([]);
const [loadingCommissionPaymentMethods, setLoadingCommissionPaymentMethods] = useState(false);
const [commissionReceiptImage, setCommissionReceiptImage] = useState(null);
const [commissionPayingId, setCommissionPayingId] = useState(null);
const [commissionDetails, setCommissionDetails] = useState("");
const [deleteModal, setDeleteModal] = useState({
isOpen: false,
property: null,
@ -1186,6 +1454,29 @@ export default function OwnerPropertiesPage() {
}
}, [router]);
const openCommissionPayment = async (property) => {
setCommissionPayment({
open: true,
property,
});
setSelectedCommissionPayment(null);
setCommissionReceiptImage(null);
setLoadingCommissionPaymentMethods(true);
try {
const result = await getPaymentTypes();
setCommissionDetails(result[0].details);
setCommissionPaymentMethods(result?.value || result?.data || result || []);
} catch (error) {
toast.error(error?.message || "فشل تحميل طرق الدفع");
} finally {
setLoadingCommissionPaymentMethods(false);
}
};
const loadProperties = async () => {
const authUser = AuthService.getUser();
const userId = authUser?.id;
@ -1275,6 +1566,7 @@ export default function OwnerPropertiesPage() {
monthlyPrice: item.monthlyRent || 0,
salePrice: item.price || 0,
deposit: item.deposit || 0,
commission: item.commission || details.commission || 0,
location: info.address || "",
bedrooms: info.numberOfBedRooms || 0,
bathrooms: info.numberOfBathRooms || 0,
@ -1299,6 +1591,7 @@ export default function OwnerPropertiesPage() {
rating: item.rating || 0,
currencyId: item.currencyId,
_raw: item,
status2: item.status,
};
});
@ -1314,6 +1607,7 @@ export default function OwnerPropertiesPage() {
return {};
}
})();
const apiBase = typeof window !== "undefined" ? process.env.NEXT_PUBLIC_API_URL || "https://45.93.137.91.nip.io/api" : "";
const raw = Array.isArray(info.images) ? info.images : [];
return {
@ -1348,6 +1642,7 @@ export default function OwnerPropertiesPage() {
monthlyPrice: 0,
salePrice: item.price || 0,
deposit: 0,
commission: item.commission || details.commission || 0,
location: info.address || "",
bedrooms: info.numberOfBedRooms || 0,
bathrooms: info.numberOfBathRooms || 0,
@ -1372,6 +1667,7 @@ export default function OwnerPropertiesPage() {
rating: item.rating || 0,
currencyId: item.currencyId,
_raw: item,
status2: item.status,
};
});
@ -1531,6 +1827,7 @@ export default function OwnerPropertiesPage() {
try {
if (prop.purpose === "rent") {
await updateRentPropertyStatus(prop.id, statusCode);
await updateRentPropertyStatus(prop.id, statusCode);
} else {
await updateSalePropertyStatus(prop.id, statusCode);
}
@ -1551,10 +1848,47 @@ export default function OwnerPropertiesPage() {
transition: { duration: 0.5 },
};
const handleCommissionPayment = async (property) => {
if (!property) return;
if (!selectedCommissionPayment) {
toast.error("اختر طريقة الدفع");
return;
}
const selectedMethod = commissionPaymentMethods.find((m) => String(m?.id ?? m?.name) === String(selectedCommissionPayment));
const isHaram = selectedMethod?.name?.toLowerCase?.() === "haram";
if (isHaram && !commissionReceiptImage) {
toast.error("يرجى رفع إيصال الدفع");
return;
}
setCommissionPayingId(property.id);
try {
await OwnerPaymentForSaleProperite(property.id, selectedCommissionPayment, 1, commissionReceiptImage);
toast.success("تم إرسال طلب دفع الكمسيون");
await loadProperties();
setCommissionPayment({
open: false,
property: null,
});
setCommissionReceiptImage(null);
setSelectedCommissionPayment(null);
} catch (error) {
console.error("COMMISSION PAYMENT ERROR:", error);
toast.error(error?.message || "فشل دفع الكمسيون");
} finally {
setCommissionPayingId(null);
}
};
if (isLoading) {
return (
<Loading/>
);
return <Loading />;
}
return (
@ -1604,7 +1938,7 @@ export default function OwnerPropertiesPage() {
<div className="p-2">
<Link
href="/owner/properties/add?purpose=rent"
className="flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-[#0f172a] rounded-lg transition-colors"
className="flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-[#0f172a] hover:text-white rounded-lg transition-colors"
onClick={() => setShowAddMenu(false)}
>
<div className="w-8 h-8 bg-green-100 rounded-lg flex items-center justify-center">
@ -1618,7 +1952,7 @@ export default function OwnerPropertiesPage() {
<Link
href="/owner/properties/add?purpose=sale"
className="flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-[#0f172a] rounded-lg transition-colors"
className="flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-[#0f172a] hover:text-white rounded-lg transition-colors"
onClick={() => setShowAddMenu(false)}
>
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
@ -1684,196 +2018,233 @@ export default function OwnerPropertiesPage() {
initial="initial"
animate="animate"
transition={{ delay: index * 0.1 }}
className="bg-white rounded-2xl shadow-sm hover:shadow-md transition-all overflow-hidden border border-gray-200"
className="bg-white rounded-2xl shadow-sm hover:shadow-md transition-all overflow-hidden border border-gray-200 flex flex-col justify-between"
>
<div className="relative aspect-[16/10] bg-gray-100 overflow-hidden">
{property.images && property.images.length > 0 ? (
<img src={property.images[0]} alt={property.title} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center">
<ImageIcon className="w-12 h-12 text-gray-400" />
</div>
)}
<div className="absolute top-2 right-2 flex gap-1">
<span
className={`px-2 py-0.5 rounded-md text-xs font-medium shadow-sm backdrop-blur-sm ${
property.status === "available" ? "bg-white/90 text-green-700" : property.status === "notAvailable" ? "bg-red-50/90 text-red-700" : "bg-white/90 text-yellow-700"
}`}
>
{property.status === "notAvailable" ? t("notAvailable") : property.status === "available" ? t("available") : t("postponed")}
</span>
{property.purpose === "rent" && property.furnished !== undefined && (
<span className={`px-2 py-0.5 rounded-md text-xs font-medium shadow-sm backdrop-blur-sm ${property.furnished ? "bg-white/90 text-amber-700" : "bg-white/90 text-gray-600"}`}>
{property.furnished ? t("furnished") : t("unfurnished")}
</span>
)}
</div>
<div className="absolute top-2 left-2">
<span className="px-2 py-0.5 rounded-md text-xs font-medium bg-white/90 text-amber-700 shadow-sm backdrop-blur-sm">
{property.propertyTypeLabel ||
(property.propertyType === "apartment"
? t("buildingType.apartment")
: property.propertyType === "villa"
? t("buildingType.villa")
: property.propertyType === "sweet"
? t("buildingType.sweet")
: property.propertyType === "room"
? t("buildingType.room")
: property.propertyType === "studio"
? t("buildingType.studio")
: property.propertyType === "office"
? t("buildingType.office")
: property.propertyType === "farms"
? t("buildingType.farms")
: property.propertyType === "shop"
? t("buildingType.shop")
: property.propertyType === "warehouse"
? t("buildingType.warehouse")
: t("offer"))}
</span>
</div>
</div>
<div className="p-3">
<div className="flex items-start justify-between mb-1">
<h3 className="font-bold text-gray-900 truncate flex-1 ml-2">{property.title}</h3>
{property.rating > 0 && (
<div className="flex items-center gap-0.5 text-amber-600 shrink-0">
<Star className="w-3 h-3 fill-amber-500" />
<span className="text-xs font-medium">{Number(property.rating).toFixed(1)}</span>
<div>
<div className="relative aspect-[16/10] bg-gray-100 overflow-hidden">
{property.images && property.images.length > 0 ? (
<img src={property.images[0]} alt={property.title} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center">
<ImageIcon className="w-12 h-12 text-gray-400" />
</div>
)}
</div>
<div className="flex items-center gap-1 text-gray-500 text-xs mb-2">
<MapPin className="w-3 h-3" />
<span className="truncate">{property.address || property.location || t("locationUnknown")}</span>
</div>
{property.description && <p className="text-xs text-gray-600 leading-relaxed mb-2 line-clamp-2">{property.description}</p>}
<div className="flex flex-wrap items-center gap-2 text-xs text-gray-600 mb-2">
{property.bedrooms > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Bed className="w-3 h-3" />
<span>{property.bedrooms}</span>
</div>
)}
{property.bathrooms > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Bath className="w-3 h-3" />
<span>{property.bathrooms}</span>
</div>
)}
{property.area > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Square className="w-3 h-3" />
<span>
{property.area}
{t("sqm")}
</span>
</div>
)}
{property.floor > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Layers className="w-3 h-3" />
<span>
{t("floor")} {property.floor}
</span>
</div>
)}
{property.salons > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Sofa className="w-3 h-3" />
<span>{property.salons}</span>
</div>
)}
{property.balconies > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<DoorOpen className="w-3 h-3" />
<span>{property.balconies}</span>
</div>
)}
</div>
<div className="flex justify-between items-center pt-2 border-t border-gray-100">
<div>
{property.purpose === "rent" ? (
<div>
{property.monthlyPrice > 0 && (
<span className="text-base font-bold text-amber-600">
{Number(property.monthlyPrice).toLocaleString()}
<span className="text-xs text-gray-500 font-normal mr-1">{t("perMonth")}</span>
</span>
)}
{property.dailyPrice > 0 && !property.monthlyPrice && (
<span className="text-base font-bold text-amber-600">
{Number(property.dailyPrice).toLocaleString()}
<span className="text-xs text-gray-500 font-normal mr-1">{t("perDay")}</span>
</span>
)}
{property.deposit > 0 && <div className="text-[10px] text-gray-400">{t("depositAmount", { amount: Number(property.deposit).toLocaleString() })}</div>}
</div>
) : (
<span className="text-base font-bold text-blue-600">
{Number(property.salePrice).toLocaleString()} {t("syp")}
</span>
)}
</div>
<div className="flex gap-1">
{property.status === "notAvailable" ? (
<button
onClick={() =>
setDeactivateModal({
isOpen: true,
property,
isActivating: true,
})
}
className="p-1.5 hover:bg-green-50 rounded-lg transition-colors group"
title={t("activateProperty")}
>
<CheckCircle className="w-3.5 h-3.5 text-gray-400 group-hover:text-green-600" />
</button>
) : (
<button
onClick={() =>
setDeactivateModal({
isOpen: true,
property,
isActivating: false,
})
}
className="p-1.5 hover:bg-red-50 rounded-lg transition-colors group"
title={t("deactivateProperty")}
>
<XCircle className="w-3.5 h-3.5 text-gray-400 group-hover:text-red-600" />
</button>
)}
<button
onClick={() => setViewModal({ isOpen: true, property })}
className="px-3 py-1.5 bg-gray-50 hover:bg-blue-50 border border-gray-200 hover:border-blue-200 rounded-lg transition-all group flex items-center gap-1.5"
title={t("viewDetails")}
<div className="absolute top-2 right-2 flex gap-1">
<span
className={`px-2 py-0.5 rounded-md text-xs font-medium shadow-sm backdrop-blur-sm ${
property.status === "available" ? "bg-white/90 text-green-700" : property.status === "notAvailable" ? "bg-red-50/90 text-red-700" : "bg-white/90 text-yellow-700"
}`}
>
<Eye className="w-3.5 h-3.5 text-gray-400 group-hover:text-blue-600" />
<span className="text-xs text-gray-500 group-hover:text-blue-600 font-medium">{t("view")}</span>
</button>
<button onClick={() => setEditModal({ isOpen: true, property })} className="p-1.5 hover:bg-amber-50 rounded-lg transition-colors group" title={t("editProperty")}>
<Edit className="w-3.5 h-3.5 text-gray-400 group-hover:text-amber-600" />
</button>
<button onClick={() => setDeleteModal({ isOpen: true, property })} className="p-1.5 hover:bg-red-50 rounded-lg transition-colors group" title={t("deleteProperty")}>
<Trash2 className="w-3.5 h-3.5 text-gray-400 group-hover:text-red-600" />
</button>
{property.status === "notAvailable" ? t("notAvailable") : property.status === "available" ? t("available") : t("postponed")}
</span>
{property.purpose === "rent" && property.furnished !== undefined && (
<span className={`px-2 py-0.5 rounded-md text-xs font-medium shadow-sm backdrop-blur-sm ${property.furnished ? "bg-white/90 text-amber-700" : "bg-white/90 text-gray-600"}`}>
{property.furnished ? t("furnished") : t("unfurnished")}
</span>
)}
</div>
<div className="absolute top-2 left-2">
<span className="px-2 py-0.5 rounded-md text-xs font-medium bg-white/90 text-amber-700 shadow-sm backdrop-blur-sm">
{property.propertyTypeLabel ||
(property.propertyType === "apartment"
? t("buildingType.apartment")
: property.propertyType === "villa"
? t("buildingType.villa")
: property.propertyType === "sweet"
? t("buildingType.sweet")
: property.propertyType === "room"
? t("buildingType.room")
: property.propertyType === "studio"
? t("buildingType.studio")
: property.propertyType === "office"
? t("buildingType.office")
: property.propertyType === "farms"
? t("buildingType.farms")
: property.propertyType === "shop"
? t("buildingType.shop")
: property.propertyType === "warehouse"
? t("buildingType.warehouse")
: t("offer"))}
</span>
</div>
</div>
<div className="p-3">
<div className="flex items-start justify-between mb-1">
<h3 className="font-bold text-gray-900 truncate flex-1 ml-2">{property.title}</h3>
{property.rating > 0 && (
<div className="flex items-center gap-0.5 text-amber-600 shrink-0">
<Star className="w-3 h-3 fill-amber-500" />
<span className="text-xs font-medium">{Number(property.rating).toFixed(1)}</span>
</div>
)}
</div>
<div className="flex items-center gap-1 text-gray-500 text-xs mb-2">
<MapPin className="w-3 h-3" />
<span className="truncate">{property.address || property.location || t("locationUnknown")}</span>
</div>
{property.description && <p className="text-xs text-gray-600 leading-relaxed mb-2 line-clamp-2">{property.description}</p>}
<div className="flex flex-wrap items-center gap-2 text-xs text-gray-600 mb-2">
{property.bedrooms > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Bed className="w-3 h-3" />
<span>{property.bedrooms}</span>
</div>
)}
{property.bathrooms > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Bath className="w-3 h-3" />
<span>{property.bathrooms}</span>
</div>
)}
{property.area > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Square className="w-3 h-3" />
<span>
{property.area}
{t("sqm")}
</span>
</div>
)}
{property.floor > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Layers className="w-3 h-3" />
<span>
{t("floor")} {property.floor}
</span>
</div>
)}
{property.salons > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<Sofa className="w-3 h-3" />
<span>{property.salons}</span>
</div>
)}
{property.balconies > 0 && (
<div className="flex items-center gap-0.5 bg-gray-50 px-2 py-0.5 rounded-md">
<DoorOpen className="w-3 h-3" />
<span>{property.balconies}</span>
</div>
)}
</div>
<div className="flex justify-between items-center pt-2 border-t border-gray-100">
<div>
{property.purpose === "rent" ? (
<div>
{property.monthlyPrice > 0 && (
<span className="text-base font-bold text-amber-600">
{Number(property.monthlyPrice).toLocaleString()}
<span className="text-xs text-gray-500 font-normal mr-1">{t("perMonth")}</span>
</span>
)}
{property.dailyPrice > 0 && !property.monthlyPrice && (
<span className="text-base font-bold text-amber-600">
{Number(property.dailyPrice).toLocaleString()}
<span className="text-xs text-gray-500 font-normal mr-1">{t("perDay")}</span>
</span>
)}
{property.deposit > 0 && <div className="text-[10px] text-gray-400">{t("depositAmount", { amount: Number(property.deposit).toLocaleString() })}</div>}
</div>
) : (
<span className="text-base font-bold text-blue-600">
{Number(property.salePrice).toLocaleString()} {t("syp")}
</span>
)}
</div>
<div className="flex gap-1">
{property.status === "notAvailable" ? (
<button
onClick={() =>
setDeactivateModal({
isOpen: true,
property,
isActivating: true,
})
}
className="p-1.5 hover:bg-green-50 rounded-lg transition-colors group"
title={t("activateProperty")}
>
<CheckCircle className="w-3.5 h-3.5 text-gray-400 group-hover:text-green-600" />
</button>
) : (
<button
onClick={() =>
setDeactivateModal({
isOpen: true,
property,
isActivating: false,
})
}
className="p-1.5 hover:bg-red-50 rounded-lg transition-colors group"
title={t("deactivateProperty")}
>
<XCircle className="w-3.5 h-3.5 text-gray-400 group-hover:text-red-600" />
</button>
)}
<button
onClick={() => setViewModal({ isOpen: true, property })}
className="px-3 py-1.5 bg-gray-50 hover:bg-blue-50 border border-gray-200 hover:border-blue-200 rounded-lg transition-all group flex items-center gap-1.5"
title={t("viewDetails")}
>
<Eye className="w-3.5 h-3.5 text-gray-400 group-hover:text-blue-600" />
<span className="text-xs text-gray-500 group-hover:text-blue-600 font-medium">{t("view")}</span>
</button>
<button onClick={() => setEditModal({ isOpen: true, property })} className="p-1.5 hover:bg-amber-50 rounded-lg transition-colors group" title={t("editProperty")}>
<Edit className="w-3.5 h-3.5 text-gray-400 group-hover:text-amber-600" />
</button>
<button onClick={() => setDeleteModal({ isOpen: true, property })} className="p-1.5 hover:bg-red-50 rounded-lg transition-colors group" title={t("deleteProperty")}>
<Trash2 className="w-3.5 h-3.5 text-gray-400 group-hover:text-red-600" />
</button>
</div>
</div>
</div>
</div>
{property.purpose === "sale" && Number(property.status2) === 0 && (
<button
onClick={() => openCommissionPayment(property)}
className="w-full h-10 bg-amber-600 hover:bg-amber-700 text-white flex items-center justify-center gap-2 font-medium transition-colors"
>
<CreditCard className="w-4 h-4" />
دفع الكمسيون
</button>
)}
{property.purpose === "sale" && Number(property.status2) === 1 && (
<div className="w-full h-10 bg-amber-50 text-amber-700 flex items-center justify-center font-medium">قيد الانتظار</div>
)}
{property.purpose === "sale" && Number(property.status2) === 2 && <div className="w-full h-10 bg-green-50 text-green-700 flex items-center justify-center font-medium">تم التأكيد</div>}
</motion.div>
))}
</div>
)}
</div>
<CommissionPaymentDialog
isOpen={commissionPayment.open}
property={commissionPayment.property}
onClose={() =>
setCommissionPayment({
open: false,
property: null,
})
}
selectedPayment={selectedCommissionPayment}
onSelectPayment={setSelectedCommissionPayment}
onConfirmPay={handleCommissionPayment}
payingId={commissionPayingId}
paymentMethods={commissionPaymentMethods}
loadingPaymentMethods={loadingCommissionPaymentMethods}
receiptImage={commissionReceiptImage}
onSetReceiptImage={setCommissionReceiptImage}
commissionDetails={commissionDetails}
setCommissionDetails={setCommissionDetails}
/>
</div>
);
}

View File

@ -692,7 +692,7 @@ export default function PropertiesPage() {
const filteredProperties = properties
// العقارات يلي 0 ما تنعرض
.filter((p) => p.purpose === purposeTab)
.filter((p) => p.purpose === "rent" || p.status === 1)
// .filter((p) => p.purpose === "rent" || p.status === 1)
.filter((property) => {
if (filters.search && !property.title.includes(filters.search) && !property.description.includes(filters.search)) {
return false;
@ -788,9 +788,9 @@ export default function PropertiesPage() {
<div className={viewMode === "grid" ? "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" : "space-y-4"}>
{/* شرط مشان ينعرض او لا */}
{filteredProperties.map((property) =>
property.purpose === "rent" || property.status === 1 ? (
// property.purpose === "rent" || property.status === 1 ? (
<PropertyCard key={property.id} property={property} viewMode={viewMode} type={property.purpose} onLoginRequired={() => setShowLoginDialog(true)} />
) : null,
// ) : null,
)}
</div>

View File

@ -433,11 +433,15 @@ export default function PropertyDetailsPage({ type }) {
const propInfoId = property._raw?.propertyInformationId || property.id;
const startDate = new Date(selectedStart + "T00:00:00.000").toISOString();
const endDate = new Date(selectedEnd + "T00:00:00.000").toISOString();
await bookReservation(propInfoId, startDate, endDate);
await bookReservation(propInfoId, startDate, endDate);
setBookingSuccess(true);
toast.success(t("bookingSentSuccess"));
} catch (err) {
setBookingError(err.message || t("bookingFailed"));
if (err.message=="API 400: Cannot book a reservation for an inactive property."){
setBookingError("لا يمكنك حجز هذا العقار لانه غير نشط" || t("bookingFailed"));
}
} finally {
setBookingLoading(false);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,6 @@
import { body } from "framer-motion/client";
import AuthService from "../services/AuthService";
import { frameData } from "framer-motion";
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "https://45.93.137.91.nip.io/api";
const REPORT_API_BASE = process.env.NEXT_PUBLIC_REPORT_API_URL || "http://45.93.137.91/api";
@ -364,16 +366,38 @@ export async function getSalePropertyById(id) {
}
export async function updateRentPropertyStatus(id, status) {
return apiFetch(`/RentProperties/UpdateStatus/${id}`, {
return apiFetch(`/RentProperties/ChangeActivityStatus/${id}`, {
method: "PUT",
body: { status },
body: status ,
});
}
export async function updateSalePropertyStatus(id, status) {
return apiFetch(`/SaleProperties/UpdateStatus/${id}`, {
return apiFetch(`/SaleProperties/ChangeActivityStatus/${id}`, {
method: "PUT",
body: { status },
body: status ,
});
}
export async function OwnerPaymentForSaleProperite(
salePropertyId,
PaymentTypeId,
TransactionType,
paymentImage
) {
const formData = new FormData();
formData.append("salePropertyId", String(salePropertyId));
formData.append("PaymentTypeId", String(PaymentTypeId));
formData.append("TransactionType", String(TransactionType));
if (paymentImage instanceof File) {
formData.append("paymentImage", paymentImage);
}
return apiFetch("/SaleProperties/PaySaleFee", {
method: "POST",
body: formData,
});
}

View File

@ -1,5 +1,6 @@
import { initializeApp, getApps } from "firebase/app";
import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { Cookies } from 'js-cookie';
const firebaseConfig = {
apiKey: "AIzaSyBZV7KBLRJSTApahfrO8lBesmIM3zNRSaY",
@ -45,8 +46,7 @@ export async function requestNotificationPermission() {
// Send token to backend
if (token) {
try {
const authToken = localStorage.getItem("auth_token");
const authToken = Cookies.get(auth_token);
if (authToken) {
const apiBase =
process.env.NEXT_PUBLIC_API_URL ||
@ -66,7 +66,6 @@ export async function requestNotificationPermission() {
}
} catch (err) {}
}
return token;
} catch (err) {
return null;

View File

@ -17,7 +17,6 @@ firebase.initializeApp({
//ننشئ رسالة
const messaging = firebase.messaging();
// Handle background messages
messaging.onBackgroundMessage((payload) => {
("[FCM SW] Background message:", payload);