@nerdjs/sales-kit 4.0.56 → 4.0.58

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.
Files changed (43) hide show
  1. package/dist/entities/quote/quote.d.ts +2 -1
  2. package/dist/helpers.d.ts +9 -0
  3. package/dist/helpers.js +54 -0
  4. package/dist/helpers.js.map +1 -1
  5. package/dist/hooks/lep/lepLoadLocation.d.ts +0 -6
  6. package/dist/hooks/lep/lepLoadLocation.js +207 -177
  7. package/dist/hooks/lep/lepLoadLocation.js.map +1 -1
  8. package/dist/hooks/lep/lepShipcons.js +25 -7
  9. package/dist/hooks/lep/lepShipcons.js.map +1 -1
  10. package/dist/hooks/lep/nerdMap/nerdMap.js +28 -40
  11. package/dist/hooks/lep/nerdMap/nerdMap.js.map +1 -1
  12. package/dist/hooks/locationsForm/locationFormSales/locationFormSales.d.ts +1 -1
  13. package/dist/hooks/locationsForm/locationFormSales/locationFormSales.js +4 -3
  14. package/dist/hooks/locationsForm/locationFormSales/locationFormSales.js.map +1 -1
  15. package/dist/hooks/locationsForm/locationFormSales/locationFormSalesMain.d.ts +0 -5
  16. package/dist/hooks/locationsForm/locationFormSales/locationFormSalesMain.js +173 -56
  17. package/dist/hooks/locationsForm/locationFormSales/locationFormSalesMain.js.map +1 -1
  18. package/dist/hooks/quote/constants.d.ts +1 -1
  19. package/dist/hooks/quote/constants.js +2 -2
  20. package/dist/hooks/quote/constants.js.map +1 -1
  21. package/dist/hooks/quote/form.js +16 -5
  22. package/dist/hooks/quote/form.js.map +1 -1
  23. package/dist/hooks/quote/helpers.d.ts +1 -0
  24. package/dist/hooks/quote/helpers.js +263 -327
  25. package/dist/hooks/quote/helpers.js.map +1 -1
  26. package/dist/hooks/quote/template.js +2 -2
  27. package/dist/hooks/quote/template.js.map +1 -1
  28. package/dist/redux/commoditiesTasksSlice/commoditiesTasksSelectors.d.ts +2 -2
  29. package/dist/redux/customerV1/customerV1Endpoints.d.ts +2 -2
  30. package/dist/redux/lep/lepSelectors.d.ts +6 -6
  31. package/dist/redux/load/loadDrawerSlice.d.ts +1 -1
  32. package/dist/redux/loadInfoTasksSlice/loadInfoTasksSelectors.d.ts +10 -10
  33. package/dist/redux/locationV1/locationV1Endpoints.d.ts +12 -12
  34. package/dist/redux/quote/quoteAction.d.ts +2 -3
  35. package/dist/redux/quote/quoteAction.js +2 -3
  36. package/dist/redux/quote/quoteAction.js.map +1 -1
  37. package/dist/redux/quote/quoteCustomerPortalEndpoints.d.ts +1 -1
  38. package/dist/redux/quote/quoteReducer.js +5 -5
  39. package/dist/redux/quote/quoteReducer.js.map +1 -1
  40. package/dist/redux/quote/quoteSelectors.d.ts +23 -23
  41. package/dist/redux/shipconsTasksSlice/shipconsTasksSelectors.d.ts +2 -2
  42. package/dist/redux/tender/tenderSelector.d.ts +7 -7
  43. package/package.json +2 -2
@@ -20,6 +20,7 @@ import { LocationForm } from "../locationsForm";
20
20
  import { FREIGHT_CLASS, LTL, SHIPPERS, TL } from "./constants";
21
21
  import { LocationFormSales } from "../locationsForm/locationFormSales/locationFormSales";
22
22
  import { unstable_debounce as debounce } from "@mui/utils";
