From 8878c9c45bf8efce2dd5b1ce5d76a7122de43e39 Mon Sep 17 00:00:00 2001 From: Lixian Zhou Date: Thu, 12 Mar 2026 18:11:37 -0400 Subject: [PATCH] fix --- .../components/signature/DriverSignature.js | 44 +++++++++----- .../trans-routes/RouteReportWithSignature.js | 59 +++++++++++++++++-- .../trans-routes/RoutesDashboard.js | 52 +++++++++++++--- .../trans-routes/RoutesSignature.js | 52 +++++++++++++--- 4 files changed, 171 insertions(+), 36 deletions(-) diff --git a/client/src/components/signature/DriverSignature.js b/client/src/components/signature/DriverSignature.js index 06739e2..03ed419 100644 --- a/client/src/components/signature/DriverSignature.js +++ b/client/src/components/signature/DriverSignature.js @@ -1,11 +1,10 @@ import React, {useState, useEffect} from "react"; -import { useNavigate, useParams } from "react-router-dom"; +import { useParams } from "react-router-dom"; import { CustomerService, SignatureRequestService } from "../../services"; import SignatureCanvas from 'react-signature-canvas'; import moment from 'moment'; const DriverSignature = () => { - const navigate = useNavigate(); const urlParams = useParams(); const [signatureRequest, setSignatureRequest] = useState(undefined); @@ -19,28 +18,45 @@ const DriverSignature = () => { } const submitSignature = () => { - var fabricCanvas = document.getElementsByClassName('sigCanvas')[0]; //real ID here - var scaledCanvas = document.createElement('canvas'); //off-screen canvas - - scaledCanvas.width = 400; //size of new canvas, make sure they are proportional - scaledCanvas.height = 200; //compared to original canvas - - // scale original image to new canvas - var ctx = scaledCanvas.getContext('2d'); - ctx.drawImage(fabricCanvas, 0, 0, scaledCanvas.width, scaledCanvas.height); + const fabricCanvas = document.getElementsByClassName('sigCanvas')[0]; + if (!fabricCanvas || !signatureRequest?.id) { + window.alert('Signature request is not ready. Please refresh and try again.'); + return; + } + const routeId = `${signatureRequest?.route_id || ''}`.trim(); + const driverId = `${signatureRequest?.driver_id || ''}`.trim(); const dateArr = moment(signatureRequest?.route_date)?.format('MM/DD/YYYY')?.split('/') || []; - const fileName = `${signatureRequest?.route_id}_${signatureRequest?.driver_id}_${dateArr[0]}_${dateArr[1]}`; + const month = `${dateArr[0] || ''}`.trim(); + const day = `${dateArr[1] || ''}`.trim(); + if (!routeId || !driverId || !month || !day || month.toLowerCase().includes('invalid')) { + window.alert('Invalid route signature key. Please ask admin to regenerate signature link.'); + return; + } + const fileName = `${routeId}_${driverId}_${month}_${day}`; + const scaledCanvas = document.createElement('canvas'); + + scaledCanvas.width = 400; + scaledCanvas.height = 200; + + const ctx = scaledCanvas.getContext('2d'); + ctx.drawImage(fabricCanvas, 0, 0, scaledCanvas.width, scaledCanvas.height); scaledCanvas.toBlob(function(blob) { + if (!blob) { + window.alert('Failed to read signature image. Please draw signature again.'); + return; + } const formData = new FormData(); formData.append('file', blob, `${fileName}.jpg`); CustomerService.uploadAvatar(fileName, formData).then(() => { - SignatureRequestService.updateSignatureRequest(signatureRequest?.id, { status: 'done'}).then(data => { + SignatureRequestService.updateSignatureRequest(signatureRequest?.id, { status: 'done'}).then(() => { SignatureRequestService.getSignatureRequestById(urlParams.id).then((data) => { setSignatureRequest(data?.data); }) }) - }) + }).catch(() => { + window.alert('Failed to upload signature. Please try again.'); + }); }) } diff --git a/client/src/components/trans-routes/RouteReportWithSignature.js b/client/src/components/trans-routes/RouteReportWithSignature.js index 8706df5..5aac580 100644 --- a/client/src/components/trans-routes/RouteReportWithSignature.js +++ b/client/src/components/trans-routes/RouteReportWithSignature.js @@ -67,17 +67,64 @@ const RouteReportWithSignature = () => { } useEffect(() => { - const dateArr = moment(currentRoute?.schedule_date)?.format('MM/DD/YYYY')?.split('/') || []; - - CustomerService.getAvatar(`${currentRoute?.id}_${currentRoute?.driver}_${dateArr[0]}_${dateArr[1]}`).then(data => { - setSignature(data.data); - }); + let isMounted = true; + const toDateTokens = (dateValue) => { + const tokenSet = new Set(); + const localFormatted = moment(dateValue)?.format('MM/DD/YYYY'); + const utcFormatted = moment.utc(dateValue)?.format('MM/DD/YYYY'); + [localFormatted, utcFormatted].forEach((formatted) => { + if (!formatted || formatted.toLowerCase().includes('invalid')) return; + const [month, day] = formatted.split('/'); + if (month && day) { + tokenSet.add(`${month}_${day}`); + } + }); + return Array.from(tokenSet).map((token) => token.split('_')); + }; + const loadSignature = async () => { + if (!currentRoute?.id || !currentRoute?.schedule_date) { + if (isMounted) setSignature(undefined); + return; + } + const routeId = currentRoute.id; + const requestRes = await SignatureRequestService.getAllSignatureRequests({ route_id: routeId }).catch(() => ({ data: [] })); + const requestList = requestRes?.data || []; + const driverIds = Array.from(new Set([ + currentRoute?.driver, + ...requestList.map((item) => item?.driver_id) + ].filter(Boolean))); + const datePairs = []; + [currentRoute?.schedule_date, ...requestList.map((item) => item?.route_date)].forEach((dateValue) => { + toDateTokens(dateValue).forEach((pair) => datePairs.push(pair)); + }); + const uniqueDatePairs = Array.from(new Set(datePairs.map((pair) => `${pair[0]}_${pair[1]}`))) + .map((token) => token.split('_')); + for (const driverId of driverIds) { + for (const [month, day] of uniqueDatePairs) { + try { + const data = await CustomerService.getAvatar(`${routeId}_${driverId}_${month}_${day}`); + if (data?.data) { + if (isMounted) setSignature(data.data); + return; + } + } catch (error) { + // Continue trying candidate keys. + } + } + } + if (isMounted) setSignature(undefined); + }; + + loadSignature(); CustomerService.getAvatar(`center_director_signature_site_${site}`).then(data => { if (data?.data) { setDirectorSignature(data?.data) } }); - }, [currentRoute]); + return () => { + isMounted = false; + }; + }, [currentRoute?.id, currentRoute?.driver, currentRoute?.schedule_date, site]); useEffect(() => { CustomerService.getAllCustomers() diff --git a/client/src/components/trans-routes/RoutesDashboard.js b/client/src/components/trans-routes/RoutesDashboard.js index 92658fd..d00ac02 100644 --- a/client/src/components/trans-routes/RoutesDashboard.js +++ b/client/src/components/trans-routes/RoutesDashboard.js @@ -4,7 +4,7 @@ import { useNavigate, useSearchParams } from "react-router-dom"; import { selectInboundRoutes, selectTomorrowAllRoutes, selectTomorrowInboundRoutes, selectTomorrowOutboundRoutes, selectHistoryInboundRoutes, selectHistoryRoutes, selectHistoryOutboundRoutes, selectOutboundRoutes, selectAllRoutes, selectAllActiveVehicles, selectAllActiveDrivers, transRoutesSlice } from "./../../store"; import RoutesSection from "./RoutesSection"; import PersonnelSection from "./PersonnelSection"; -import { AuthService, CustomerService, TransRoutesService, DriverService, EventsService, DailyRoutesTemplateService, ReportService } from "../../services"; +import { AuthService, CustomerService, TransRoutesService, DriverService, EventsService, DailyRoutesTemplateService, ReportService, SignatureRequestService } from "../../services"; import { PERSONAL_ROUTE_STATUS, ROUTE_STATUS, CUSTOMER_TYPE_TEXT, PERSONAL_ROUTE_STATUS_TEXT, PICKUP_STATUS, PICKUP_STATUS_TEXT, REPORT_TYPE } from "../../shared"; import moment from 'moment'; import DatePicker from "react-datepicker"; @@ -710,16 +710,52 @@ const RoutesDashboard = () => { }, []); useEffect(() => { + const toDateTokens = (dateValue) => { + const tokenSet = new Set(); + const localFormatted = moment(dateValue)?.format('MM/DD/YYYY'); + const utcFormatted = moment.utc(dateValue)?.format('MM/DD/YYYY'); + [localFormatted, utcFormatted].forEach((formatted) => { + if (!formatted || formatted.toLowerCase().includes('invalid')) return; + const [month, day] = formatted.split('/'); + if (month && day) { + tokenSet.add(`${month}_${day}`); + } + }); + return Array.from(tokenSet).map((token) => token.split('_')); + }; + const resolveRouteSignature = async (routeItem) => { + const requestRes = await SignatureRequestService.getAllSignatureRequests({ route_id: routeItem?.id }).catch(() => ({ data: [] })); + const requestList = requestRes?.data || []; + const driverIds = Array.from(new Set([ + routeItem?.driver, + ...requestList.map((item) => item?.driver_id) + ].filter(Boolean))); + const datePairs = []; + [routeItem?.schedule_date, dateSelected, ...requestList.map((item) => item?.route_date)].forEach((dateValue) => { + toDateTokens(dateValue).forEach((pair) => datePairs.push(pair)); + }); + const uniqueDatePairs = Array.from(new Set(datePairs.map((pair) => `${pair[0]}_${pair[1]}`))) + .map((token) => token.split('_')); + for (const driverId of driverIds) { + for (const [month, day] of uniqueDatePairs) { + try { + const result = await CustomerService.getAvatar(`${routeItem.id}_${driverId}_${month}_${day}`); + if (result?.data) { + return result.data; + } + } catch (ex) { + // Continue trying candidate keys. + } + } + } + return undefined; + }; + TransRoutesService.getAll(moment(dateSelected)?.format('MM/DD/YYYY')).then(data => { const routesResults = data.data; const finalRoutes = routesResults.map(async (routeItem) => { - const dateArr = moment(dateSelected)?.format('MM/DD/YYYY')?.split('/') || []; - try { - const result = await CustomerService.getAvatar(`${routeItem.id}_${routeItem.driver}_${dateArr[0]}_${dateArr[1]}`); - return result?.data ? Object.assign({}, routeItem, {signature: result?.data}) : routeItem; - } catch (ex) { - return routeItem; - } + const signature = await resolveRouteSignature(routeItem); + return signature ? Object.assign({}, routeItem, {signature}) : routeItem; }); Promise.all(finalRoutes).then(finalRoutesData => { setRoutesForSignature(finalRoutesData); diff --git a/client/src/components/trans-routes/RoutesSignature.js b/client/src/components/trans-routes/RoutesSignature.js index e0d08f5..af9113d 100644 --- a/client/src/components/trans-routes/RoutesSignature.js +++ b/client/src/components/trans-routes/RoutesSignature.js @@ -1,7 +1,7 @@ import React, {useState, useEffect} from "react"; import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; -import { CustomerService, DriverService, EmployeeService, EventsService, TransRoutesService } from "../../services"; +import { CustomerService, DriverService, EmployeeService, EventsService, SignatureRequestService, TransRoutesService } from "../../services"; import { selectAllActiveDrivers, selectAllActiveVehicles } from "./../../store"; import moment from 'moment'; import DatePicker from "react-datepicker"; @@ -95,6 +95,47 @@ const RouteSignatureList = () => { useEffect(() => { const site = EventsService.site; + const toDateTokens = (dateValue) => { + const tokenSet = new Set(); + const localFormatted = moment(dateValue)?.format('MM/DD/YYYY'); + const utcFormatted = moment.utc(dateValue)?.format('MM/DD/YYYY'); + [localFormatted, utcFormatted].forEach((formatted) => { + if (!formatted || formatted.toLowerCase().includes('invalid')) return; + const [month, day] = formatted.split('/'); + if (month && day) { + tokenSet.add(`${month}_${day}`); + } + }); + return Array.from(tokenSet).map((token) => token.split('_')); + }; + const resolveRouteSignature = async (routeItem) => { + const requestRes = await SignatureRequestService.getAllSignatureRequests({ route_id: routeItem?.id }).catch(() => ({ data: [] })); + const requestList = requestRes?.data || []; + const driverIds = Array.from(new Set([ + routeItem?.driver, + ...requestList.map((item) => item?.driver_id) + ].filter(Boolean))); + const datePairs = []; + [routeItem?.schedule_date, dateSelected, ...requestList.map((item) => item?.route_date)].forEach((dateValue) => { + toDateTokens(dateValue).forEach((pair) => datePairs.push(pair)); + }); + const uniqueDatePairs = Array.from(new Set(datePairs.map((pair) => `${pair[0]}_${pair[1]}`))) + .map((token) => token.split('_')); + for (const driverId of driverIds) { + for (const [month, day] of uniqueDatePairs) { + try { + const result = await CustomerService.getAvatar(`${routeItem.id}_${driverId}_${month}_${day}`); + if (result?.data) { + return result.data; + } + } catch (ex) { + // Continue trying candidate keys. + } + } + } + return undefined; + }; + CustomerService.getAvatar(`center_director_signature_site_${site}`).then(data => { if (data?.data) { setDirectorSignature(data?.data) @@ -103,13 +144,8 @@ const RouteSignatureList = () => { TransRoutesService.getAll(moment(dateSelected)?.format('MM/DD/YYYY')).then(data => { const routesResults = data.data; const finalRoutes = routesResults.map(async (routeItem) => { - const dateArr = moment(dateSelected)?.format('MM/DD/YYYY')?.split('/') || []; - try { - const result = await CustomerService.getAvatar(`${routeItem.id}_${routeItem.driver}_${dateArr[0]}_${dateArr[1]}`); - return result?.data ? Object.assign({}, routeItem, {signature: result?.data}) : routeItem; - } catch (ex) { - return routeItem; - } + const signature = await resolveRouteSignature(routeItem); + return signature ? Object.assign({}, routeItem, {signature}) : routeItem; }); Promise.all(finalRoutes).then(finalRoutesData => { setRoutes(finalRoutesData);