From 0922ff94de01b6cd973f0fc667fe83d0055fe35b Mon Sep 17 00:00:00 2001 From: maniamartial Date: Wed, 19 Nov 2025 11:49:29 +0300 Subject: [PATCH 1/5] feat: update the filters on service orders --- .../schedule/schedule-left-panel.tsx | 519 ++++++++++++------ 1 file changed, 359 insertions(+), 160 deletions(-) diff --git a/schedule/src/components/schedule/schedule-left-panel.tsx b/schedule/src/components/schedule/schedule-left-panel.tsx index bcf9b88..9c32316 100644 --- a/schedule/src/components/schedule/schedule-left-panel.tsx +++ b/schedule/src/components/schedule/schedule-left-panel.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, useMemo } from "react"; import { Checkbox } from "../ui/checkbox"; import { Badge } from "../ui/badge"; import { ScrollArea } from "../ui/scroll-area"; @@ -14,13 +14,14 @@ import { import { Skeleton } from "../ui/skeleton"; import { ServiceOrderDetailSheet } from "./service-order-detail-sheet"; import { MassActionsDropdown } from "./mass-actions-dropdown"; -import { Filter, CalendarIcon } from "lucide-react"; +import { Filter, CalendarIcon, XCircle } from "lucide-react"; import { Button } from "../ui/button"; import { Calendar } from "../ui/calendar"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { format } from "date-fns"; import { cn } from "../../lib/utils"; import { fetchServiceOrderDetail } from "../../hooks/use-appointments"; +import { Input } from "../ui/input"; const STATUS_OPTIONS: AppointmentStatus[] = [ "Open", @@ -95,6 +96,14 @@ export function ScheduleLeftPanel({ const [filtersPopoverOpen, setFiltersPopoverOpen] = useState(false); const [startDatePickerOpen, setStartDatePickerOpen] = useState(false); const [endDatePickerOpen, setEndDatePickerOpen] = useState(false); + const [appointmentSearchTerm, setAppointmentSearchTerm] = useState(""); + const [serviceOrderSearchTerm, setServiceOrderSearchTerm] = useState(""); + const [serviceOrderDateRange, setServiceOrderDateRange] = useState<{ + startDate: Date | null; + endDate: Date | null; + }>({ startDate: null, endDate: null }); + const [serviceOrderStartPickerOpen, setServiceOrderStartPickerOpen] = useState(false); + const [serviceOrderEndPickerOpen, setServiceOrderEndPickerOpen] = useState(false); const selectAllCheckboxRef = useRef(null); const allSelected = appointments.length > 0 && selectedAppointments.length === appointments.length; @@ -183,11 +192,70 @@ const getOrderStatusColor = (status?: string) => { ) ).sort(); - const filteredServiceOrders = serviceOrders.filter((order) => { - if (serviceOrderStatusFilter === "all") return true; - const status = (order.status || "").toLowerCase(); - return status === serviceOrderStatusFilter.toLowerCase(); - }); + const filteredServiceOrders = useMemo(() => { + const statusFiltered = serviceOrders.filter((order) => { + if (serviceOrderStatusFilter === "all") return true; + const status = (order.status || "").toLowerCase(); + return status === serviceOrderStatusFilter.toLowerCase(); + }); + + const normalizedStart = serviceOrderDateRange.startDate + ? new Date(serviceOrderDateRange.startDate) + : null; + if (normalizedStart) normalizedStart.setHours(0, 0, 0, 0); + const normalizedEnd = serviceOrderDateRange.endDate + ? new Date(serviceOrderDateRange.endDate) + : null; + if (normalizedEnd) normalizedEnd.setHours(23, 59, 59, 999); + + const dateFiltered = statusFiltered.filter((order) => { + if (!normalizedStart && !normalizedEnd) return true; + if (!order.posting_date) return false; + const postingDate = new Date(order.posting_date); + if (normalizedStart && postingDate < normalizedStart) return false; + if (normalizedEnd && postingDate > normalizedEnd) return false; + return true; + }); + + if (!serviceOrderSearchTerm.trim()) { + return dateFiltered; + } + + const term = serviceOrderSearchTerm.toLowerCase(); + return dateFiltered.filter((order) => { + const nameMatch = order.name?.toLowerCase().includes(term); + const customerMatch = order.customer?.toLowerCase().includes(term); + return nameMatch || customerMatch; + }); + }, [ + serviceOrders, + serviceOrderStatusFilter, + serviceOrderSearchTerm, + serviceOrderDateRange.startDate, + serviceOrderDateRange.endDate, + ]); + + const filteredAppointments = useMemo(() => { + if (!appointmentSearchTerm.trim()) { + return appointments; + } + const term = appointmentSearchTerm.toLowerCase(); + return appointments.filter((appointment) => { + const idMatch = appointment.name?.toLowerCase().includes(term); + const customerMatch = appointment.customer?.toLowerCase().includes(term); + return idMatch || customerMatch; + }); + }, [appointments, appointmentSearchTerm]); + + const isOrderMode = mode === "orders"; + + useEffect(() => { + setFiltersPopoverOpen(false); + setStartDatePickerOpen(false); + setEndDatePickerOpen(false); + setServiceOrderStartPickerOpen(false); + setServiceOrderEndPickerOpen(false); + }, [isOrderMode]); const handleServiceOrderClick = async (orderName: string) => { try { @@ -220,7 +288,13 @@ const getOrderStatusColor = (status?: string) => { if (!serviceOrdersLoading && filteredServiceOrders.length === 0) { return (
-

No service orders found

+

+ {serviceOrderSearchTerm.trim() || + serviceOrderDateRange.startDate || + serviceOrderDateRange.endDate + ? "No service orders match your filters" + : "No service orders found"} +

); } @@ -274,8 +348,6 @@ const getOrderStatusColor = (status?: string) => { ); }; - const isOrderMode = mode === "orders"; - return ( <>
@@ -288,7 +360,7 @@ const getOrderStatusColor = (status?: string) => { {isOrderMode ? "Service Orders" : "Service Appointments"} - {isOrderMode ? serviceOrders.length : appointments.length} + {isOrderMode ? filteredServiceOrders.length : filteredAppointments.length}
@@ -298,7 +370,7 @@ const getOrderStatusColor = (status?: string) => { className={cn( "w-full justify-center text-sm font-medium transition-colors", isOrderMode - ? "border-primary text-primary hover:bg-primary/5" + ? "border-primary text-primary hover:bg-primary/5 hover:text-primary" : "bg-primary text-primary-foreground" )} onClick={onModeToggle} @@ -306,158 +378,281 @@ const getOrderStatusColor = (status?: string) => { {isOrderMode ? "View Service Appointments" : "View Service Orders"} - {isOrderMode ? ( -
- -
- ) : ( - <> - {/* Filter Section */} -
- {/* Filter Menu Button */} - - - - - -
-
+
+ + + + + + {isOrderMode ? ( +
+
+

Search Orders

+ setServiceOrderSearchTerm(event.target.value)} + autoFocus + /> +
+
+

Date Range Filter

+ {(serviceOrderDateRange.startDate || serviceOrderDateRange.endDate) && ( + + )} +
+
+ + + + + + + { + setServiceOrderDateRange((prev) => ({ + ...prev, + startDate: date || null, + })); + setServiceOrderStartPickerOpen(false); + }} + initialFocus + /> + + +
- {/* Start Date */} -
- - - - - - - { - if (date) { - onDateRangeChange({ - ...appointmentDateRange, - startDate: date, - }); - setStartDatePickerOpen(false); - } - }} - initialFocus - /> - - -
+
+ + + + + + + { + setServiceOrderDateRange((prev) => ({ + ...prev, + endDate: date || null, + })); + setServiceOrderEndPickerOpen(false); + }} + initialFocus + /> + + +
+
+
+ ) : ( +
+
+

Search Appointments

+ setAppointmentSearchTerm(event.target.value)} + autoFocus + /> +
+
+
+

Date Range Filter

+ {(appointmentDateRange.startDate || appointmentDateRange.endDate) && ( + + )} +
- {/* End Date */} -
- - - - - - - { - if (date) { - onDateRangeChange({ - ...appointmentDateRange, - endDate: date, - }); - setEndDatePickerOpen(false); - } - }} - initialFocus - /> - - -
+
+ + + + + + + { + if (date) { + onDateRangeChange({ + ...appointmentDateRange, + startDate: date, + }); + setStartDatePickerOpen(false); + } + }} + initialFocus + /> + + +
+ +
+ + + + + + + { + if (date) { + onDateRangeChange({ + ...appointmentDateRange, + endDate: date, + }); + setEndDatePickerOpen(false); + } + }} + initialFocus + /> + +
- - - - {/* Status Filter - Always Visible */} -
- -
-
+
+ )} + + - {/* Mass Actions */} - {selectedAppointments.length > 0 && ( -
- -
+
+ {isOrderMode ? ( + + ) : ( + )} - +
+
+ + {!isOrderMode && selectedAppointments.length > 0 && ( +
+ +
)}
@@ -505,14 +700,18 @@ const getOrderStatusColor = (status?: string) => { )} {/* Appointments */} - {!loading && appointments.length === 0 && ( + {!loading && filteredAppointments.length === 0 && (
-

No appointments found

+

+ {appointmentSearchTerm.trim() + ? "No appointments match your search" + : "No appointments found"} +

)} {!loading && - appointments.map((appointment) => { + filteredAppointments.map((appointment) => { const isSelected = selectedAppointments.includes(appointment.name); const isCompleted = appointment.status === "Completed"; return ( From 28d779eb86cc151205449c6391e95f7b2b13a47e Mon Sep 17 00:00:00 2001 From: maniamartial Date: Wed, 19 Nov 2025 11:58:47 +0300 Subject: [PATCH 2/5] feat: Update on clicking create appintment from service order details it closes --- schedule/src/components/schedule/service-order-detail-sheet.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/schedule/src/components/schedule/service-order-detail-sheet.tsx b/schedule/src/components/schedule/service-order-detail-sheet.tsx index a2b4af5..a020fbe 100644 --- a/schedule/src/components/schedule/service-order-detail-sheet.tsx +++ b/schedule/src/components/schedule/service-order-detail-sheet.tsx @@ -62,6 +62,7 @@ export function ServiceOrderDetailSheet({ size="sm" className="bg-primary text-primary-foreground" onClick={() => { + if (!order) return; const event = new CustomEvent("open-create-appointment", { detail: { service_order: order.name, @@ -69,6 +70,7 @@ export function ServiceOrderDetailSheet({ }, }); window.dispatchEvent(event); + onOpenChange(false); }} > Create Appointment From 585d17f6dcc59edf582a97f41ee0a1f06b33424c Mon Sep 17 00:00:00 2001 From: maniamartial Date: Wed, 19 Nov 2025 12:21:32 +0300 Subject: [PATCH 3/5] fix: Update the size of product movement page --- .../components/service-request/product-tracking.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/schedule/src/components/service-request/product-tracking.tsx b/schedule/src/components/service-request/product-tracking.tsx index 5e52497..8079ebe 100644 --- a/schedule/src/components/service-request/product-tracking.tsx +++ b/schedule/src/components/service-request/product-tracking.tsx @@ -109,9 +109,9 @@ export function ServiceOrdersView() { filteredOrders.find((req) => req.name === selectedOrderId) || filteredOrders[0] || null; return ( -
+
{/* Left Pane */} -
+
@@ -206,7 +206,7 @@ export function ServiceOrdersView() {
{/* Right Pane */} -
+
{selectedOrder ? (
@@ -281,9 +281,8 @@ export function ServiceOrdersView() { Date - Destination - Linked Document - Service Order + Destination + Linked Document Handled By @@ -310,7 +309,6 @@ export function ServiceOrdersView() { "—" )} - {movement.service_order || "—"} {movement.handled_by || "—"} ))} From c3111e6e522920f0fae0a0d2550ef3b3994142e1 Mon Sep 17 00:00:00 2001 From: maniamartial Date: Wed, 19 Nov 2025 12:24:31 +0300 Subject: [PATCH 4/5] fix: reduce size of footer --- schedule/src/pages/schedule/schedule.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schedule/src/pages/schedule/schedule.tsx b/schedule/src/pages/schedule/schedule.tsx index 1e93919..2d5bf4c 100644 --- a/schedule/src/pages/schedule/schedule.tsx +++ b/schedule/src/pages/schedule/schedule.tsx @@ -231,7 +231,7 @@ export default function SchedulePage() {
-
+
Powered By Beveren Software
From d8085cbd64f410b5f0de86a2d74dc53c1b7c4653 Mon Sep 17 00:00:00 2001 From: maniamartial Date: Wed, 19 Nov 2025 13:23:35 +0300 Subject: [PATCH 5/5] fix: update the timer to remove the lefta nd right button --- .../src/components/schedule/gantt-view.tsx | 66 ++----------------- 1 file changed, 4 insertions(+), 62 deletions(-) diff --git a/schedule/src/components/schedule/gantt-view.tsx b/schedule/src/components/schedule/gantt-view.tsx index 0dda801..e58bfca 100644 --- a/schedule/src/components/schedule/gantt-view.tsx +++ b/schedule/src/components/schedule/gantt-view.tsx @@ -1,14 +1,11 @@ "use client"; import { useState, useEffect, useMemo, useRef } from "react"; -import { Button } from "../ui/button"; -import { ChevronLeft, ChevronRight } from "lucide-react"; import { Appointment } from "../../pages/schedule/types"; -import { fetchTechnicians, reallocateAppointment, createAppointment, fetchServiceOrders, fetchServiceTypes, fetchItems, fetchAvailableServiceOrders } from "../../hooks/use-appointments"; +import { fetchTechnicians, reallocateAppointment, createAppointment, fetchServiceTypes, fetchItems, fetchAvailableServiceOrders } from "../../hooks/use-appointments"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"; import { toast } from "../ui/use-toast"; import { format, startOfDay, parse } from "date-fns"; -import { cn } from "../../lib/utils"; interface GanttViewProps { appointments: Appointment[]; @@ -47,8 +44,6 @@ export function GanttView({ const [optionsServiceOrders, setOptionsServiceOrders] = useState>([]); const [optionsServiceTypes, setOptionsServiceTypes] = useState>([]); const [optionsItems, setOptionsItems] = useState>([]); - const [canScrollLeft, setCanScrollLeft] = useState(false); - const [canScrollRight, setCanScrollRight] = useState(false); useEffect(() => { // Load master data for create modal @@ -174,31 +169,6 @@ export function GanttView({ const scrollContainerRef = useRef(null); const timelineContentRef = useRef(null); - const scrollTimeline = (direction: "left" | "right") => { - const container = scrollContainerRef.current; - if (!container) return; - const delta = direction === "left" ? -HOUR_COLUMN_WIDTH * 3 : HOUR_COLUMN_WIDTH * 3; - container.scrollBy({ left: delta, behavior: "smooth" }); - }; - - useEffect(() => { - const container = scrollContainerRef.current; - if (!container) return; - - const updateScrollState = () => { - const maxScrollLeft = container.scrollWidth - container.clientWidth; - setCanScrollLeft(container.scrollLeft > 0); - setCanScrollRight(container.scrollLeft < maxScrollLeft - 1); - }; - - updateScrollState(); - container.addEventListener("scroll", updateScrollState); - window.addEventListener("resize", updateScrollState); - return () => { - container.removeEventListener("scroll", updateScrollState); - window.removeEventListener("resize", updateScrollState); - }; - }, []); const toFrappeDateTime = (date: Date) => { const pad = (n: number) => String(n).padStart(2, "0"); @@ -309,25 +279,9 @@ export function GanttView({ className="relative flex-shrink-0" style={{ width: `${TIMELINE_WIDTH}px` }} > - {/* Time Column Headers with Scroll Arrows */} -
- {/* Left Arrow Button */} - {canScrollLeft && ( - - )} - - {/* Time Labels */} -
+ {/* Time Column Headers */} +
+
{ALL_HOURS.map((hour) => (
))}
- - {/* Right Arrow Button */} - {canScrollRight && ( - - )}
{/* Technician Rows with Appointments */}