23
+ import { useGoogleMapsAutocompleteCache } from "../../helpers";
23
24
  export const getStopsColumnDef = (dispatch, stopKind, rows, customerPortal, onEditLocationClick, errors) => {
24
25
  const date = {
25
26
  headerName: "Date",
@@ -47,7 +48,7 @@ export const getStopsColumnDef = (dispatch, stopKind, rows, customerPortal, onEd
47
48
  editable: false,
48
49
  width: 40,
49
50
  valueGetter: (_v, row) => {
50
- return rows.findIndex((r) => r.key === row.key) + 1;
51
+ return rows.findIndex((r) => r.id === row.id) + 1;
51
52
  },
52
53
  renderCell: (p) => (_jsx(Avatar, { size: "sm", variant: "soft", children: p.value })),
53
54
  },
@@ -77,25 +78,32 @@ export const getStopsColumnDef = (dispatch, stopKind, rows, customerPortal, onEd
77
78
  valueSetter(value, row) {
78
79
  if (value && typeof value != "string")
79
80
  if (typeof value != "number")
80
- if (locationEntityTypeGuard(value))
81
+ if (locationEntityTypeGuard(value)) {
81
82
  return {
82
83
  ...row,
83
84
  locationId: value.id,
84
85
  zip: value.address.zip,
86
+ placeId: undefined,
85
87
  };
86
- else
88
+ }
89
+ else {
87
90
  return {
88
91
  ...row,
89
92
  locationId: value.id,
90
93
  zip: value.zip,
94
+ placeId: undefined,
91
95
  };
92
- else
96
+ }
97
+ else {
93
98
  return {
94
99
  ...row,
95
100
  locationId: value,
101
+ placeId: undefined,
96
102
  };
97
- else
98
- return { ...row, locationId: undefined };
103
+ }
104
+ else {
105
+ return { ...row, locationId: undefined, placeId: undefined };
106
+ }
99
107
  },
100
108
  },
101
109
  {
@@ -120,7 +128,7 @@ export const getStopsColumnDef = (dispatch, stopKind, rows, customerPortal, onEd
120
128
  getActions: (p) => [
121
129
  _jsx(GridActionsCellItem, { icon: _jsx("i", { style: { fontSize: 14 }, className: "fa-sharp fa-solid fa-trash" }), label: `Delete`, onClick: (e) => {
122
130
  e.stopPropagation();
123
- dispatch(removeStop(p.row.key));
131
+ dispatch(removeStop(p.row.id));
124
132
  }, color: "inherit" }, "delete"),
125
133
  ],
126
134
  },
@@ -275,144 +283,138 @@ export const getPalletsColumnDef = (deleteAction, customerPortal, perPalletClass
275
283
  });
276
284
  return ret;
277
285
  };
278
- const ZipCodeRenderCell = ({ value, errors, }) => {
279
- const fetchAutocompleteSuggestions = google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions;
280
- const [googleMapsPredictions, setGoogleMapsPredictions] = useState([]);
286
+ const ZipCodeRenderCell = ({ value, errors, row, id, }) => {
287
+ const apiRef = useGridApiContext();
288
+ const { getSuggestions } = useGoogleMapsAutocompleteCache();
281
289
  useEffect(() => {
282
- if (value) {
283
- fetchAutocompleteSuggestions({
290
+ let active = true;
291
+ if (!value) {
292
+ return;
293
+ }
294
+ if (!row.placeId)
295
+ getSuggestions({
284
296
  input: value,
285
- includedRegionCodes: ["US"],
286
297
  includedPrimaryTypes: ["(regions)"],
298
+ includedRegionCodes: ["US"],
287
299
  language: "en-US",
288
300
  region: "us",
289
- }).then(({ suggestions }) => {
290
- setGoogleMapsPredictions(suggestions);
301
+ }).then((suggestions) => {
302
+ if (!active || !suggestions?.[0])
303
+ return;
304
+ // Only enrich the row with a placeId — never overwrite the user's
305
+ // entered value (zip stays zip, "City, ST" stays "City, ST").
306
+ apiRef.current.updateRows([
307
+ {
308
+ id,
309
+ placeId: suggestions[0].placePrediction?.placeId || undefined,
310
+ },
311
+ ]);
291
312
  });
292
- }
313
+ return () => {
314
+ active = false;
315
+ };
293
316
  }, [value]);
294
317
  return (_jsx(Box, { width: "100%", children: value ? (_jsx(Typography, { level: "body-sm", sx: {
295
318
  display: "inline",
296
- opacity: value || errors ? 1 : 0.5,
297
- color: (theme) => errors && !value ? theme.palette.danger[500] : undefined,
298
- }, children: googleMapsPredictions?.[0]
299
- ? googleMapsPredictions[0].placePrediction?.text.text || ""
300
- : value })) : (_jsxs(_Fragment, { children: [_jsx(Typography, { display: "inline", level: "body-sm", sx: {
301
- opacity: value || errors ? 1 : 0.5,
302
- color: (theme) => errors && !value ? theme.palette.danger[500] : undefined,
303
- }, children: "Enter zip or city, state... " }), _jsx(Typography, { display: "inline", color: "danger", children: "*" })] })) }));
319
+ opacity: errors ? 1 : 0.5,
320
+ color: (theme) => (errors ? theme.palette.danger[500] : undefined),
321
+ }, children: value })) : (_jsxs(_Fragment, { children: [_jsx(Typography, { level: "body-sm", display: "inline", children: "Enter zip or city, state..." }), _jsx(Typography, { display: "inline", color: "danger", children: "*" })] })) }));
304
322
  };
