stay_commerce-frontend 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/bundle.css +8665 -6069
- data/app/assets/builds/bundle.css.map +3 -3
- data/app/assets/builds/bundle.js +37877 -27557
- data/app/assets/builds/bundle.js.map +4 -4
- data/app/assets/builds/styles.css +6328 -5031
- data/app/assets/builds/styles.css.map +3 -3
- data/app/javascript/react/components/Accountpage/AccountInfo.jsx +2 -1
- data/app/javascript/react/components/AddNewProperty/CommonLayout.jsx +0 -2
- data/app/javascript/react/components/AddNewProperty/Description.jsx +51 -52
- data/app/javascript/react/components/AddNewProperty/Details.jsx +212 -129
- data/app/javascript/react/components/AddNewProperty/Images.jsx +122 -45
- data/app/javascript/react/components/AddNewProperty/Location.jsx +21 -14
- data/app/javascript/react/components/AddNewProperty/Room.jsx +639 -548
- data/app/javascript/react/components/AvatarDropdown/AvatarDropDown.jsx +9 -1
- data/app/javascript/react/components/FacilitiesSection/Facilities.jsx +18 -0
- data/app/javascript/react/components/FixedNavbar/FixedNav.jsx +20 -14
- data/app/javascript/react/components/HeroSectionDesign/BookingForm.jsx +136 -88
- data/app/javascript/react/components/HeroSectionDesign/MyPropertiesListing.jsx +79 -69
- data/app/javascript/react/components/Layout/Layout.js +8 -1
- data/app/javascript/react/components/Listing-stay-Detail/ApartmentCard.jsx +3 -3
- data/app/javascript/react/components/Listing-stay-Detail/BookingModal.jsx +167 -122
- data/app/javascript/react/components/Listing-stay-Detail/CardManager.jsx +285 -0
- data/app/javascript/react/components/Listing-stay-Detail/CheckoutForm.jsx +147 -84
- data/app/javascript/react/components/Listing-stay-Detail/ListingStayDetailPage.jsx +1 -7
- data/app/javascript/react/components/Listing-stay-Detail/PropertiesPage.jsx +464 -0
- data/app/javascript/react/components/MobileNav/MobileMenu.jsx +1 -4
- data/app/javascript/react/components/PropertyListing/MyProperties.jsx +45 -44
- data/app/javascript/react/components/PropertyListing/StayBooking/BookingDetails.jsx +4 -4
- data/app/javascript/react/components/PropertyListing/StayBooking/MyBooking.jsx +41 -29
- data/app/javascript/react/components/StayCard/StayCard.jsx +5 -3
- data/app/javascript/react/packs/index.jsx +1 -0
- data/app/javascript/react/packs/routes/Route.jsx +18 -1
- data/app/javascript/react/pages/Home.jsx +6 -4
- data/app/javascript/react/redux/slices/PropertySlice/PropertySlice.jsx +21 -21
- data/app/javascript/react/redux/slices/PropertySlice/Searchslice.jsx +53 -6
- data/app/javascript/react/redux/slices/UserSlice/UserSlice.jsx +1 -0
- data/app/javascript/react/shared/Avatar/Avatar.jsx +5 -8
- data/app/javascript/react/shared/Button/SecondryButton.jsx +9 -0
- data/app/javascript/react/shared/Loader.jsx +13 -0
- data/app/javascript/react/shared/Pagination.jsx +53 -0
- data/app/javascript/react/shared/Schema/FormSchema +143 -0
- data/app/javascript/react/styles/BookingDetails.scss +1 -0
- data/app/javascript/react/styles/CardManager.scss +608 -0
- data/app/javascript/react/styles/Loader.scss +30 -0
- data/app/javascript/react/styles/Pagination.scss +33 -0
- data/app/javascript/react/styles/PropertiesPage.scss +0 -4
- data/app/javascript/react/styles/RenderSection.scss +1 -0
- data/app/javascript/react/styles/accountpage.scss +3 -0
- data/app/javascript/react/styles/application.scss +13 -1
- data/app/javascript/react/styles/bookingform.scss +56 -28
- data/app/javascript/react/styles/buttonSecondry.scss +24 -0
- data/app/javascript/react/styles/checkbox.scss +34 -35
- data/app/javascript/react/styles/commonlayout.scss +7 -2
- data/app/javascript/react/styles/commonpage.scss +5 -1
- data/app/javascript/react/styles/description.scss +3 -0
- data/app/javascript/react/styles/facilities.scss +2 -1
- data/app/javascript/react/styles/fixednavbar.scss +8 -0
- data/app/javascript/react/styles/listingstaydetailpage.scss +5 -0
- data/app/javascript/react/styles/mobilemenu.scss +0 -1
- data/app/javascript/react/styles/mybooking.scss +20 -0
- data/app/javascript/react/styles/myproperty.scss +26 -0
- data/app/javascript/react/styles/propertydetailscard.scss +265 -267
- data/app/javascript/react/styles/react-datepicker/react-datepicker.css +869 -0
- data/app/javascript/react/styles/room.scss +13 -8
- data/app/javascript/react/utils/helpers/ToastErros.js +12 -0
- data/db/migrate/20250627101451_add_role_to_stay_users.rb +5 -0
- data/lib/stay_commerce/frontend/version.rb +1 -1
- metadata +15 -5
- data/app/javascript/react/components/HeroSectionDesign/PropertiesPage.jsx +0 -122
- data/app/javascript/react/shared/DateField/CustomDatePicker.jsx +0 -69
- data/app/javascript/react/styles/customdatepicker.scss +0 -120
@@ -21,11 +21,14 @@ import successHandler from "../../utils/helpers/SuccessHandler";
|
|
21
21
|
import Checkbox from "../../shared/Checkbox/Checkbox";
|
22
22
|
import CommonLayout from "./CommonLayout";
|
23
23
|
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
24
|
-
import CustomDatePicker from "../../shared/DateField/CustomDatePicker";
|
25
24
|
import { useDropzone } from "react-dropzone";
|
26
25
|
import NcImage from "../../shared/NcImage/NcImage";
|
27
26
|
import moment from "moment";
|
28
27
|
import "../../styles/room.scss";
|
28
|
+
import DatePicker from "react-datepicker";
|
29
|
+
import { roomValidationSchema } from "../../shared/Schema/FormSchema";
|
30
|
+
import { showToastError } from "../../utils/helpers/ToastErros";
|
31
|
+
import SecondryButton from "../../shared/Button/SecondryButton";
|
29
32
|
|
30
33
|
const Rooms = () => {
|
31
34
|
const dispatch = useDispatch();
|
@@ -34,7 +37,8 @@ const Rooms = () => {
|
|
34
37
|
const { slug } = useParams();
|
35
38
|
const id = slug;
|
36
39
|
const [property, setProperty] = useState(null);
|
37
|
-
|
40
|
+
const [originalValues, setOrignalValues] = useState(null);
|
41
|
+
const [showAddRoomButton, setShowAddRoomButton] = useState(true);
|
38
42
|
const initialRoomValues = {
|
39
43
|
id: "",
|
40
44
|
name: "",
|
@@ -67,27 +71,26 @@ const Rooms = () => {
|
|
67
71
|
const [roomImagePreviews, setRoomImagePreviews] = useState({});
|
68
72
|
const [roomImagesToDelete, setRoomImagesToDelete] = useState({});
|
69
73
|
const [isLoading, setIsLoading] = useState(true);
|
74
|
+
|
70
75
|
useEffect(() => {
|
71
76
|
if (initialValues?.rooms_attributes?.length > 0) {
|
72
77
|
setCount(initialValues?.rooms_attributes?.length);
|
73
78
|
}
|
74
79
|
}, [initialValues]);
|
75
|
-
|
76
80
|
useEffect(() => {
|
77
81
|
if (id) {
|
78
82
|
const fetchPropertyData = async () => {
|
79
83
|
const response = await dispatch(
|
80
84
|
getallupdateProperties({ propertyId: id })
|
81
85
|
).unwrap();
|
82
|
-
|
86
|
+
|
87
|
+
if (response) {
|
83
88
|
setProperty(response);
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
);
|
90
|
-
return {
|
89
|
+
// Set total rooms from API, default to 1 if not provided
|
90
|
+
setTotalRooms(response.total_rooms || 1);
|
91
|
+
let roomsToShow = [];
|
92
|
+
if (response?.rooms?.length > 0) {
|
93
|
+
roomsToShow = response.rooms.map((room) => ({
|
91
94
|
id: room.id,
|
92
95
|
name: room.name,
|
93
96
|
price_per_month:
|
@@ -108,27 +111,52 @@ const Rooms = () => {
|
|
108
111
|
amenities: room.amenities || [],
|
109
112
|
features: room.features || [],
|
110
113
|
room_images: [],
|
111
|
-
existing_room_images:
|
114
|
+
existing_room_images:
|
115
|
+
[...room?.room_images].sort(
|
116
|
+
(a, b) => a.position - b.position
|
117
|
+
) || [],
|
112
118
|
max_guests: room.max_guests?.toString() || "",
|
113
|
-
};
|
114
|
-
|
115
|
-
|
116
|
-
|
119
|
+
}));
|
120
|
+
roomsToShow = Array.from(
|
121
|
+
new Map(roomsToShow.map((room) => [room.id, room])).values()
|
122
|
+
);
|
123
|
+
} else {
|
124
|
+
roomsToShow = [{ ...initialRoomValues }];
|
125
|
+
}
|
126
|
+
const finalValues = {
|
127
|
+
rooms_attributes: Array.from(roomsToShow),
|
128
|
+
};
|
129
|
+
|
130
|
+
setInitialValues(finalValues);
|
131
|
+
setOrignalValues(JSON.parse(JSON.stringify(finalValues)));
|
132
|
+
setShowAddRoomButton(
|
133
|
+
roomsToShow.length < (response.total_rooms || 1)
|
117
134
|
);
|
118
|
-
setInitialValues({
|
119
|
-
rooms_attributes:
|
120
|
-
uniqueRooms.length > 0
|
121
|
-
? uniqueRooms
|
122
|
-
: initialValues.rooms_attributes?.map((item) => ({
|
123
|
-
...item,
|
124
|
-
})),
|
125
|
-
});
|
126
135
|
}
|
127
136
|
};
|
128
137
|
fetchPropertyData();
|
129
138
|
}
|
130
139
|
}, [dispatch, globalId, id]);
|
131
140
|
|
141
|
+
const hasFormChanged = (currentValues, originalValues) => {
|
142
|
+
if (!originalValues) return true;
|
143
|
+
const currentStr = JSON.stringify(currentValues);
|
144
|
+
const originalStr = JSON.stringify(originalValues);
|
145
|
+
|
146
|
+
return currentStr !== originalStr;
|
147
|
+
};
|
148
|
+
const normalizeArrayForComparison = (arr) => {
|
149
|
+
return (
|
150
|
+
arr
|
151
|
+
?.map((item) => ({
|
152
|
+
id: item.id || item.amenity_id || item.feature_id,
|
153
|
+
amenity_id: item.amenity_id,
|
154
|
+
feature_id: item.feature_id,
|
155
|
+
}))
|
156
|
+
.sort((a, b) => (a.id || 0) - (b.id || 0)) || []
|
157
|
+
);
|
158
|
+
};
|
159
|
+
|
132
160
|
const handleAmenitiesChange = (
|
133
161
|
amenityId,
|
134
162
|
roomIndex,
|
@@ -181,6 +209,10 @@ const Rooms = () => {
|
|
181
209
|
};
|
182
210
|
|
183
211
|
const handleSubmit = async (values) => {
|
212
|
+
if (!hasFormChanged(values, originalValues)) {
|
213
|
+
navigate(`/listing-stay-detail/${slug}`);
|
214
|
+
return;
|
215
|
+
}
|
184
216
|
const formData = new FormData();
|
185
217
|
|
186
218
|
if (id) {
|
@@ -313,6 +345,38 @@ const Rooms = () => {
|
|
313
345
|
}
|
314
346
|
};
|
315
347
|
|
348
|
+
const formatFieldName = (field) => {
|
349
|
+
return field.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
|
350
|
+
};
|
351
|
+
const displayValidationErrors = (formErrors) => {
|
352
|
+
if (
|
353
|
+
formErrors.rooms_attributes &&
|
354
|
+
Array.isArray(formErrors.rooms_attributes)
|
355
|
+
) {
|
356
|
+
for (let index = 0; index < formErrors.rooms_attributes.length; index++) {
|
357
|
+
const roomError = formErrors.rooms_attributes[index];
|
358
|
+
if (roomError && typeof roomError === "object") {
|
359
|
+
for (const field of Object.keys(roomError)) {
|
360
|
+
if (roomError[field]) {
|
361
|
+
return [`Room ${index + 1} -${roomError[field]}`];
|
362
|
+
}
|
363
|
+
}
|
364
|
+
}
|
365
|
+
}
|
366
|
+
}
|
367
|
+
for (const key of Object.keys(formErrors)) {
|
368
|
+
if (key !== "rooms_attributes") {
|
369
|
+
const error = formErrors[key];
|
370
|
+
if (typeof error === "string") {
|
371
|
+
const formattedKey = formatFieldName(key);
|
372
|
+
return [`${formattedKey}: ${error}`];
|
373
|
+
}
|
374
|
+
}
|
375
|
+
}
|
376
|
+
|
377
|
+
return [];
|
378
|
+
};
|
379
|
+
|
316
380
|
function arraysEqual(arr1, arr2) {
|
317
381
|
if (arr1.length !== arr2.length) return false;
|
318
382
|
for (let i = 0; i < arr1.length; i++) {
|
@@ -321,24 +385,6 @@ const Rooms = () => {
|
|
321
385
|
return true;
|
322
386
|
}
|
323
387
|
|
324
|
-
const handleRoomImagesChange = (e, index, setFieldValue, values) => {
|
325
|
-
const files = e.target.files;
|
326
|
-
const newFiles = files ? Array.from(files) : [];
|
327
|
-
if (files) {
|
328
|
-
const newPreviews = newFiles.map((file) => URL.createObjectURL(file));
|
329
|
-
|
330
|
-
setRoomImagePreviews((prevPreviews) => ({
|
331
|
-
...prevPreviews,
|
332
|
-
[index]: [...(prevPreviews[index] || []), ...newPreviews],
|
333
|
-
}));
|
334
|
-
|
335
|
-
const existingImages =
|
336
|
-
values.rooms_attributes?.[index]?.room_images || [];
|
337
|
-
const combinedImages = [...existingImages, ...newFiles];
|
338
|
-
setFieldValue(`rooms_attributes[${index}].room_images`, combinedImages);
|
339
|
-
}
|
340
|
-
};
|
341
|
-
|
342
388
|
const handleRemoveRoomImage = async (
|
343
389
|
previewIndex,
|
344
390
|
index,
|
@@ -427,53 +473,85 @@ const Rooms = () => {
|
|
427
473
|
fetchRoomTypes();
|
428
474
|
}, [dispatch]);
|
429
475
|
|
430
|
-
const useRoomsImagesDropzone = (index, values, setFieldValue) => {
|
431
|
-
return useDropzone({
|
432
|
-
onDrop: (acceptedFiles) => {
|
433
|
-
const files = acceptedFiles.map((file) => URL.createObjectURL(file));
|
434
|
-
|
435
|
-
setRoomImagePreviews((prevPreviews) => ({
|
436
|
-
...prevPreviews,
|
437
|
-
[index]: [...(prevPreviews[index] || []), ...files],
|
438
|
-
}));
|
439
|
-
|
440
|
-
const existingImages =
|
441
|
-
values.rooms_attributes?.[index]?.room_images || [];
|
442
|
-
const combinedImages = [...existingImages, ...acceptedFiles];
|
443
|
-
setFieldValue(`rooms_attributes[${index}].room_images`, combinedImages);
|
444
|
-
},
|
445
|
-
});
|
446
|
-
};
|
447
|
-
|
448
476
|
const RoomImagesUploader = ({ index }) => {
|
449
|
-
const { values, setFieldValue } = useFormikContext();
|
477
|
+
const { values, setFieldValue, errors, touched } = useFormikContext();
|
478
|
+
|
450
479
|
const handleImageUpload = (files) => {
|
451
480
|
if (!files || files.length === 0) return;
|
452
|
-
const
|
453
|
-
|
481
|
+
const allowedTypes = [
|
482
|
+
"image/png",
|
483
|
+
"image/jpg",
|
484
|
+
"image/jpeg",
|
485
|
+
"image/gif",
|
486
|
+
];
|
487
|
+
const maxSize = 10 * 1024 * 1024; // 10MB
|
488
|
+
|
489
|
+
const validFiles = [];
|
490
|
+
const invalidFiles = [];
|
491
|
+
|
492
|
+
Array.from(files).forEach((file) => {
|
493
|
+
if (!allowedTypes.includes(file.type)) {
|
494
|
+
invalidFiles.push(`${file.name}: Invalid file type`);
|
495
|
+
} else if (file.size > maxSize) {
|
496
|
+
invalidFiles.push(`${file.name}: File too large (max 10MB)`);
|
497
|
+
} else {
|
498
|
+
validFiles.push(file);
|
499
|
+
}
|
500
|
+
});
|
501
|
+
|
502
|
+
if (invalidFiles.length > 0) {
|
503
|
+
alert(`Invalid files:\n${invalidFiles.join("\n")}`);
|
504
|
+
}
|
505
|
+
|
506
|
+
if (validFiles.length === 0) return;
|
507
|
+
const existingImages =
|
508
|
+
values.rooms_attributes?.[index]?.existing_room_images || [];
|
509
|
+
const currentImages = values.rooms_attributes?.[index]?.room_images || [];
|
510
|
+
const totalImages =
|
511
|
+
existingImages.length + currentImages.length + validFiles.length;
|
512
|
+
|
513
|
+
if (totalImages > 10) {
|
514
|
+
alert(
|
515
|
+
`Maximum 10 images allowed per room. You can add ${
|
516
|
+
10 - (existingImages.length + currentImages.length)
|
517
|
+
} more images.`
|
518
|
+
);
|
519
|
+
return;
|
520
|
+
}
|
521
|
+
|
522
|
+
const newPreviews = validFiles.map((file) => ({
|
454
523
|
previewUrl: URL.createObjectURL(file),
|
455
524
|
file,
|
456
525
|
}));
|
457
|
-
|
458
|
-
...prevPreviews,
|
459
|
-
[index]: [...(prevPreviews[index] || []), ...newPreviews],
|
460
|
-
}));
|
461
|
-
const existingImages =
|
462
|
-
values.rooms_attributes?.[index]?.room_images || [];
|
463
|
-
const combinedImages = [...existingImages, ...newFiles];
|
526
|
+
const combinedImages = [...currentImages, ...validFiles];
|
464
527
|
setFieldValue(`rooms_attributes[${index}].room_images`, combinedImages);
|
465
528
|
};
|
529
|
+
|
466
530
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
467
531
|
accept: {
|
468
532
|
"image/*": [".jpeg", ".jpg", ".png", ".gif"],
|
469
533
|
},
|
470
534
|
onDrop: handleImageUpload,
|
535
|
+
maxSize: 10 * 1024 * 1024,
|
471
536
|
});
|
472
|
-
|
537
|
+
const getImageError = () => {
|
538
|
+
if (
|
539
|
+
errors.rooms_attributes &&
|
540
|
+
Array.isArray(errors.rooms_attributes) &&
|
541
|
+
errors.rooms_attributes[index] &&
|
542
|
+
errors.rooms_attributes[index].room_images
|
543
|
+
) {
|
544
|
+
return errors.rooms_attributes[index].room_images;
|
545
|
+
}
|
546
|
+
return null;
|
547
|
+
};
|
548
|
+
const imageError = getImageError();
|
473
549
|
return (
|
474
550
|
<div
|
475
551
|
{...getRootProps()}
|
476
|
-
className={`upload-container ${isDragActive ? "drag-active" : ""}
|
552
|
+
className={`upload-container ${isDragActive ? "drag-active" : ""} ${
|
553
|
+
imageError ? "error" : ""
|
554
|
+
}`}
|
477
555
|
>
|
478
556
|
<input
|
479
557
|
{...getInputProps()}
|
@@ -482,7 +560,6 @@ const Rooms = () => {
|
|
482
560
|
type="file"
|
483
561
|
className="sr-only"
|
484
562
|
multiple
|
485
|
-
// No need for onChange here as the dropzone will handle all inputs
|
486
563
|
/>
|
487
564
|
<div className="upload-content">
|
488
565
|
<svg
|
@@ -505,6 +582,7 @@ const Rooms = () => {
|
|
505
582
|
</div>
|
506
583
|
<p className="upload-info">PNG, JPG, GIF up to 10MB</p>
|
507
584
|
</div>
|
585
|
+
{imageError && <div className="error-message">{imageError}</div>}
|
508
586
|
</div>
|
509
587
|
);
|
510
588
|
};
|
@@ -524,7 +602,7 @@ const Rooms = () => {
|
|
524
602
|
<div className="divider" />
|
525
603
|
<Formik
|
526
604
|
initialValues={initialValues}
|
527
|
-
|
605
|
+
validationSchema={roomValidationSchema}
|
528
606
|
onSubmit={handleSubmit}
|
529
607
|
enableReinitialize={true}
|
530
608
|
>
|
@@ -536,477 +614,421 @@ const Rooms = () => {
|
|
536
614
|
isSubmitting,
|
537
615
|
getFieldProps,
|
538
616
|
setFieldValue,
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
</FormItem>
|
566
|
-
<FormItem label="Price per Month" className="form-item">
|
567
|
-
<div className="relative">
|
568
|
-
<Input
|
569
|
-
type="text"
|
570
|
-
name={`rooms_attributes[${index}].price_per_month`}
|
571
|
-
placeholder="Please enter price per month"
|
572
|
-
onChange={handleChange}
|
573
|
-
value={values.rooms_attributes[index]?.price_per_month}
|
574
|
-
className="common-input"
|
575
|
-
/>
|
576
|
-
</div>
|
577
|
-
{errors.rooms_attributes &&
|
578
|
-
Array.isArray(errors.rooms_attributes) &&
|
579
|
-
typeof errors.rooms_attributes[index] === "object" &&
|
580
|
-
errors.rooms_attributes[index] !== null &&
|
581
|
-
errors.rooms_attributes[index].price_per_month && (
|
582
|
-
<div className="error-text">
|
583
|
-
{errors.rooms_attributes[index].price_per_month}
|
584
|
-
</div>
|
585
|
-
)}
|
586
|
-
</FormItem>
|
587
|
-
<FormItem label="Status" className="form-item">
|
588
|
-
<Select
|
589
|
-
name={`rooms_attributes[${index}].status`}
|
590
|
-
onChange={handleChange}
|
591
|
-
value={room.status}
|
592
|
-
className="common-input"
|
593
|
-
>
|
594
|
-
<option value="">Select status</option>
|
595
|
-
<option value="active">Active</option>
|
596
|
-
<option value="inactive">Inactive</option>
|
597
|
-
</Select>
|
598
|
-
{errors.rooms_attributes &&
|
599
|
-
Array.isArray(errors.rooms_attributes) &&
|
600
|
-
typeof errors.rooms_attributes[index] === "object" &&
|
601
|
-
errors.rooms_attributes[index] !== null &&
|
602
|
-
errors.rooms_attributes[index].status && (
|
603
|
-
<div className="error-text">
|
604
|
-
{errors.rooms_attributes[index].status}
|
605
|
-
</div>
|
606
|
-
)}
|
607
|
-
</FormItem>
|
608
|
-
<FormItem label="Max Guests" className="form-item">
|
609
|
-
<div className="relative">
|
617
|
+
validateForm,
|
618
|
+
}) => {
|
619
|
+
const handleFormSubmit = async (e) => {
|
620
|
+
e.preventDefault();
|
621
|
+
const formErrors = await validateForm();
|
622
|
+
if (Object.keys(formErrors).length > 0) {
|
623
|
+
const errorMessages = displayValidationErrors(formErrors);
|
624
|
+
if (errorMessages.length > 0) {
|
625
|
+
showToastError(errorMessages[0]);
|
626
|
+
return;
|
627
|
+
}
|
628
|
+
}
|
629
|
+
await handleSubmit(values);
|
630
|
+
};
|
631
|
+
|
632
|
+
return (
|
633
|
+
<form onSubmit={handleFormSubmit} className="form-container">
|
634
|
+
{values.rooms_attributes.map((room, index) => (
|
635
|
+
<div
|
636
|
+
key={index}
|
637
|
+
className={`room-section ${
|
638
|
+
index > 0 ? "room-section-bordered" : ""
|
639
|
+
}`}
|
640
|
+
>
|
641
|
+
<h3 className="room-title">Room {index + 1}</h3>
|
642
|
+
<FormItem label="Room Name" className="form-item">
|
610
643
|
<Input
|
611
644
|
type="text"
|
612
|
-
name
|
613
|
-
|
614
|
-
onChange={handleChange}
|
615
|
-
value={values.rooms_attributes[index]?.max_guests}
|
616
|
-
className="common-input"
|
617
|
-
/>
|
618
|
-
</div>
|
619
|
-
{errors.rooms_attributes &&
|
620
|
-
Array.isArray(errors.rooms_attributes) &&
|
621
|
-
typeof errors.rooms_attributes[index] === "object" &&
|
622
|
-
errors.rooms_attributes[index] !== null &&
|
623
|
-
errors.rooms_attributes[index].max_guests && (
|
624
|
-
<div className="error-text">
|
625
|
-
{errors.rooms_attributes[index].max_guests}
|
626
|
-
</div>
|
627
|
-
)}
|
628
|
-
</FormItem>
|
629
|
-
<div className="date-picker-container">
|
630
|
-
<FormItem label="Availability Start Date">
|
631
|
-
<input
|
632
|
-
type="date"
|
645
|
+
placeholder="Enter Room name"
|
646
|
+
{...getFieldProps(`rooms_attributes[${index}].name`)}
|
633
647
|
className="common-input"
|
634
|
-
value={
|
635
|
-
room.booking_start
|
636
|
-
? room.booking_start.split("T")[0]
|
637
|
-
: ""
|
638
|
-
}
|
639
|
-
onChange={(e) => {
|
640
|
-
const selectedDate = e.target.value;
|
641
|
-
setFieldValue(
|
642
|
-
`rooms_attributes[${index}].booking_start`,
|
643
|
-
selectedDate
|
644
|
-
);
|
645
|
-
setFieldValue(
|
646
|
-
`rooms_attributes[${index}].booking_end`,
|
647
|
-
null
|
648
|
-
);
|
649
|
-
}}
|
650
|
-
min={new Date().toISOString().split("T")[0]}
|
651
648
|
/>
|
652
649
|
{errors.rooms_attributes &&
|
653
650
|
Array.isArray(errors.rooms_attributes) &&
|
654
|
-
errors.rooms_attributes[index] &&
|
655
651
|
typeof errors.rooms_attributes[index] === "object" &&
|
656
652
|
errors.rooms_attributes[index] !== null &&
|
657
|
-
errors.rooms_attributes[index].
|
653
|
+
errors.rooms_attributes[index].name && (
|
658
654
|
<div className="error-text">
|
659
|
-
{
|
660
|
-
.booking_start === "string" &&
|
661
|
-
errors.rooms_attributes[index].booking_start}
|
655
|
+
{errors.rooms_attributes[index].name}
|
662
656
|
</div>
|
663
657
|
)}
|
664
658
|
</FormItem>
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
setFieldValue(
|
679
|
-
`rooms_attributes[${index}].booking_end`,
|
680
|
-
selectedDate
|
681
|
-
);
|
682
|
-
}}
|
683
|
-
min={
|
684
|
-
room.booking_start
|
685
|
-
? new Date(
|
686
|
-
new Date(room.booking_start).getTime() +
|
687
|
-
86400000
|
688
|
-
)
|
689
|
-
.toISOString()
|
690
|
-
.split("T")[0]
|
691
|
-
: new Date().toISOString().split("T")[0]
|
692
|
-
}
|
693
|
-
/>
|
659
|
+
<FormItem label="Price per Month" className="form-item">
|
660
|
+
<div className="relative">
|
661
|
+
<Input
|
662
|
+
type="text"
|
663
|
+
name={`rooms_attributes[${index}].price_per_month`}
|
664
|
+
placeholder="Please enter price per month"
|
665
|
+
onChange={handleChange}
|
666
|
+
value={
|
667
|
+
values.rooms_attributes[index]?.price_per_month
|
668
|
+
}
|
669
|
+
className="common-input"
|
670
|
+
/>
|
671
|
+
</div>
|
694
672
|
{errors.rooms_attributes &&
|
695
673
|
Array.isArray(errors.rooms_attributes) &&
|
696
|
-
errors.rooms_attributes[index] &&
|
697
674
|
typeof errors.rooms_attributes[index] === "object" &&
|
698
675
|
errors.rooms_attributes[index] !== null &&
|
699
|
-
errors.rooms_attributes[index].
|
676
|
+
errors.rooms_attributes[index].price_per_month && (
|
700
677
|
<div className="error-text">
|
701
|
-
{
|
702
|
-
.booking_end === "string" &&
|
703
|
-
errors.rooms_attributes[index].booking_end}
|
678
|
+
{errors.rooms_attributes[index].price_per_month}
|
704
679
|
</div>
|
705
680
|
)}
|
706
681
|
</FormItem>
|
707
|
-
|
708
|
-
<FormItem label="Description" className="form-item">
|
709
|
-
<textarea
|
710
|
-
className="description-textarea"
|
711
|
-
name={`rooms_attributes[${index}].description`}
|
712
|
-
placeholder="Enter room description"
|
713
|
-
onChange={handleChange}
|
714
|
-
value={room.description}
|
715
|
-
/>
|
716
|
-
{errors.rooms_attributes &&
|
717
|
-
Array.isArray(errors.rooms_attributes) &&
|
718
|
-
typeof errors.rooms_attributes[index] === "object" &&
|
719
|
-
errors.rooms_attributes[index] !== null &&
|
720
|
-
errors.rooms_attributes[index].description && (
|
721
|
-
<div className="error-text">
|
722
|
-
{errors.rooms_attributes[index].description}
|
723
|
-
</div>
|
724
|
-
)}
|
725
|
-
</FormItem>
|
726
|
-
<div className="room-details-container">
|
727
|
-
<FormItem label="Bed Type" className="form-item">
|
682
|
+
<FormItem label="Status" className="form-item">
|
728
683
|
<Select
|
729
|
-
name={`rooms_attributes[${index}].
|
684
|
+
name={`rooms_attributes[${index}].status`}
|
730
685
|
onChange={handleChange}
|
731
|
-
value={room.
|
686
|
+
value={room.status}
|
732
687
|
className="common-input"
|
733
688
|
>
|
734
|
-
<option value="">Select
|
735
|
-
|
736
|
-
|
737
|
-
{bedType.name}
|
738
|
-
</option>
|
739
|
-
))}
|
689
|
+
<option value="">Select status</option>
|
690
|
+
<option value="active">Active</option>
|
691
|
+
<option value="inactive">Inactive</option>
|
740
692
|
</Select>
|
741
693
|
{errors.rooms_attributes &&
|
742
694
|
Array.isArray(errors.rooms_attributes) &&
|
743
695
|
typeof errors.rooms_attributes[index] === "object" &&
|
744
696
|
errors.rooms_attributes[index] !== null &&
|
745
|
-
errors.rooms_attributes[index].
|
697
|
+
errors.rooms_attributes[index].status && (
|
746
698
|
<div className="error-text">
|
747
|
-
{errors.rooms_attributes[index].
|
699
|
+
{errors.rooms_attributes[index].status}
|
748
700
|
</div>
|
749
701
|
)}
|
750
702
|
</FormItem>
|
751
|
-
<FormItem label="
|
752
|
-
<
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
703
|
+
<FormItem label="Max Guests" className="form-item">
|
704
|
+
<div className="relative">
|
705
|
+
<Input
|
706
|
+
type="text"
|
707
|
+
name={`rooms_attributes[${index}].max_guests`}
|
708
|
+
placeholder="Max-Guests"
|
709
|
+
onChange={handleChange}
|
710
|
+
value={values.rooms_attributes[index]?.max_guests}
|
711
|
+
className="common-input"
|
712
|
+
/>
|
713
|
+
</div>
|
760
714
|
{errors.rooms_attributes &&
|
761
715
|
Array.isArray(errors.rooms_attributes) &&
|
762
716
|
typeof errors.rooms_attributes[index] === "object" &&
|
763
717
|
errors.rooms_attributes[index] !== null &&
|
764
|
-
errors.rooms_attributes[index].
|
718
|
+
errors.rooms_attributes[index].max_guests && (
|
765
719
|
<div className="error-text">
|
766
|
-
{errors.rooms_attributes[index].
|
720
|
+
{errors.rooms_attributes[index].max_guests}
|
767
721
|
</div>
|
768
722
|
)}
|
769
723
|
</FormItem>
|
770
|
-
<
|
771
|
-
<
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
724
|
+
<div className="date-picker-container">
|
725
|
+
<FormItem label="Availability Start Date">
|
726
|
+
<DatePicker
|
727
|
+
selected={
|
728
|
+
room.booking_start
|
729
|
+
? new Date(room.booking_start)
|
730
|
+
: null
|
731
|
+
}
|
732
|
+
onChange={(date) => {
|
733
|
+
setFieldValue(
|
734
|
+
`rooms_attributes[${index}].booking_start`,
|
735
|
+
date
|
736
|
+
);
|
737
|
+
setFieldValue(
|
738
|
+
`rooms_attributes[${index}].booking_end`,
|
739
|
+
null
|
740
|
+
);
|
741
|
+
}}
|
742
|
+
minDate={new Date()}
|
743
|
+
placeholderText="Select start date"
|
744
|
+
dateFormat="yyyy-MM-dd"
|
745
|
+
className="common-input"
|
746
|
+
isClearable
|
747
|
+
/>
|
748
|
+
{errors.rooms_attributes &&
|
749
|
+
Array.isArray(errors.rooms_attributes) &&
|
750
|
+
errors.rooms_attributes[index] &&
|
751
|
+
typeof errors.rooms_attributes[index] === "object" &&
|
752
|
+
errors.rooms_attributes[index] !== null &&
|
753
|
+
errors.rooms_attributes[index].booking_start && (
|
754
|
+
<div className="error-text">
|
755
|
+
{errors.rooms_attributes[index].booking_start}
|
756
|
+
</div>
|
757
|
+
)}
|
758
|
+
</FormItem>
|
759
|
+
|
760
|
+
<FormItem
|
761
|
+
label="Availability End Date"
|
762
|
+
className="form-item"
|
776
763
|
>
|
777
|
-
<
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
764
|
+
<DatePicker
|
765
|
+
selected={
|
766
|
+
room.booking_end ? new Date(room.booking_end) : null
|
767
|
+
}
|
768
|
+
onChange={(date) =>
|
769
|
+
setFieldValue(
|
770
|
+
`rooms_attributes[${index}].booking_end`,
|
771
|
+
date
|
772
|
+
)
|
773
|
+
}
|
774
|
+
minDate={
|
775
|
+
room.booking_start
|
776
|
+
? moment(room.booking_start)
|
777
|
+
.add(1, "days")
|
778
|
+
.toDate()
|
779
|
+
: new Date()
|
780
|
+
}
|
781
|
+
placeholderText="Select end date"
|
782
|
+
dateFormat="yyyy-MM-dd"
|
783
|
+
className="common-input"
|
784
|
+
isClearable
|
785
|
+
/>
|
786
|
+
{errors.rooms_attributes &&
|
787
|
+
Array.isArray(errors.rooms_attributes) &&
|
788
|
+
errors.rooms_attributes[index] &&
|
789
|
+
typeof errors.rooms_attributes[index] === "object" &&
|
790
|
+
errors.rooms_attributes[index] !== null &&
|
791
|
+
errors.rooms_attributes[index].booking_end && (
|
792
|
+
<div className="error-text">
|
793
|
+
{errors.rooms_attributes[index].booking_end}
|
794
|
+
</div>
|
795
|
+
)}
|
796
|
+
</FormItem>
|
797
|
+
</div>
|
798
|
+
<FormItem label="Description" className="form-item">
|
799
|
+
<textarea
|
800
|
+
className="description-textarea"
|
801
|
+
name={`rooms_attributes[${index}].description`}
|
802
|
+
placeholder="Enter room description"
|
803
|
+
onChange={handleChange}
|
804
|
+
value={room.description}
|
805
|
+
/>
|
784
806
|
{errors.rooms_attributes &&
|
785
807
|
Array.isArray(errors.rooms_attributes) &&
|
786
808
|
typeof errors.rooms_attributes[index] === "object" &&
|
787
809
|
errors.rooms_attributes[index] !== null &&
|
788
|
-
errors.rooms_attributes[index].
|
810
|
+
errors.rooms_attributes[index].description && (
|
789
811
|
<div className="error-text">
|
790
|
-
{errors.rooms_attributes[index].
|
812
|
+
{errors.rooms_attributes[index].description}
|
791
813
|
</div>
|
792
814
|
)}
|
793
815
|
</FormItem>
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
item.id === amenity.id
|
816
|
+
<div className="room-details-container">
|
817
|
+
<FormItem label="Bed Type" className="form-item">
|
818
|
+
<Select
|
819
|
+
name={`rooms_attributes[${index}].bed_type_id`}
|
820
|
+
onChange={handleChange}
|
821
|
+
value={room.bed_type_id}
|
822
|
+
className="common-input"
|
823
|
+
>
|
824
|
+
<option value="">Select bed type</option>
|
825
|
+
{bedTypes.map((bedType) => (
|
826
|
+
<option key={bedType.id} value={bedType.id}>
|
827
|
+
{bedType.name}
|
828
|
+
</option>
|
829
|
+
))}
|
830
|
+
</Select>
|
831
|
+
{errors.rooms_attributes &&
|
832
|
+
Array.isArray(errors.rooms_attributes) &&
|
833
|
+
typeof errors.rooms_attributes[index] === "object" &&
|
834
|
+
errors.rooms_attributes[index] !== null &&
|
835
|
+
errors.rooms_attributes[index].bed_type_id && (
|
836
|
+
<div className="error-text">
|
837
|
+
{errors.rooms_attributes[index].bed_type_id}
|
838
|
+
</div>
|
818
839
|
)}
|
840
|
+
</FormItem>
|
841
|
+
<FormItem label="Room Size (sq ft)" className="form-item">
|
842
|
+
<Input
|
843
|
+
type="text"
|
844
|
+
name={`rooms_attributes[${index}].size`}
|
845
|
+
placeholder="Enter room size"
|
846
|
+
onChange={handleChange}
|
847
|
+
value={room.size}
|
848
|
+
className="common-input"
|
819
849
|
/>
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
850
|
+
{errors.rooms_attributes &&
|
851
|
+
Array.isArray(errors.rooms_attributes) &&
|
852
|
+
typeof errors.rooms_attributes[index] === "object" &&
|
853
|
+
errors.rooms_attributes[index] !== null &&
|
854
|
+
errors.rooms_attributes[index].size && (
|
855
|
+
<div className="error-text">
|
856
|
+
{errors.rooms_attributes[index].size}
|
857
|
+
</div>
|
858
|
+
)}
|
859
|
+
</FormItem>
|
860
|
+
<FormItem label="Room Type" className="form-item">
|
861
|
+
<Select
|
862
|
+
name={`rooms_attributes[${index}].room_type_id`}
|
863
|
+
onChange={handleChange}
|
864
|
+
value={room.room_type_id}
|
831
865
|
className="common-input"
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
]
|
844
|
-
|
845
|
-
|
846
|
-
|
866
|
+
>
|
867
|
+
<option value="">Select room type</option>
|
868
|
+
{roomTypes.map((roomType) => (
|
869
|
+
<option key={roomType.id} value={roomType.id}>
|
870
|
+
{roomType.name}
|
871
|
+
</option>
|
872
|
+
))}
|
873
|
+
</Select>
|
874
|
+
{errors.rooms_attributes &&
|
875
|
+
Array.isArray(errors.rooms_attributes) &&
|
876
|
+
typeof errors.rooms_attributes[index] === "object" &&
|
877
|
+
errors.rooms_attributes[index] !== null &&
|
878
|
+
errors.rooms_attributes[index].room_type_id && (
|
879
|
+
<div className="error-text">
|
880
|
+
{errors.rooms_attributes[index].room_type_id}
|
881
|
+
</div>
|
847
882
|
)}
|
848
|
-
|
849
|
-
))}
|
883
|
+
</FormItem>
|
850
884
|
</div>
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
{
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
885
|
+
<FormItem>
|
886
|
+
<h2 className="section-title">Amenities</h2>
|
887
|
+
<div className="checkbox-grid">
|
888
|
+
{amenities?.map((amenity) => (
|
889
|
+
<Checkbox
|
890
|
+
key={amenity.id.toString()}
|
891
|
+
id={amenity.id.toString()}
|
892
|
+
label={amenity.name}
|
893
|
+
name={`rooms_attributes[${index}].amenities`}
|
894
|
+
onChange={(checked) =>
|
895
|
+
handleAmenitiesChange(
|
896
|
+
amenity?.id,
|
897
|
+
index,
|
898
|
+
values,
|
899
|
+
setFieldValue
|
900
|
+
)
|
901
|
+
}
|
902
|
+
checked={values.rooms_attributes[
|
903
|
+
index
|
904
|
+
]?.amenities?.some(
|
905
|
+
(item) =>
|
906
|
+
item.amenity_id === amenity.id ||
|
907
|
+
item.id === amenity.id
|
908
|
+
)}
|
909
|
+
/>
|
910
|
+
))}
|
911
|
+
</div>
|
912
|
+
</FormItem>
|
913
|
+
<FormItem>
|
914
|
+
<h2 className="section-title">Features</h2>
|
915
|
+
<div className="checkbox-grid">
|
916
|
+
{features?.map((feature) => (
|
917
|
+
<Checkbox
|
918
|
+
key={feature.id.toString()}
|
919
|
+
id={feature.id.toString()}
|
920
|
+
label={feature.name}
|
921
|
+
name={`rooms_attributes[${index}].features`}
|
922
|
+
onChange={(checked) =>
|
923
|
+
handleFeaturesChange(
|
924
|
+
feature?.id,
|
925
|
+
index,
|
926
|
+
values,
|
927
|
+
setFieldValue
|
928
|
+
)
|
929
|
+
}
|
930
|
+
checked={values.rooms_attributes[
|
931
|
+
index
|
932
|
+
]?.features?.some(
|
933
|
+
(item) =>
|
934
|
+
item.feature_id === feature.id ||
|
935
|
+
item.id === feature.id
|
936
|
+
)}
|
937
|
+
/>
|
938
|
+
))}
|
939
|
+
</div>
|
940
|
+
</FormItem>
|
941
|
+
<div key={index}>
|
942
|
+
<div className="image-upload-section">
|
943
|
+
<span className="image-upload-title">Room images</span>
|
944
|
+
<div className="image-upload-content">
|
945
|
+
<RoomImagesUploader key={index} index={index} />
|
946
|
+
{isLoading ? (
|
947
|
+
<div className="image-preview-container">
|
948
|
+
{Array.from({
|
949
|
+
length: room.existing_room_images?.length,
|
950
|
+
}).map((_, previewIndex) => (
|
951
|
+
<div
|
952
|
+
key={previewIndex}
|
953
|
+
className="image-placeholder"
|
954
|
+
></div>
|
955
|
+
))}
|
956
|
+
</div>
|
957
|
+
) : (
|
958
|
+
<DragDropContext
|
959
|
+
onDragEnd={(result) => {
|
960
|
+
const { source, destination } = result;
|
961
|
+
if (!destination) return;
|
962
|
+
const existingImages =
|
963
|
+
room.existing_room_images || [];
|
964
|
+
const newImages = room.room_images || [];
|
965
|
+
const combinedImages = [
|
966
|
+
...existingImages,
|
967
|
+
...newImages,
|
968
|
+
];
|
880
969
|
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
970
|
+
// Reorder the images
|
971
|
+
const reorderedImages = [...combinedImages];
|
972
|
+
const [removed] = reorderedImages.splice(
|
973
|
+
source.index,
|
974
|
+
1
|
975
|
+
);
|
976
|
+
reorderedImages.splice(
|
977
|
+
destination.index,
|
978
|
+
0,
|
979
|
+
removed
|
980
|
+
);
|
981
|
+
const newPosition = destination.index + 1;
|
982
|
+
if (removed.id) {
|
983
|
+
setReorderImages((prev) => {
|
984
|
+
const updated = prev.filter(
|
985
|
+
(item) =>
|
986
|
+
item.position !== newPosition ||
|
987
|
+
item.blob_id === removed.id
|
988
|
+
);
|
989
|
+
return [
|
990
|
+
...updated,
|
991
|
+
{
|
992
|
+
blob_id: removed.id,
|
993
|
+
position: newPosition,
|
994
|
+
},
|
995
|
+
];
|
996
|
+
});
|
997
|
+
}
|
909
998
|
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
}}
|
924
|
-
>
|
925
|
-
<Droppable
|
926
|
-
droppableId={`room-images-${index}`}
|
927
|
-
direction="horizontal"
|
999
|
+
const updatedExistingRoomImages =
|
1000
|
+
reorderedImages.filter((item) => item.id);
|
1001
|
+
const updatedRoomImages =
|
1002
|
+
reorderedImages.filter((item) => !item.id);
|
1003
|
+
setFieldValue(
|
1004
|
+
`rooms_attributes[${index}].existing_room_images`,
|
1005
|
+
updatedExistingRoomImages
|
1006
|
+
);
|
1007
|
+
setFieldValue(
|
1008
|
+
`rooms_attributes[${index}].room_images`,
|
1009
|
+
updatedRoomImages
|
1010
|
+
);
|
1011
|
+
}}
|
928
1012
|
>
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
draggableId={`existing-image-${
|
942
|
-
image.id || previewIndex
|
943
|
-
}`}
|
944
|
-
index={previewIndex}
|
945
|
-
>
|
946
|
-
{(provided) => (
|
947
|
-
<div
|
948
|
-
ref={provided.innerRef}
|
949
|
-
{...provided.draggableProps}
|
950
|
-
{...provided.dragHandleProps}
|
951
|
-
className="image-wrapper"
|
952
|
-
>
|
953
|
-
<div className="image-container">
|
954
|
-
<NcImage
|
955
|
-
src={image.url || image}
|
956
|
-
alt={`Room ${index + 1} Image ${
|
957
|
-
previewIndex + 1
|
958
|
-
}`}
|
959
|
-
className="image"
|
960
|
-
/>
|
961
|
-
</div>
|
962
|
-
<button
|
963
|
-
type="button"
|
964
|
-
onClick={() =>
|
965
|
-
handleRemoveRoomImage(
|
966
|
-
previewIndex,
|
967
|
-
index,
|
968
|
-
values,
|
969
|
-
setFieldValue
|
970
|
-
)
|
971
|
-
}
|
972
|
-
className="remove-image-button"
|
973
|
-
>
|
974
|
-
<svg
|
975
|
-
className="remove-image-icon"
|
976
|
-
xmlns="http://www.w3.org/2000/svg"
|
977
|
-
fill="none"
|
978
|
-
viewBox="0 0 24 24"
|
979
|
-
stroke="currentColor"
|
980
|
-
>
|
981
|
-
<path
|
982
|
-
strokeLinecap="round"
|
983
|
-
strokeLinejoin="round"
|
984
|
-
strokeWidth={2}
|
985
|
-
d="M6 18L18 6M6 6l12 12"
|
986
|
-
/>
|
987
|
-
</svg>
|
988
|
-
</button>
|
989
|
-
</div>
|
990
|
-
)}
|
991
|
-
</Draggable>
|
992
|
-
)
|
993
|
-
)}
|
994
|
-
{(room.room_images || []).map(
|
995
|
-
(image, newIndex) => {
|
996
|
-
const previewIndex =
|
997
|
-
(room.existing_room_images?.length ||
|
998
|
-
0) + newIndex;
|
999
|
-
const previewUrl =
|
1000
|
-
roomImagePreviews[index]?.[newIndex]
|
1001
|
-
?.previewUrl ||
|
1002
|
-
(image instanceof File
|
1003
|
-
? URL.createObjectURL(image)
|
1004
|
-
: null);
|
1005
|
-
|
1006
|
-
return (
|
1013
|
+
<Droppable
|
1014
|
+
droppableId={`room-images-${index}`}
|
1015
|
+
direction="horizontal"
|
1016
|
+
>
|
1017
|
+
{(provided) => (
|
1018
|
+
<div
|
1019
|
+
className="image-preview-container"
|
1020
|
+
{...provided.droppableProps}
|
1021
|
+
ref={provided.innerRef}
|
1022
|
+
>
|
1023
|
+
{(room.existing_room_images || []).map(
|
1024
|
+
(image, previewIndex) => (
|
1007
1025
|
<Draggable
|
1008
|
-
key={`
|
1009
|
-
|
1026
|
+
key={`existing-image-${
|
1027
|
+
image.id || previewIndex
|
1028
|
+
}`}
|
1029
|
+
draggableId={`existing-image-${
|
1030
|
+
image.id || previewIndex
|
1031
|
+
}`}
|
1010
1032
|
index={previewIndex}
|
1011
1033
|
>
|
1012
1034
|
{(provided) => (
|
@@ -1018,7 +1040,7 @@ const Rooms = () => {
|
|
1018
1040
|
>
|
1019
1041
|
<div className="image-container">
|
1020
1042
|
<NcImage
|
1021
|
-
src={
|
1043
|
+
src={image.url || image}
|
1022
1044
|
alt={`Room ${
|
1023
1045
|
index + 1
|
1024
1046
|
} Image ${previewIndex + 1}`}
|
@@ -1055,74 +1077,143 @@ const Rooms = () => {
|
|
1055
1077
|
</div>
|
1056
1078
|
)}
|
1057
1079
|
</Draggable>
|
1058
|
-
)
|
1059
|
-
}
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1080
|
+
)
|
1081
|
+
)}
|
1082
|
+
{(room.room_images || []).map(
|
1083
|
+
(image, newIndex) => {
|
1084
|
+
const previewIndex =
|
1085
|
+
(room.existing_room_images?.length ||
|
1086
|
+
0) + newIndex;
|
1087
|
+
const previewUrl =
|
1088
|
+
roomImagePreviews[index]?.[newIndex]
|
1089
|
+
?.previewUrl ||
|
1090
|
+
(image instanceof File
|
1091
|
+
? URL.createObjectURL(image)
|
1092
|
+
: null);
|
1093
|
+
|
1094
|
+
return (
|
1095
|
+
<Draggable
|
1096
|
+
key={`new-image-${previewIndex}`}
|
1097
|
+
draggableId={`new-image-${previewIndex}`}
|
1098
|
+
index={previewIndex}
|
1099
|
+
>
|
1100
|
+
{(provided) => (
|
1101
|
+
<div
|
1102
|
+
ref={provided.innerRef}
|
1103
|
+
{...provided.draggableProps}
|
1104
|
+
{...provided.dragHandleProps}
|
1105
|
+
className="image-wrapper"
|
1106
|
+
>
|
1107
|
+
<div className="image-container">
|
1108
|
+
<NcImage
|
1109
|
+
src={previewUrl || image}
|
1110
|
+
alt={`Room ${
|
1111
|
+
index + 1
|
1112
|
+
} Image ${
|
1113
|
+
previewIndex + 1
|
1114
|
+
}`}
|
1115
|
+
className="image"
|
1116
|
+
/>
|
1117
|
+
</div>
|
1118
|
+
<button
|
1119
|
+
type="button"
|
1120
|
+
onClick={() =>
|
1121
|
+
handleRemoveRoomImage(
|
1122
|
+
previewIndex,
|
1123
|
+
index,
|
1124
|
+
values,
|
1125
|
+
setFieldValue
|
1126
|
+
)
|
1127
|
+
}
|
1128
|
+
className="remove-image-button"
|
1129
|
+
>
|
1130
|
+
<svg
|
1131
|
+
className="remove-image-icon"
|
1132
|
+
xmlns="http://www.w3.org/2000/svg"
|
1133
|
+
fill="none"
|
1134
|
+
viewBox="0 0 24 24"
|
1135
|
+
stroke="currentColor"
|
1136
|
+
>
|
1137
|
+
<path
|
1138
|
+
strokeLinecap="round"
|
1139
|
+
strokeLinejoin="round"
|
1140
|
+
strokeWidth={2}
|
1141
|
+
d="M6 18L18 6M6 6l12 12"
|
1142
|
+
/>
|
1143
|
+
</svg>
|
1144
|
+
</button>
|
1145
|
+
</div>
|
1146
|
+
)}
|
1147
|
+
</Draggable>
|
1148
|
+
);
|
1149
|
+
}
|
1150
|
+
)}
|
1151
|
+
{provided.placeholder}
|
1152
|
+
</div>
|
1153
|
+
)}
|
1154
|
+
</Droppable>
|
1155
|
+
</DragDropContext>
|
1156
|
+
)}
|
1157
|
+
</div>
|
1067
1158
|
</div>
|
1068
1159
|
</div>
|
1069
1160
|
</div>
|
1161
|
+
))}
|
1162
|
+
<div
|
1163
|
+
className="room-actions-container"
|
1164
|
+
style={{
|
1165
|
+
display: "flex",
|
1166
|
+
gap: "10px",
|
1167
|
+
flexWrap: "wrap",
|
1168
|
+
}}
|
1169
|
+
>
|
1170
|
+
{values.rooms_attributes.length > 1 && (
|
1171
|
+
<ButtonPrimary
|
1172
|
+
type="button"
|
1173
|
+
onClick={() => {
|
1174
|
+
const newRooms = values.rooms_attributes.slice(0, -1);
|
1175
|
+
setFieldValue("rooms_attributes", newRooms);
|
1176
|
+
setShowAddRoomButton(newRooms.length < totalRooms);
|
1177
|
+
}}
|
1178
|
+
className="action-button"
|
1179
|
+
>
|
1180
|
+
Remove Last Room
|
1181
|
+
</ButtonPrimary>
|
1182
|
+
)}
|
1183
|
+
|
1184
|
+
{showAddRoomButton && (
|
1185
|
+
<ButtonPrimary
|
1186
|
+
type="button"
|
1187
|
+
onClick={() => {
|
1188
|
+
const newRooms = [
|
1189
|
+
...values.rooms_attributes,
|
1190
|
+
initialRoomValues,
|
1191
|
+
];
|
1192
|
+
setFieldValue("rooms_attributes", newRooms);
|
1193
|
+
setShowAddRoomButton(newRooms.length < totalRooms);
|
1194
|
+
}}
|
1195
|
+
className="action-button"
|
1196
|
+
>
|
1197
|
+
Add Another Room
|
1198
|
+
</ButtonPrimary>
|
1199
|
+
)}
|
1070
1200
|
</div>
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
gap: "10px",
|
1077
|
-
flexWrap: "wrap",
|
1078
|
-
}}
|
1079
|
-
>
|
1080
|
-
{values.rooms_attributes.length > 1 && (
|
1201
|
+
|
1202
|
+
<div className="form-actions">
|
1203
|
+
<SecondryButton href={`/property-4/${slug}`}>
|
1204
|
+
Go back
|
1205
|
+
</SecondryButton>
|
1081
1206
|
<ButtonPrimary
|
1082
|
-
type="
|
1083
|
-
onClick={() =>
|
1084
|
-
|
1085
|
-
setFieldValue(
|
1086
|
-
"rooms_attributes",
|
1087
|
-
values.rooms_attributes.filter(
|
1088
|
-
(_, idx) => idx !== values.rooms_attributes.length - 1
|
1089
|
-
)
|
1090
|
-
);
|
1091
|
-
}}
|
1092
|
-
className="action-button"
|
1207
|
+
type="submit"
|
1208
|
+
onClick={() => handleFormSubmit()}
|
1209
|
+
disabled={isSubmitting}
|
1093
1210
|
>
|
1094
|
-
|
1211
|
+
{isSubmitting ? "Loading..." : "Continue"}
|
1095
1212
|
</ButtonPrimary>
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
setCount((prev) => prev + 1);
|
1101
|
-
setFieldValue("rooms_attributes", [
|
1102
|
-
...values.rooms_attributes,
|
1103
|
-
initialRoomValues,
|
1104
|
-
]);
|
1105
|
-
}}
|
1106
|
-
className="action-button"
|
1107
|
-
>
|
1108
|
-
Add Another Room
|
1109
|
-
</ButtonPrimary>
|
1110
|
-
</div>
|
1111
|
-
|
1112
|
-
<div className="form-actions">
|
1113
|
-
<ButtonPrimary href={`/property-4/${slug}`}>
|
1114
|
-
Go back
|
1115
|
-
</ButtonPrimary>
|
1116
|
-
<ButtonPrimary
|
1117
|
-
type="submit"
|
1118
|
-
onClick={handleSubmit}
|
1119
|
-
disabled={isSubmitting}
|
1120
|
-
>
|
1121
|
-
{isSubmitting ? "Loading..." : "Continue"}
|
1122
|
-
</ButtonPrimary>
|
1123
|
-
</div>
|
1124
|
-
</form>
|
1125
|
-
)}
|
1213
|
+
</div>
|
1214
|
+
</form>
|
1215
|
+
);
|
1216
|
+
}}
|
1126
1217
|
</Formik>
|
1127
1218
|
</div>
|
1128
1219
|
</CommonLayout>
|