Added city and document type
All checks were successful
Build frontend / build (push) Successful in 54s

This commit is contained in:
Rahaf
2026-07-04 02:53:34 +03:00
parent 2d2e2427a2
commit 3bef951d5c
5 changed files with 196 additions and 47 deletions

19
app/enums/DocumentType.js Normal file
View File

@ -0,0 +1,19 @@
/**
* Document type enum for owner property submission.
* Values are intentionally English keys so the UI can store normalized identifiers.
*/
const DocumentType = Object.freeze({
PASSPORT: 'Passport',
IDCARD: 'IDCard',
BOTH: 'Both',
});
const DocumentTypeLabels = Object.freeze({
[DocumentType.PASSPORT]: 'جواز سفر',
[DocumentType.IDCARD]: 'بطاقة شخصية',
[DocumentType.BOTH]: 'كلاهما',
});
const DocumentTypeList = Object.freeze(Object.values(DocumentType));
export { DocumentType, DocumentTypeLabels, DocumentTypeList };

39
app/enums/Governorate.js Normal file
View File

@ -0,0 +1,39 @@
/**
* Syrian governorates enum using English normalized values.
* Labels are provided separately for Arabic display.
*/
const Governorate = Object.freeze({
DAMASCUS: 'Damascus',
ALEPPO: 'Aleppo',
LATAKIA: 'Latakia',
HAMA: 'Hama',
TARTOUS: 'Tartous',
DEIR_EZZOR: 'DeirEzzor',
RAQQA: 'Raqqa',
IDLIB: 'Idlib',
DRAA: 'Daraa',
SUWEIDA: 'Suweida',
HASAKAH: 'Hasakah',
QUNEITRA: 'Quneitra',
RURAL_DAMASCUS: 'RuralDamascus',
});
const GovernorateLabels = Object.freeze({
[Governorate.DAMASCUS]: 'دمشق',
[Governorate.ALEPPO]: 'حلب',
[Governorate.LATAKIA]: 'اللاذقية',
[Governorate.HAMA]: 'حماة',
[Governorate.TARTOUS]: 'طرطوس',
[Governorate.DEIR_EZZOR]: 'دير الزور',
[Governorate.RAQQA]: 'الرقة',
[Governorate.IDLIB]: 'إدلب',
[Governorate.DRAA]: 'درعا',
[Governorate.SUWEIDA]: 'السويداء',
[Governorate.HASAKAH]: 'الحسكة',
[Governorate.QUNEITRA]: 'القنيطرة',
[Governorate.RURAL_DAMASCUS]: 'ريف دمشق',
});
const GovernorateList = Object.freeze(Object.values(Governorate));
export { Governorate, GovernorateLabels, GovernorateList };

View File

@ -18,6 +18,8 @@ export { RentPropertyType, RentPropertyTypeLabels } from './RentPropertyType';
export { RentType, RentTypeLabels } from './RentType'; export { RentType, RentTypeLabels } from './RentType';
export { PropertyService, PropertyServiceLabels, PropertyServicesList } from './PropertyService'; export { PropertyService, PropertyServiceLabels, PropertyServicesList } from './PropertyService';
export { PropertyTerm, PropertyTermLabels, PropertyTermsList } from './PropertyTerm'; export { PropertyTerm, PropertyTermLabels, PropertyTermsList } from './PropertyTerm';
export { DocumentType, DocumentTypeLabels, DocumentTypeList } from './DocumentType';
export { Governorate, GovernorateLabels, GovernorateList } from './Governorate';
export { Currency, CurrencyLabels, CurrencySymbols } from './Currency'; export { Currency, CurrencyLabels, CurrencySymbols } from './Currency';
export { DeviceType } from './DeviceType'; export { DeviceType } from './DeviceType';
export { SalePropertiesStatus, SalePropertiesStatusLabels } from './SalePropertiesStatus'; export { SalePropertiesStatus, SalePropertiesStatusLabels } from './SalePropertiesStatus';

View File