305
- const ZipCodeRenderEditCell = ({ value: _value, id, field, hasFocus, }) => {
323
+ export const ZipCodeRenderEditCell = ({ id, field, hasFocus, value: initialValue, }) => {
306
324
  const apiRef = useGridApiContext();
307
- const [googleMapsPredictions, setGoogleMapsPredictions] = useState([]);
308
- const fetchAutocompleteSuggestions = google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions;
309
325
  const ref = useRef(null);
326
+ const { getSuggestions } = useGoogleMapsAutocompleteCache();
327
+ const [options, setOptions] = useState([]);
328
+ const [inputValue, setInputValue] = useState("");
310
329
  const [open, setOpen] = useState(false);
311
- const [value, setValue] = useState(null);
312
330
  useLayoutEffect(() => {
313
- if (hasFocus) {
331
+ if (hasFocus)
314
332
  ref.current?.focus();
315
- }
316
333
  }, [hasFocus]);
317
334
  useEffect(() => {
318
- if (_value)
319
- fetchAutocompleteSuggestions({
320
- input: _value,
321
- includedPrimaryTypes: ["(regions)"],
322
- language: "en-US",
323
- region: "us",
324
- includedRegionCodes: ["US"],
325
- }).then(async ({ suggestions }) => {
326
- const result = suggestions[0];
327
- if (result) {
328
- if (result.placePrediction?.text.text) {
329
- setValue(result.placePrediction?.text.text);
330
- }
331
- else
332
- setValue(_value);
333
- }
334
- else
335
- setValue(_value);
336
- });
335
+ setInputValue(initialValue || "");
337
336
  }, []);
338
- const handleValueChange = async (_, newValue) => {
339
- setValue(newValue);
340
- if (typeof newValue != "string" && newValue != null) {
341
- if (newValue.placePrediction?.text.text) {
342
- const _place = newValue.placePrediction.toPlace();
343
- if (_place) {
344
- const place = (await _place.fetchFields({ fields: ["addressComponents"] })).place;
345
- const mainText = place.addressComponents?.[0]?.longText;
346
- let _value = mainText;
347
- if (Number.isNaN(Number(mainText)) &&
348
- place.addressComponents?.[2]?.shortText) {
349
- _value =
350
- place.addressComponents?.[0]?.longText +
351
- ", " +
352
- place.addressComponents?.[2]?.shortText;
353
- }
354
- apiRef.current?.setEditCellValue({
355
- id,
356
- field,
357
- value: _value,
358
- });
359
- }
360
- }
361
- }
362
- else {
363
- apiRef.current?.setEditCellValue({
337
+ const loadSuggestions = useMemo(() => debounce(async (val) => {
338
+ if (!val)
339
+ return;
340
+ const results = await getSuggestions({
341
+ input: val,
342
+ includedPrimaryTypes: ["(regions)"],
343
+ includedRegionCodes: ["US"],
344
+ language: "en-US",
345
+ region: "us",
346
+ });
347
+ setOptions(results);
348
+ }, 300), [getSuggestions]);
349
+ const handleChange = async (_, newValue) => {
350
+ if (!newValue) {
351
+ apiRef.current.setEditCellValue({ id, field, value: "" });
352
+ apiRef.current.setEditCellValue({
364
353
  id,
365
- field,
366
- value: newValue,
354
+ field: "placeId",
355
+ value: undefined,
367
356
  });
357
+ return;
368
358
  }
369
- };
370
- const getGoogleAddresses = async (_event, value) => {
371
- if (value) {
372
- const { suggestions } = await fetchAutocompleteSuggestions({
373
- input: value,
374
- includedRegionCodes: ["US"],
375
- includedPrimaryTypes: ["(regions)"],
376
- language: "en-US",
377
- region: "us",
359
+ if (typeof newValue === "string") {
360
+ setInputValue(newValue);
361
+ apiRef.current.setEditCellValue({ id, field, value: newValue });
362
+ apiRef.current.setEditCellValue({
363
+ id,
364
+ field: "placeId",
365
+ value: undefined,
378
366
  });
379
- setGoogleMapsPredictions(suggestions);
367
+ return;
368
+ }
369
+ // Capture intent BEFORE the async boundary — onInputChange("reset") fires
370
+ // synchronously after onChange and would overwrite inputValue state, but
371
+ // the closure here still holds the value the user actually typed.
372
+ const isZipInput = /^\d+$/.test(inputValue.trim());
373
+ const place = newValue.placePrediction?.toPlace();
374
+ if (!place)
375
+ return;
376
+ const result = await place.fetchFields({ fields: ["addressComponents"] });
377
+ const components = result.place.addressComponents ?? [];
378
+ let finalValue;
379
+ if (isZipInput) {
380
+ // User typed digits → fill with the zip code from the selected place
381
+ finalValue =
382
+ components.find((c) => c.types.includes("postal_code"))?.longText ||
383
+ inputValue;
380
384
  }
385
+ else {
386
+ // User typed city/state text → fill with "City, ST"
387
+ const city = components.find((c) => c.types.includes("locality"))?.longText ||
388
+ components.find((c) => c.types.includes("postal_town"))?.longText ||
389
+ components.find((c) => c.types.includes("administrative_area_level_2"))
390
+ ?.longText ||
391
+ "";
392
+ const state = components.find((c) => c.types.includes("administrative_area_level_1"))
393
+ ?.shortText || "";
394
+ finalValue =
395
+ city && state ? `${city}, ${state}` : city || state || inputValue;
396
+ }
397
+ setInputValue(finalValue);
398
+ apiRef.current.setEditCellValue({ id, field, value: finalValue });
399
+ apiRef.current.setEditCellValue({
400
+ id,
401
+ field: "placeId",
402
+ value: newValue.placePrediction?.placeId || undefined,
403
+ });
381
404
  };
382
- const debouncedChangeHandler = useMemo(() => debounce(getGoogleAddresses, 300), []);
383
- return (_jsx(Autocomplete, { fullWidth: true, sx: {
384
- "& fieldset": {
385
- borderWidth: "0px !important",
386
- },
387
- }, onClose: () => {
388
- setOpen(false);
389
- }, options: googleMapsPredictions || [], open: open, onOpen: () => setOpen(true), value: value || "", freeSolo: true, onChange: handleValueChange, onKeyDown: (e) => {
390
- if (e.key === "Enter" && open) {
391
- e.stopPropagation();
392
- setOpen(false);
393
- }
394
- }, onInputChange: (e, v) => {
395
- debouncedChangeHandler(e, v);
396
- apiRef.current?.setEditCellValue({
405
+ return (_jsx(Autocomplete, { fullWidth: true, freeSolo: true, open: open, onOpen: () => setOpen(true), onClose: () => setOpen(false), options: options, filterOptions: (x) => x, value: inputValue, getOptionLabel: (option) => typeof option === "string"
406
+ ? option
407
+ : option.placePrediction?.text.text || "", onChange: handleChange, onInputChange: (_, v) => {
408
+ if (v === inputValue)
409
+ return;
410
+ setInputValue(v);
411
+ loadSuggestions(v);
412
+ apiRef.current.setEditCellValue({
397
413
  id,
398
414
  field,
399
415
  value: v,
400
416
  });
401
- }, getOptionLabel: (option) => {
402
- if (typeof option != "string") {
403
- return option.placePrediction?.text.text || "";
404
- }
405
- else
406
- return option;
407
- }, filterOptions: (x) => x, renderInput: (params) => (_jsx(TextField, { tabIndex: 0, ...params, placeholder: "Enter zip or city, state...", autoFocus: apiRef.current?.getRowMode(id) === "edit" ? false : true, inputRef: ref, InputProps: {
408
- ...params.InputProps,
409
- tabIndex: 0,
410
- onKeyDown: (e) => {
411
- if (e.key === "Tab") {
412
- e.stopPropagation();
413
- }
414
- },
415
- } })), renderOption: (props, c) => (_jsx(LocationOption, { cellInput: true, props: props, c: c }, locationEntityTypeGuard(c) ? c.id : c.placePrediction?.placeId)) }));
417
+ }, renderInput: (params) => (_jsx(TextField, { ...params, inputRef: ref, placeholder: "Enter zip or city, state...", autoFocus: true })) }));
416
418
  };
417
419
  const NumberEditCell = ({ id, value, field }) => {
418
420
  const apiRef = useGridApiContext();
@@ -672,152 +674,125 @@ export function AccessorialInput({ accessorialIDs: _accessorialIDs, onChange, })
672
674
  * @returns {ReactElement} The autocomplete
673
675
  */
674
676
  export function LocationInputCell(props) {
675
- const { id, value, field, hasFocus, onEditLocationClick, customerPortal } = props;
677
+ const { id, field, hasFocus, onEditLocationClick, customerPortal } = props;
676
678
  const apiRef = useGridApiContext();
677
679
  const [openDialog, setOpenDialog] = useState(false);
678
680
  const [locationInputValue, setLocationInputValue] = useState("");
681
+ const [open, setOpen] = useState(false);
679
682
  const [searchExistingLocation, { data: existingLocations }] = useLazySearchExistingLocationQuery();
680
683
  const [searchLocation, { data: locationSuggestions }] = useLazySearchLocationQuery();
681
- const [bounds, setBounds] = useState();
682
684
  const [googleMapsPredictions, setGoogleMapsPredictions] = useState([]);
683
685
  const fetchAutocompleteSuggestions = google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions;
684
686
  const googleMapsGeocoder = new google.maps.Geocoder();
685
687
  const [getLocation, { data: location }] = useLazyGetLocationQuery();
688
+ const [bounds, setBounds] = useState();
686
689
  const ref = useRef(null);
687
- const [open, setOpen] = useState(false);
690
+ const zip = useMemo(() => {
691
+ const z = props.row.zip || props.row.quoteZip || "";
692
+ return [...z.matchAll(/\d/g)].map((m) => m[0]).join("") || z;
693
+ }, [props.row.zip, props.row.quoteZip]);
688
694
  useLayoutEffect(() => {
689
- if (hasFocus) {
695
+ if (hasFocus)
690
696
  ref.current?.focus();
691
- }
692
697
  }, [hasFocus]);
693
698
  useEffect(() => {
694
- const id = Number(value);
695
- if (id) {
696
- getLocation(id, true);
697
- }
698
- }, [value]);
699
- useEffect(() => {
700
- if (props.row.zip || props.row.quoteZip) {
701
- findBounds();
702
- }
703
- }, [props.row]);
704
- const handleValueChange = async (_, newValue) => {
705
- if (typeof newValue != "string") {
706
- if (newValue && locationEntityTypeGuard(newValue)) {
707
- apiRef.current?.setEditCellValue({
708
- id,
709
- field,
710
- value: newValue,
711
- });
712
- }
713
- else if (newValue) {
714
- const place = newValue.placePrediction?.toPlace();
715
- await place?.fetchFields({
716
- fields: ["formattedAddress"],
717
- });
718
- if (place && place.formattedAddress) {
719
- try {
720
- const existingLocations = await searchExistingLocation(place.formattedAddress).unwrap();
721
- if (existingLocations.length > 0)
722
- setOpenDialog(true);
723
- else {
724
- onEditLocationClick(place, id, field);
725
- }
726
- }
727
- catch {
728
- return;
729
- }
730
- }
731
- }
732
- else {
733
- apiRef.current?.setEditCellValue({
734
- id,
735
- field,
736
- value: newValue,
737
- });
738
- }
739
- }
740
- };
741
- const findBounds = () => {
699
+ const idNum = Number(props.value);
700
+ if (idNum)
701
+ getLocation(idNum, true);
702
+ }, [props.value]);
703
+ const findBounds = useCallback(() => {
704
+ if (!zip)
705
+ return;
742
706
  googleMapsGeocoder.geocode({
743
- address: props.row.zip || props.row.quoteZip,
707
+ address: zip,
744
708
  componentRestrictions: { country: "US" },
745
709
  language: "en-US",
746
710
  }, (results, status) => {
747
- if (status === google.maps.GeocoderStatus.OK && results != undefined) {
748
- const result = results.find((r) => r.address_components.find((a) => a.types.includes("country"))
749
- ?.short_name === "US") || results[0];
750
- const lat = result.geometry.location.lat();
751
- const lng = result.geometry.location.lng();
752
- const center = new google.maps.LatLng(lat, lng);
711
+ if (status === google.maps.GeocoderStatus.OK && results?.length) {
712
+ const result = results.find((r) => r.address_components?.some((a) => a.types.includes("country"))) || results[0];
713
+ const center = new google.maps.LatLng(result.geometry.location.lat(), result.geometry.location.lng());
753
714
  const circle = new google.maps.Circle({
754
- center: center,
715
+ center,
755
716
  radius: 10000,
756
717
  });
757
718
  const bounds = circle.getBounds() || undefined;
758
719
  setBounds(bounds);
759
720
  }
760
721
  });
761
- };
762
- const getGoogleAddresses = async (searchText) => {
763
- if (searchText) {
764
- const { suggestions } = await fetchAutocompleteSuggestions({
765
- input: searchText,
766
- locationBias: bounds,
767
- language: "en-US",
768
- region: "us",
769
- includedRegionCodes: ["US"],
770
- });
771
- setGoogleMapsPredictions(suggestions);
722
+ }, [zip]);
723
+ useEffect(() => {
724
+ if (props.row.zip || props.row.quoteZip) {
725
+ findBounds();
772
726
  }
773
- };
774
- const onInputChange = useCallback((e) => {
775
- const zip = props.row.zip || props.row.quoteZip || "";
776
- const _zip = [...zip.matchAll(/\d/g)].map((m) => m[0]).join("");
727
+ }, [props.row.zip, props.row.quoteZip, findBounds]);
728
+ const getGoogleAddresses = useCallback(async (searchText) => {
729
+ if (!searchText)
730
+ return;
731
+ const { suggestions } = await fetchAutocompleteSuggestions({
732
+ input: searchText,
733
+ locationBias: bounds,
734
+ language: "en-US",
735
+ region: "us",
736
+ includedRegionCodes: ["US"],
737
+ });
738
+ setGoogleMapsPredictions(suggestions || []);
739
+ }, [bounds]);
740
+ const debouncedSearch = useMemo(() => debounce((value) => {
777
741
  searchLocation({
778
- zip: _zip ? _zip : zip,
779
- location: e.target.value,
742
+ zip,
743
+ location: value,
780
744
  });
781
- getGoogleAddresses(e.target.value);
782
- }, [props.row]);
783
- const debouncedChangeHandler = useMemo(() => debounce(onInputChange, 500), []);
745
+ getGoogleAddresses(value);
746
+ }, 400), [zip, bounds, searchLocation, getGoogleAddresses]);
747
+ const handleValueChange = async (_, newValue) => {
748
+ if (!newValue || typeof newValue === "string") {
749
+ apiRef.current?.setEditCellValue({ id, field, value: undefined });
750
+ return;
751
+ }
752
+ if (locationEntityTypeGuard(newValue)) {
753
+ apiRef.current?.setEditCellValue({
754
+ id,
755
+ field,
756
+ value: newValue,
757
+ });
758
+ return;
759
+ }
760
+ const place = newValue.placePrediction?.toPlace();
761
+ await place?.fetchFields({ fields: ["formattedAddress"] });
762
+ if (!place?.formattedAddress)
763
+ return;
764
+ try {
765
+ const existing = await searchExistingLocation(place.formattedAddress).unwrap();
766
+ if (existing.length > 0) {
767
+ setOpenDialog(true);
768
+ }
769
+ else {
770
+ onEditLocationClick(place, id, field);
771
+ }
772
+ }
773
+ catch {
774
+ return;
775
+ }
776
+ };
784
777
  return (_jsxs(_Fragment, { children: [_jsx(Autocomplete, { fullWidth: true, sx: {
785
- "& fieldset": {
786
- borderWidth: "0px !important",
787
- },
778
+ "& fieldset": { borderWidth: "0px !important" },
788
779
  }, options: [
789
780
  ...(locationSuggestions ?? []),
790
781
  ...(googleMapsPredictions ?? []),
791
- ], open: open, onClose: () => {
792
- setOpen(false);
793
- }, onOpen: () => setOpen(true), value: location || "", onChange: handleValueChange, inputValue: locationInputValue, onKeyDown: (e) => {
794
- if (e.key === "Enter" && open) {
795
- e.stopPropagation();
796
- setOpen(false);
797
- }
798
- }, freeSolo: true, onInputChange: (_e, v) => setLocationInputValue(v), getOptionLabel: (option) => {
799
- if (typeof option != "string") {
800
- if (locationEntityTypeGuard(option)) {
801
- return option.name;
802
- }
803
- return option.placePrediction?.mainText?.text || "";
804
- }
805
- else
806
- return option;
807
- }, filterOptions: (x) => x, renderInput: (params) => (_jsx(TextField, { tabIndex: 0, variant: "outlined", ...params, size: "small", placeholder: "Pick a location...", autoFocus: apiRef.current?.getRowMode(id) === "edit" ? false : true, onChange: (e) => {
808
- debouncedChangeHandler(e);
809
- }, inputRef: ref, InputProps: {
810
- ...params.InputProps,
811
- tabIndex: 0,
812
- } })), renderOption: (props, c) => (_jsx(LocationOption, { cellInput: true, props: props, c: c }, locationEntityTypeGuard(c) ? c.id : c.placePrediction?.placeId)) }), _jsx(ExistingLocations, { updateCell: (value) => {
782
+ ], open: open, onOpen: () => setOpen(true), onClose: () => setOpen(false), value: location || "", inputValue: locationInputValue, freeSolo: true, filterOptions: (x) => x, getOptionLabel: (option) => typeof option === "string"
783
+ ? option
784
+ : locationEntityTypeGuard(option)
785
+ ? option.name
786
+ : option.placePrediction?.mainText?.text || "", onChange: handleValueChange, onInputChange: (_e, v) => {
787
+ setLocationInputValue(v);
788
+ debouncedSearch(v);
789
+ }, renderInput: (params) => (_jsx(TextField, { ...params, size: "small", placeholder: "Pick a location...", inputRef: ref })), renderOption: (props, c) => (_jsx(LocationOption, { cellInput: true, props: props, c: c }, locationEntityTypeGuard(c) ? c.id : c.placePrediction?.placeId)) }), _jsx(ExistingLocations, { updateCell: (value) => {
813
790
  apiRef.current?.setEditCellValue({
814
791
  id,
815
792
  field,
816
- value: value,
793
+ value,
817
794
  });
818
- }, existingLocations: existingLocations, close: () => {
819
- setOpenDialog(false);
820
- }, open: openDialog, openLocationForm: (place) => onEditLocationClick(place, id, field), customerPortal: customerPortal })] }));
795
+ }, existingLocations: existingLocations, close: () => setOpenDialog(false), open: openDialog, openLocationForm: (place) => onEditLocationClick(place, id, field), customerPortal: customerPortal })] }));
821
796
  }
822
797
  export const LocationInput = ({ zip, locationID, onChange, customerPortal, autoCompleteProps, }) => {
823
798
  const [locationFormOpen, setLocationFormOpen] = useState({
@@ -825,131 +800,92 @@ export const LocationInput = ({ zip, locationID, onChange, customerPortal, autoC
825
800
  place: undefined,
826
801
  });
827
802
  const [openDialog, setOpenDialog] = useState(false);
828
- const [locationInputValue, setLocationInputValue] = useState("");
829
- const [searchExistingLocation, { data: existingLocations }] = useLazySearchExistingLocationQuery();
830
- const [searchLocation, { data: locationSuggestions }] = useLazySearchLocationQuery();
831
- const [bounds, setBounds] = useState();
803
+ const [inputValue, setInputValue] = useState("");
832
804
  const [googleMapsPredictions, setGoogleMapsPredictions] = useState([]);
833
- const fetchAutocompleteSuggestions = google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions;
834
805
  const googleMapsGeocoder = new google.maps.Geocoder();
806
+ const [bounds, setBounds] = useState();
807
+ const [searchExistingLocation, { data: existingLocations }] = useLazySearchExistingLocationQuery();
808
+ const [searchLocation, { data: locationSuggestions }] = useLazySearchLocationQuery();
835
809
  const [getLocation] = useLazyGetLocationQuery();
836
810
  const [location, setLocation] = useState();
811
+ const { getSuggestions } = useGoogleMapsAutocompleteCache();
837
812
  useEffect(() => {
838
813
  if (locationID) {
839
814
  getLocation(locationID, true)
840
815
  .unwrap()
841
- .then((d) => setLocation(d))
816
+ .then(setLocation)
842
817
  .catch(() => undefined);
843
818
  }
844
- else
819
+ else {
845
820
  setLocation(undefined);
821
+ }
846
822
  }, [locationID]);
847
823
  useEffect(() => {
848
- if (zip) {
849
- findBounds();
850
- }
851
- }, [zip]);
852
- const handleValueChange = async (_, newValue) => {
853
- if (typeof newValue != "string")
854
- if (newValue && locationEntityTypeGuard(newValue)) {
855
- onChange(newValue);
856
- }
857
- else if (newValue) {
858
- const place = newValue.placePrediction?.toPlace();
859
- await place?.fetchFields({
860
- fields: ["formattedAddress"],
861
- });
862
- if (place && place.formattedAddress) {
863
- try {
864
- const existingLocations = await searchExistingLocation(place?.formattedAddress).unwrap();
865
- if (existingLocations.length > 0)
866
- setOpenDialog(true);
867
- else {
868
- setLocationFormOpen({ open: true, place: place });
869
- }
870
- }
871
- catch {
872
- return;
873
- }
874
- }
875
- }
876
- else {
877
- onChange(newValue);
878
- }
879
- };
880
- const findBounds = () => {
824
+ if (!zip)
825
+ return;
881
826
  googleMapsGeocoder.geocode({
882
827
  address: zip,
883
828
  componentRestrictions: { country: "US" },
884
- language: "en",
885
829
  }, (results, status) => {
886
- if (status === google.maps.GeocoderStatus.OK && results != undefined) {
887
- const result = results[0];
888
- const lat = result.geometry.location.lat();
889
- const lng = result.geometry.location.lng();
890
- const center = new google.maps.LatLng(lat, lng);
830
+ if (status === google.maps.GeocoderStatus.OK && results?.[0]) {
831
+ const loc = results[0].geometry.location;
891
832
  const circle = new google.maps.Circle({
892
- center: center,
833
+ center: loc,
893
834
  radius: 10000,
894
835
  });
895
- const bounds = circle.getBounds() || undefined;
896
- setBounds(bounds);
836
+ setBounds(circle.getBounds() || undefined);
897
837
  }
898
838
  });
899
- };
900
- const getGoogleAddresses = async (searchText) => {
901
- if (searchText) {
902
- const { suggestions } = await fetchAutocompleteSuggestions({
903
- input: searchText,
904
- locationBias: bounds,
905
- language: "en-US",
906
- region: "us",
907
- includedRegionCodes: ["US"],
839
+ }, [zip]);
840
+ const handleValueChange = async (_, newValue) => {
841
+ if (typeof newValue !== "string") {
842
+ if (!newValue) {
843
+ onChange(undefined);
844
+ return;
845
+ }
846
+ if (locationEntityTypeGuard(newValue)) {
847
+ onChange(newValue);
848
+ return;
849
+ }
850
+ const place = newValue.placePrediction?.toPlace();
851
+ if (!place)
852
+ return;
853
+ const result = await place.fetchFields({
854
+ fields: ["formattedAddress"],
908
855
  });
909
- setGoogleMapsPredictions(suggestions);
856
+ const formatted = result.place.formattedAddress;
857
+ if (!formatted)
858
+ return;
859
+ const existing = await searchExistingLocation(formatted).unwrap();
860
+ if (existing.length > 0) {
861
+ setOpenDialog(true);
862
+ return;
863
+ }
864
+ setLocationFormOpen({ open: true, place });
910
865
  }
911
866
  };
912
- const onInputChange = useCallback((v) => {
913
- searchLocation({ zip: zip, location: v });
914
- getGoogleAddresses(v);
915
- }, [zip]);
916
- const debouncedChangeHandler = useMemo(() => debounce(onInputChange, 500), []);
917
- return (_jsxs(_Fragment, { children: [_jsxs(FormControl, { children: [_jsx(FormLabel, { children: "Pick a location..." }), _jsx(JoyAutocomplete, { size: "sm", ...autoCompleteProps, slotProps: {
918
- listbox: {
919
- sx: {
920
- zIndex: (theme) => theme.zIndex.modal,
921
- },
922
- },
923
- }, options: [
924
- ...(locationSuggestions ?? []),
925
- ...(googleMapsPredictions ?? []),
926
- ], filterOptions: (x) => x, value: location || "", onChange: handleValueChange, inputValue: locationInputValue, freeSolo: true, onInputChange: (_e, v) => {
927
- setLocationInputValue(v);
928
- debouncedChangeHandler(v);
929
- }, getOptionLabel: (option) => {
930
- if (typeof option != "string") {
931
- if (locationEntityTypeGuard(option)) {
932
- return option.name;
933
- }
934
- return option.placePrediction?.mainText?.text || "";
935
- }
936
- else
937
- return option;
938
- }, endDecorator: _jsx(IconButton, { sx: {
939
- display: location ? "inherit" : "none",
940
- }, size: "small", onClick: () => {
941
- setLocationFormOpen({ open: true, place: location });
942
- }, children: _jsx("i", { className: "fa-solid fa-pen-to-square" }) }), renderOption: (props, c) => typeof c !== "string" ? (_jsx(LocationOption, { props: props, c: c, cellInput: false }, locationEntityTypeGuard(c) ? c.id : c.placePrediction?.placeId)) : (c) })] }), customerPortal ? (_jsx(LocationForm, { open: locationFormOpen.open, onClose: () => setLocationFormOpen({ open: false, place: undefined }), place: locationFormOpen.place, onConfirm: (value) => {
943
- onChange(value);
944
- } })) : (_jsx(LocationFormSales, { onClose: () => setLocationFormOpen({ open: false, place: undefined }), open: locationFormOpen.open, place: locationFormOpen.place, onCreateLocation: (value) => {
945
- onChange(value);
946
- }, onSaveLocation: (value) => {
947
- onChange(value);
948
- } })), _jsx(ExistingLocations, { updateCell: (value) => {
949
- onChange(value);
950
- }, existingLocations: existingLocations, close: () => {
951
- setOpenDialog(false);
952
- }, open: openDialog, openLocationForm: (place, open) => setLocationFormOpen({ open, place }), customerPortal: customerPortal })] }));
867
+ const debouncedSearch = useMemo(() => debounce(async (v) => {
868
+ searchLocation({ zip, location: v });
869
+ const googleResults = await getSuggestions({
870
+ input: v,
871
+ locationBias: bounds,
872
+ includedRegionCodes: ["US"],
873
+ language: "en-US",
874
+ region: "us",
875
+ });
876
+ setGoogleMapsPredictions(googleResults);
877
+ }, 400), [zip, bounds]);
878
+ return (_jsxs(_Fragment, { children: [_jsxs(FormControl, { children: [_jsx(FormLabel, { children: "Pick a location..." }), _jsx(JoyAutocomplete, { ...autoCompleteProps, size: "sm", value: location || null, inputValue: inputValue, freeSolo: true, options: [...(locationSuggestions ?? []), ...googleMapsPredictions], filterOptions: (x) => x, getOptionLabel: (option) => typeof option === "string"
879
+ ? option
880
+ : locationEntityTypeGuard(option)
881
+ ? option.name
882
+ : option.placePrediction?.mainText?.text || "", onChange: handleValueChange, onInputChange: (_, v) => {
883
+ setInputValue(v);
884
+ debouncedSearch(v);
885
+ }, endDecorator: location ? (_jsx(IconButton, { size: "small", onClick: () => setLocationFormOpen({
886
+ open: true,
887
+ place: location,
888
+ }), children: _jsx("i", { className: "fa-solid fa-pen-to-square" }) })) : null, renderOption: (props, c) => typeof c !== "string" ? (_jsx(LocationOption, { props: props, c: c, cellInput: false }, locationEntityTypeGuard(c) ? c.id : c.placePrediction?.placeId)) : (c) })] }), customerPortal ? (_jsx(LocationForm, { open: locationFormOpen.open, onClose: () => setLocationFormOpen({ open: false, place: undefined }), place: locationFormOpen.place, onConfirm: onChange })) : (_jsx(LocationFormSales, { open: locationFormOpen.open, onClose: () => setLocationFormOpen({ open: false, place: undefined }), place: locationFormOpen.place, onCreateLocation: onChange, onSaveLocation: onChange })), _jsx(ExistingLocations, { open: openDialog, close: () => setOpenDialog(false), existingLocations: existingLocations, updateCell: onChange, openLocationForm: (place, open) => setLocationFormOpen({ open, place }), customerPortal: customerPortal })] }));
953
889
  };
954
890
  /**
955
891
  *