From 62777a67dec141b53122d67f59f71355167a0ba6 Mon Sep 17 00:00:00 2001 From: jaanvi Date: Fri, 11 Apr 2025 18:30:41 +0530 Subject: [PATCH] image slotbooking integration and ui changes --- src/components/AddSlotModal/addSlotModal.tsx | 107 ++--- .../AddVehicleModal/addVehicleModal.tsx | 446 +++++++++++------- src/components/CustomTable/customTable.tsx | 88 ++-- .../EditVehicleModal/editVehicleModal.tsx | 100 ++-- src/components/MainGrid/mainGrid.tsx | 2 +- src/components/MenuContent/index.tsx | 2 +- .../Modals/VehicleViewModal/index.tsx | 35 +- src/components/SideMenu/sideMenu.tsx | 123 +++-- src/pages/EvSlotList/index.tsx | 133 ++++-- src/pages/VehicleList/index.tsx | 94 ++-- src/redux/slices/VehicleSlice.ts | 70 ++- src/redux/slices/slotSlice.ts | 72 ++- 12 files changed, 786 insertions(+), 486 deletions(-) diff --git a/src/components/AddSlotModal/addSlotModal.tsx b/src/components/AddSlotModal/addSlotModal.tsx index 930bb2e..1986283 100644 --- a/src/components/AddSlotModal/addSlotModal.tsx +++ b/src/components/AddSlotModal/addSlotModal.tsx @@ -164,8 +164,10 @@ import { InputLabel, } from "@mui/material"; import { useForm } from "react-hook-form"; +import { useDispatch } from "react-redux"; // Import the Redux dispatch +import { createSlot } from "../../redux/slices/slotSlice.ts"; // Assuming this is your slice -const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => { +const AddSlotModal = ({ open, handleClose }: any) => { const { register, handleSubmit, @@ -173,6 +175,8 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => { watch, formState: { errors }, } = useForm(); + const dispatch = useDispatch(); // Get dispatch from Redux + const [isAvailable, setIsAvailable] = useState(true); const [isDateRange, setIsDateRange] = useState(false); const [durationUnit, setDurationUnit] = useState("minutes"); @@ -190,60 +194,41 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => { } }, [startHour]); - const onSubmit = (data: any) => { - const { date, startDate, endDate, startHour, endHour, duration } = data; - const slots: { date: string; startHour: string; endHour: string; isAvailable: boolean; duration: number; }[] = []; +const onSubmit = (data: any) => { + const { + date, + startingDate, + endingDate, + startHour, + endHour, + duration, + + } = data; - const generateSlotsForDate = (date: string) => { - const startTime = new Date(`1970-01-01T${startHour}:00`); - const endTime = new Date(`1970-01-01T${endHour}:00`); - let durationMinutes = parseInt(duration, 10); + const payload = isDateRange + ? { + startingDate, + endingDate, + startHour, + endHour, + duration: parseInt(duration, 10), + isAvailable, + + } + : { + date, + startHour, + endHour, + duration: parseInt(duration, 10), + isAvailable, + + }; - if (durationUnit === "hours") { - durationMinutes *= 60; - } + dispatch(createSlot(payload)); + reset(); + handleClose(); +}; - for ( - let time = startTime; - time < endTime; - time.setMinutes(time.getMinutes() + durationMinutes) - ) { - const slotEndTime = new Date(time); - slotEndTime.setMinutes( - slotEndTime.getMinutes() + durationMinutes - ); - - if (slotEndTime <= endTime) { - slots.push({ - date, - startHour: time.toTimeString().slice(0, 5), - endHour: slotEndTime.toTimeString().slice(0, 5), - isAvailable, - duration: durationMinutes, - }); - } - } - }; - - if (isDateRange) { - const start = new Date(startDate); - const end = new Date(endDate); - for ( - let d = new Date(start); - d <= end; - d.setDate(d.getDate() + 1) - ) { - const dateString = d.toISOString().split("T")[0]; - generateSlotsForDate(dateString); - } - } else { - generateSlotsForDate(date); - } - - handleAddSlot(slots); - reset(); - handleClose(); - }; return ( @@ -265,7 +250,7 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => { Start Date value >= today || @@ -274,15 +259,15 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => { type="date" fullWidth margin="normal" - error={!!errors.startDate} - helperText={errors.startDate?.message} + error={!!errors.startingDate} + helperText={errors.startingDate?.message} inputProps={{ min: today }} /> End Date value >= today || @@ -291,8 +276,8 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => { type="date" fullWidth margin="normal" - error={!!errors.endDate} - helperText={errors.endDate?.message} + error={!!errors.endingDate} + helperText={errors.endingDate?.message} inputProps={{ min: today }} /> @@ -366,13 +351,13 @@ const AddSlotModal = ({ open, handleClose, handleAddSlot }: any) => { helperText={errors.duration?.message} /> - Unit + ( + + )} + /> + + + + + Company + + ( + + )} /> - {/* Submit Button */} + {/* Second Row - Two Inputs */} + + + + Model Name + + ( + + )} + /> + + + + + Charge Type + + ( + + )} + /> + + + + {/* Image Upload */} + + Upload Image + + {errors.imageFile && ( + + {errors.imageFile.message} + + )} + {imagePreview && ( + + + Preview: + + image preview + + )} - + + + {/* Submit Button */} + + + ); diff --git a/src/components/CustomTable/customTable.tsx b/src/components/CustomTable/customTable.tsx index 5e1c6f3..3fd21ba 100644 --- a/src/components/CustomTable/customTable.tsx +++ b/src/components/CustomTable/customTable.tsx @@ -41,6 +41,7 @@ import { fetchAvailableSlots, } from "../../redux/slices/slotSlice.ts"; import { bookingList, deleteBooking } from "../../redux/slices/bookSlice.ts"; +import AddCircleIcon from "@mui/icons-material/AddCircle"; // Styled components for customization const StyledTableCell = styled(TableCell)(({ theme }) => ({ [`&.${tableCellClasses.head}`]: { @@ -49,7 +50,7 @@ const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderBottom: "none", // Remove any border at the bottom of the header }, [`&.${tableCellClasses.body}`]: { - fontSize: 14, + fontSize: "16px", borderBottom: "1px solid #454545", // Adding border to body cells }, })); @@ -72,6 +73,7 @@ export interface Column { id: string; label: string; align?: "left" | "center" | "right"; + } interface Row { @@ -246,8 +248,8 @@ const CustomTable: React.FC = ({ {/* Dynamic title based on the page type */} @@ -306,7 +308,7 @@ const CustomTable: React.FC = ({ height: "44px", borderWidth: "1px", padding: "14px 12px 14px 12px", - gap: "16px", + "& fieldset": { borderColor: "#FFFFFF" }, "&:hover fieldset": { borderColor: "#FFFFFF" }, "&.Mui-focused fieldset": { @@ -350,12 +352,15 @@ const CustomTable: React.FC = ({ sx={{ backgroundColor: "#52ACDF", color: "white", - borderRadius: "8px", - width: "184px", + minWidth: "115px", // Start small but allow it to grow + maxWidth: "250px", // Optional: limit it from being *too* wide marginRight: "16px", + paddingX: "16px", + whiteSpace: "nowrap", // Prevents text from wrapping "&:hover": { backgroundColor: "#439BC1" }, }} - onClick={() => handleClickOpen()} + onClick={handleClickOpen} + //startIcon={} // <-- this adds the icon! > Add{" "} {(() => { @@ -450,8 +455,7 @@ const CustomTable: React.FC = ({ backgroundColor: "#272727", boxShadow: "-5px 0 5px -2px rgba(0,0,0,0.15)", - borderBottom: - "1px solid #454545", + borderBottom: "1px solid #454545", }), }} > @@ -533,6 +537,7 @@ const CustomTable: React.FC = ({ style={{ width: "50px", height: "50px", + borderRadius: "50%", objectFit: "cover", }} /> @@ -576,30 +581,42 @@ const CustomTable: React.FC = ({ marginTop: "16px", }} > - - Page Number : - - + {filteredRows.length > 0 && ( + <> + + Page Number : + + + + )} + {/* Menu Actions */} {open && ( = ({ onClick={(e) => { e.stopPropagation(); setViewModal(true); - - }} color="primary" sx={{ @@ -642,7 +657,6 @@ const CustomTable: React.FC = ({ handleViewButton(selectedRow?.id) - } open={viewModal} setViewModal={setViewModal} @@ -701,7 +715,7 @@ const CustomTable: React.FC = ({ setModalOpen(true); // Only open if a row is selected setRowData(selectedRow); } - handleClose(); + handleClose(); }} color="primary" sx={{ @@ -759,7 +773,7 @@ const CustomTable: React.FC = ({ onClick={(e) => { e.stopPropagation(); setDeleteModal(true); - handleClose(); + handleClose(); }} color="error" sx={{ diff --git a/src/components/EditVehicleModal/editVehicleModal.tsx b/src/components/EditVehicleModal/editVehicleModal.tsx index 3501516..0f7e8db 100644 --- a/src/components/EditVehicleModal/editVehicleModal.tsx +++ b/src/components/EditVehicleModal/editVehicleModal.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react"; import { Box, Button, Typography, Modal } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; import { useForm, Controller } from "react-hook-form"; -import { updateVehicle } from "../../redux/slices/VehicleSlice"; import { CustomIconButton, CustomTextField } from "../AddUserModal/styled.css"; interface EditVehicleModalProps { @@ -49,53 +48,61 @@ const EditVehicleModal: React.FC = ({ }, }); - // Set values if editRow is provided const [imagePreview, setImagePreview] = useState(null); + // Set form values and image preview when editRow changes useEffect(() => { if (editRow) { - // Construct full image URL - const imageUrl = `${process.env.REACT_APP_BACKEND_URL}/image/${editRow.imageUrl}`; - setImagePreview(imageUrl); // Set the image URL to display the preview - setValue("name", editRow.name); - setValue("company", editRow.company); - setValue("modelName", editRow.modelName); - setValue("chargeType", editRow.chargeType); + // Set form fields + setValue("name", editRow.name || ""); + setValue("company", editRow.company || ""); + setValue("modelName", editRow.modelName || ""); + setValue("chargeType", editRow.chargeType || ""); + + // Set image preview for existing image + if (editRow?.imageUrl) { + const imageUrl = + editRow.imageUrl.startsWith("http") || + editRow.imageUrl.startsWith("blob") + ? editRow.imageUrl + : `${process.env.REACT_APP_BACKEND_URL}/image/${editRow.imageUrl}`; + + setImagePreview(imageUrl); + } else { + setImagePreview(null); + } + } else { + // Reset form and preview when no editRow reset(); + setImagePreview(null); } }, [editRow, setValue, reset]); + // Handle image upload + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + setImagePreview(URL.createObjectURL(file)); // Show preview of new image + setValue("imageUrl", file); // Update form with new file + } + }; + // Handle form submission + const onSubmit = (data: FormData) => { + handleUpdate( + editRow.id, + data.name, + data.company, + data.modelName, + data.chargeType, + data.imageUrl // Pass File | null to handleUpdate + ); -const handleImageChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (file) { - setImagePreview(URL.createObjectURL(file)); // Preview the image - setValue("imageUrl", file); // Set the file in the form - } -}; - -const onSubmit = (data: FormData) => { - // Check if a new image was selected and set its URL or filename - const imageUrl = data.imageUrl - ? `${process.env.REACT_APP_BACKEND_URL}/image/${data.imageUrl.name}` // Assuming image is a file object - : editRow?.imageUrl; // Keep existing image if not changed - - handleUpdate( - editRow.id, - data.name, - data.company, - data.modelName, - data.chargeType, - imageUrl // Send the updated image URL or filename to backend - ); - - handleClose(); - reset(); -}; - - + handleClose(); + reset(); + setImagePreview(null); // Clear preview after submission + }; return ( { if (reason === "backdropClick") { return; } - handleClose(); // Close modal when clicking cross or cancel + handleClose(); }} aria-labelledby="edit-vehicle-modal" > @@ -176,7 +183,7 @@ const onSubmit = (data: FormData) => { "Maximum 30 characters allowed", }, pattern: { - value: /^[A-Za-z\s]+$/, // Only letters and spaces are allowed + value: /^[A-Za-z\s]+$/, message: "Vehicle Name must only contain letters and spaces", }, @@ -307,17 +314,17 @@ const onSubmit = (data: FormData) => { Upload Image */} + +