@ -1149,6 +1149,11 @@ const resources = {
"addProperty.furnishedStatus": "Property Condition", "addProperty.furnishedStatus": "Property Condition",
"addProperty.furnished": "Furnished", "addProperty.furnished": "Furnished",
"addProperty.unfurnished": "Unfurnished", "addProperty.unfurnished": "Unfurnished",
"addProperty.documentTypeLabel": "Document Type",
"addProperty.cityLabel": "Governorate",
"documentType.passport": "Passport",
"documentType.idCard": "ID Card",
"documentType.both": "Both",
"addProperty.additionalDescription": "Additional Description (optional)", "addProperty.additionalDescription": "Additional Description (optional)",
"addProperty.servicesLabel": "Available Services", "addProperty.servicesLabel": "Available Services",
"addProperty.serviceDetailPlaceholder": "Service details (e.g., in all rooms)...", "addProperty.serviceDetailPlaceholder": "Service details (e.g., in all rooms)...",
@ -2786,6 +2791,11 @@ const resources = {
"addProperty.furnishedStatus": "حالة العقار", "addProperty.furnishedStatus": "حالة العقار",
"addProperty.furnished": "مفروش", "addProperty.furnished": "مفروش",
"addProperty.unfurnished": "غير مفروش", "addProperty.unfurnished": "غير مفروش",
"addProperty.documentTypeLabel": "نوع الوثيقة",
"addProperty.cityLabel": "المحافظة",
"documentType.passport": "جواز سفر",
"documentType.idCard": "بطاقة شخصية",
"documentType.both": "كلاهما",
"addProperty.additionalDescription": "وصف إضافي (اختياري)", "addProperty.additionalDescription": "وصف إضافي (اختياري)",
"addProperty.servicesLabel": "الخدمات المتوفرة", "addProperty.servicesLabel": "الخدمات المتوفرة",
"addProperty.serviceDetailPlaceholder": "تفاصيل الخدمة (مثال: في جميع الغرف)...", "addProperty.serviceDetailPlaceholder": "تفاصيل الخدمة (مثال: في جميع الغرف)...",

View File

@ -64,6 +64,11 @@ import {
PropertyTerm, PropertyTerm,
PropertyTermLabels, PropertyTermLabels,
PropertyTermsList, PropertyTermsList,
DocumentType,
DocumentTypeList,
Governorate,
GovernorateLabels,
GovernorateList,
Currency, Currency,
CurrencyLabels CurrencyLabels
} from '../../../enums'; } from '../../../enums';
@ -84,6 +89,7 @@ export default function AddPropertyPage() {
propertyType: 'apartment', propertyType: 'apartment',
furnished: false, furnished: false,
documentType: DocumentType.PASSPORT,
bedrooms: 1, bedrooms: 1,
bathrooms: 1, bathrooms: 1,
@ -118,7 +124,7 @@ export default function AddPropertyPage() {
deposit: '', deposit: '',
allowedPaymentPeriod: '', allowedPaymentPeriod: '',
city: '', city: Governorate.DAMASCUS,
district: '', district: '',
address: '', address: '',
lat: null, lat: null,
@ -206,6 +212,19 @@ export default function AddPropertyPage() {
{ id: 'both', label: t('addProperty.dailyAndMonthly'), icon: Calendar }, { id: 'both', label: t('addProperty.dailyAndMonthly'), icon: Calendar },
].filter(Boolean); ].filter(Boolean);
const getDocumentTypeLabel = (type) => {
switch (type) {
case DocumentType.PASSPORT:
return t('documentType.passport');
case DocumentType.IDCARD:
return t('documentType.idCard');
case DocumentType.BOTH:
return t('documentType.both');
default:
return type;
}
};
useEffect(() => { useEffect(() => {
// Fetch available currencies // Fetch available currencies
getCurrencies().then((data) => { getCurrencies().then((data) => {
@ -662,6 +681,8 @@ const handleMarkerDragEnd = async (lat, lng) => {
buildingType: buildingTypeMap[formData.propertyType] ?? BuildingType.APARTMENT, buildingType: buildingTypeMap[formData.propertyType] ?? BuildingType.APARTMENT,
status: 0, status: 0,
propertyType: formData.furnished ? RentPropertyCondition.WITH_FURNITURE : RentPropertyCondition.WITHOUT_FURNITURE, propertyType: formData.furnished ? RentPropertyCondition.WITH_FURNITURE : RentPropertyCondition.WITHOUT_FURNITURE,
documentType: formData.documentType,
governorate: formData.city,
}; };
try { try {
@ -822,6 +843,23 @@ const handleMarkerDragEnd = async (lat, lng) => {
</div> </div>
</div> </div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('addProperty.documentTypeLabel')} <span className="text-red-500">*</span>
</label>
<select
value={formData.documentType}
onChange={(e) => setFormData({...formData, documentType: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
>
{DocumentTypeList.map((type) => (
<option key={type} value={type}>
{getDocumentTypeLabel(type)}
</option>
))}
</select>
</div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
{t('addProperty.additionalDescription')} {t('addProperty.additionalDescription')}
@ -1519,38 +1557,72 @@ const handleMarkerDragEnd = async (lat, lng) => {
<p className="text-gray-600">{t('addProperty.locationAndImagesSubtitle')}</p> <p className="text-gray-600">{t('addProperty.locationAndImagesSubtitle')}</p>
</div> </div>
<div className="rounded-3xl border border-amber-100 bg-gradient-to-br from-amber-50 via-white to-orange-50 p-4 sm:p-5 md:p-6 shadow-sm">
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between mb-5">
<div> <div>
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('addProperty.selectLocationTitle')}</h3> <div className="flex items-center gap-2 text-amber-700 font-semibold mb-1">
<Navigation className="w-4 h-4" />
{t('addProperty.selectLocationTitle')}
</div>
<p className="text-sm text-gray-600">{t('addProperty.clickMapHint')}</p>
</div>
<div className="inline-flex items-center gap-2 rounded-full border border-amber-200 bg-white px-3 py-1.5 text-sm font-medium text-amber-700 shadow-sm">
<MapPin className="w-4 h-4" />
{t('addProperty.cityLabel')}
</div>
</div>
<div className="flex gap-2 mb-4"> <div className="grid grid-cols-1 xl:grid-cols-[minmax(220px,260px)_1fr] gap-4 mb-5">
<div className="flex-1 relative"> <div className="rounded-2xl border border-white/80 bg-white/90 p-4 shadow-sm">
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('addProperty.cityLabel')} <span className="text-red-500">*</span>
</label>
<select
value={formData.city}
onChange={(e) => setFormData({...formData, city: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 bg-white"
>
{GovernorateList.map((city) => (
<option key={city} value={city}>
{GovernorateLabels[city] || city}
</option>
))}
</select>
<p className="text-xs text-gray-500 mt-2">{t('addProperty.searchHint')}</p>
</div>
<div className="rounded-2xl border border-white/80 bg-white/90 p-4 shadow-sm">
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('addProperty.searchAddress')}
</label>
<div className="flex flex-col sm:flex-row gap-2">
<div className="relative flex-1">
<input <input
type="text" type="text"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()} onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
placeholder={t('addProperty.searchAddress')} placeholder={t('addProperty.searchAddress')}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 pr-12" className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 pr-10"
/> />
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" /> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
</div> </div>
<button <button
onClick={handleSearch} onClick={handleSearch}
className="px-6 py-3 bg-amber-500 text-white rounded-xl hover:bg-amber-600 transition-colors" className="px-5 py-3 bg-amber-500 text-white rounded-xl hover:bg-amber-600 transition-colors font-medium flex items-center justify-center gap-2"
> >
<Search className="w-4 h-4" />
{t('addProperty.searchButton')} {t('addProperty.searchButton')}
</button> </button>
</div> </div>
<div className="mt-3 flex items-start gap-2 text-sm text-amber-700">
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-4 text-sm text-amber-800 leading-relaxed"> <Info className="w-4 h-4 mt-0.5 shrink-0" />
<Search className="w-4 h-4 inline ml-1" /> <span>{t('addProperty.clickMapHint')}</span>
{t('addProperty.searchHint')} </div>
<br /> </div>
<MapPin className="w-4 h-4 inline ml-1" />
{t('addProperty.clickMapHint')}
</div> </div>
<div className="relative w-full h-96 rounded-xl overflow-hidden border-2 border-gray-200 mb-4"> <div className="relative w-full h-96 rounded-2xl overflow-hidden border border-gray-200 shadow-inner">
<AddPropertyMap <AddPropertyMap
mapCenter={mapCenter} mapCenter={mapCenter}
mapZoom={mapZoom} mapZoom={mapZoom}
@ -1560,18 +1632,24 @@ const handleMarkerDragEnd = async (lat, lng) => {
/> />
</div> </div>
{selectedLocation && !formData.lat && ( <div className="mt-4 flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
{selectedLocation && !formData.lat ? (
<button <button
onClick={confirmLocation} onClick={confirmLocation}
className="w-full mb-4 bg-green-500 text-white py-3 rounded-xl font-medium hover:bg-green-600 transition-colors flex items-center justify-center gap-2" className="w-full md:w-auto bg-green-600 text-white py-3 px-5 rounded-xl font-medium hover:bg-green-700 transition-colors flex items-center justify-center gap-2 shadow-sm"
> >
<CheckCircle className="w-5 h-5" /> <CheckCircle className="w-5 h-5" />
{t('addProperty.confirmLocation')} {t('addProperty.confirmLocation')}
</button> </button>
) : (
<div className="rounded-2xl border border-amber-100 bg-amber-50 px-4 py-3 text-sm text-amber-800 flex items-start gap-2">
<Info className="w-4 h-4 mt-0.5 shrink-0" />
<span>{t('addProperty.clickMapHint')}</span>
</div>
)} )}
{formData.lat && ( {formData.lat && (
<div className="bg-green-50 border border-green-200 rounded-xl p-4 mb-4"> <div className="flex-1 rounded-2xl border border-green-200 bg-green-50 p-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<CheckCircle className="w-5 h-5 text-green-600" /> <CheckCircle className="w-5 h-5 text-green-600" />
<span className="text-green-800 font-medium">{t('addProperty.locationConfirmed')}:</span> <span className="text-green-800 font-medium">{t('addProperty.locationConfirmed')}:</span>
@ -1579,9 +1657,10 @@ const handleMarkerDragEnd = async (lat, lng) => {
<p className="text-green-700 text-sm mt-2 line-clamp-2">{formData.address}</p> <p className="text-green-700 text-sm mt-2 line-clamp-2">{formData.address}</p>
</div> </div>
)} )}
</div>
{errors.location && ( {errors.location && (
<p className="text-red-500 text-sm text-center mb-4">{errors.location}</p> <p className="text-red-500 text-sm text-center mt-4">{errors.location}</p>
)} )}
</div> </div>