@sustaina/shared-ui 1.24.0 → 1.25.0

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.
package/dist/index.mjs CHANGED
@@ -4,7 +4,6 @@ import { twMerge } from 'tailwind-merge';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import * as React4 from 'react';
6
6
  import React4__default, { forwardRef, useRef, useMemo, useCallback, isValidElement, useState, useEffect, useLayoutEffect, createElement } from 'react';
7
- import { format, isValid, parseISO, isAfter, compareAsc, parse } from 'date-fns';
8
7
  import { CircleX, CircleHelp, Undo, Redo, Bold, Italic, Underline, Strikethrough, Code, Pilcrow, Heading1, Heading2, Heading3, List as List$1, ListOrdered, Quote, CodeSquare, Link, Link2Off, Image as Image$1, AlignLeft, AlignCenter, AlignRight, XIcon, ChevronRight, CheckIcon, Triangle, CalendarIcon, X, Search, ChevronUp, ChevronDown, Minimize2, Maximize2, Plus, ChevronLeft, CircleUserRound, PanelLeftIcon, Bug, GripVertical, Info, CircleMinus, Minus } from 'lucide-react';
9
8
  import { createPortal } from 'react-dom';
10
9
  import * as SelectPrimitive from '@radix-ui/react-select';
@@ -12,6 +11,7 @@ import { useForm, FormProvider, Controller, useFormContext, useFormState, useFie
12
11
  import { Slot } from '@radix-ui/react-slot';
13
12
  import * as LabelPrimitive from '@radix-ui/react-label';
14
13
  import { cva } from 'class-variance-authority';
14
+ import { format, isValid, parseISO, isAfter, compareAsc, parse } from 'date-fns';
15
15
  import * as PopoverPrimitive from '@radix-ui/react-popover';
16
16
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
17
17
  import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
@@ -3421,6 +3421,66 @@ function getBuilder(fieldType) {
3421
3421
  return new JSONBuilder();
3422
3422
  }
3423
3423
  }
3424
+ var FILTER_FIELD_MAP = {
3425
+ timezone: "timezoneId",
3426
+ decimalSeparator: "decimalSeparatorId",
3427
+ country: "countryId",
3428
+ currency: "currencyId"
3429
+ };
3430
+ function transformFilterKeys(obj, fieldMap = FILTER_FIELD_MAP) {
3431
+ if (Array.isArray(obj)) {
3432
+ return obj.map((item) => transformFilterKeys(item, fieldMap));
3433
+ }
3434
+ if (obj && typeof obj === "object") {
3435
+ const newObj = {};
3436
+ for (const key in obj) {
3437
+ const mappedKey = fieldMap[key] ?? key;
3438
+ newObj[mappedKey] = transformFilterKeys(obj[key], fieldMap);
3439
+ }
3440
+ return newObj;
3441
+ }
3442
+ return obj;
3443
+ }
3444
+ var sanitizeInput = (val) => {
3445
+ if (!val) return val;
3446
+ if (typeof val !== "string") return "__INVALID_TYPE__";
3447
+ if (val.includes("\n") || val.includes("\r") || /[\u2028\u2029]/u.test(val))
3448
+ return "__INVALID_NEWLINE__";
3449
+ if (/\\\\/.test(val)) return "__INVALID_ESCAPE__";
3450
+ if (/\\(n|t|r|b|f|u[0-9a-fA-F]{4})/.test(val)) return "__INVALID_ESCAPE__";
3451
+ if (/\p{Cf}/u.test(val)) return "__INVALID_UNICODE_WHITESPACE__";
3452
+ if (/[\u00A0\u1680\u180E\u202F\u205F\u3000]/u.test(val)) return "__INVALID_UNICODE_WHITESPACE__";
3453
+ const trimmed = val.trim();
3454
+ if (/^\{.*\}$/s.test(trimmed) || /^\[.*\]$/s.test(trimmed)) return "__INVALID_JSON_LITERAL__";
3455
+ if (/\\\{/.test(val) || /\\\}/.test(val)) return "__INVALID_JSON_ESCAPE__";
3456
+ if (/[%_*~^]/.test(trimmed)) return "__INVALID_WILDCARD__";
3457
+ if (/[%><={}\\[\]"']/u.test(trimmed)) return "__INVALID_CHAR__";
3458
+ if (/\p{Cc}/u.test(val)) return "__INVALID_CONTROL_CHAR__";
3459
+ return trimmed.replace(/\s+/g, " ");
3460
+ };
3461
+ var numericTypes = ["number", "integer", "decimal"];
3462
+ var dateTypes = ["date", "datemonth"];
3463
+ var validateByFieldType = (value, fieldType) => {
3464
+ if (!value) return { valid: true };
3465
+ if (numericTypes.includes(fieldType)) {
3466
+ if (!/^\d+(\.\d+)?$/.test(value)) {
3467
+ return { valid: false, message: "Please enter a valid number." };
3468
+ }
3469
+ }
3470
+ if (fieldType === "boolean") {
3471
+ if (!["true", "false"].includes(value.toLowerCase())) {
3472
+ return { valid: false, message: "Please enter a boolean value (true/false)." };
3473
+ }
3474
+ }
3475
+ if (dateTypes.includes(fieldType)) {
3476
+ const normalized = fieldType === "datemonth" ? `${value}-01` : value;
3477
+ const parsed = parseISO(normalized);
3478
+ if (!isValid(parsed)) {
3479
+ return { valid: false, message: "Invalid date format." };
3480
+ }
3481
+ }
3482
+ return { valid: true };
3483
+ };
3424
3484
  var AdvanceSearch = ({
3425
3485
  fields,
3426
3486
  portalId,
@@ -3428,7 +3488,8 @@ var AdvanceSearch = ({
3428
3488
  limitRows = 4,
3429
3489
  onSearch,
3430
3490
  onClear,
3431
- shortDateFormat
3491
+ shortDateFormat,
3492
+ filterFieldMap = FILTER_FIELD_MAP
3432
3493
  }) => {
3433
3494
  const fieldsData = useMemo(() => {
3434
3495
  if (fields.length === 0) throw new Error("fields cannot be an empty array");
@@ -3480,72 +3541,75 @@ var AdvanceSearch = ({
3480
3541
  const onSubmit = useCallback(() => {
3481
3542
  const operatorValidation = {};
3482
3543
  rows.forEach((r) => {
3483
- const availableOperators = operatorsForField(r.fieldName);
3484
- if (!availableOperators.length || !availableOperators.includes(r.operator)) {
3544
+ const ops = operatorsForField(r.fieldName);
3545
+ if (!ops.length || !ops.includes(r.operator))
3485
3546
  operatorValidation[r.id] = "Please select an operator.";
3486
- }
3487
3547
  });
3488
3548
  setOperatorErrors(operatorValidation);
3489
- if (Object.keys(operatorValidation).length > 0) {
3490
- return;
3491
- }
3549
+ if (Object.keys(operatorValidation).length > 0) return;
3492
3550
  const currentValues = getValues();
3493
- let hasRangeError = false;
3494
- const rawRows = rows.map((r) => {
3495
- const startFieldName = `value_${r.id}`;
3496
- const startValue = currentValues[startFieldName] ?? "";
3551
+ let hasError = false;
3552
+ const processedRows = rows.map((r) => {
3553
+ const startField = `value_${r.id}`;
3554
+ const endField = `value2_${r.id}`;
3555
+ let v1 = currentValues[startField] ?? "";
3556
+ let v2 = currentValues[endField] ?? "";
3557
+ const s1 = sanitizeInput(v1);
3558
+ if (s1?.startsWith("__INVALID")) {
3559
+ hasError = true;
3560
+ setError(startField, { type: "validate", message: "Invalid input." });
3561
+ return null;
3562
+ }
3563
+ v1 = s1 || "";
3564
+ const valid1 = validateByFieldType(v1, r.fieldType);
3565
+ if (!valid1.valid) {
3566
+ hasError = true;
3567
+ setError(startField, { type: "validate", message: valid1.message });
3568
+ return null;
3569
+ }
3497
3570
  if (r.operator === "between") {
3498
- const endFieldName = `value2_${r.id}`;
3499
- const endValue = currentValues[endFieldName] ?? "";
3500
- if (startValue && endValue) {
3501
- const startDate = parseRangeValue(startValue, r.fieldType);
3502
- const endDate = parseRangeValue(endValue, r.fieldType);
3503
- if (startDate && endDate && isAfter(startDate, endDate)) {
3504
- hasRangeError = true;
3505
- setError(startFieldName, {
3506
- type: "validate",
3507
- message: "Start value must be before end value."
3508
- });
3509
- setError(endFieldName, {
3510
- type: "validate",
3511
- message: "End value must be after start value."
3512
- });
3513
- } else {
3514
- clearErrors([startFieldName, endFieldName]);
3571
+ const s2 = sanitizeInput(v2);
3572
+ if (s2?.startsWith("__INVALID")) {
3573
+ hasError = true;
3574
+ setError(endField, { type: "validate", message: "Invalid input." });
3575
+ return null;
3576
+ }
3577
+ v2 = s2 || "";
3578
+ const valid2 = validateByFieldType(v2, r.fieldType);
3579
+ if (!valid2.valid) {
3580
+ hasError = true;
3581
+ setError(endField, { type: "validate", message: valid2.message });
3582
+ return null;
3583
+ }
3584
+ if (v1 && v2 && ["date", "datemonth"].includes(r.fieldType)) {
3585
+ const d1 = parseRangeValue(v1, r.fieldType);
3586
+ const d2 = parseRangeValue(v2, r.fieldType);
3587
+ if (d1 && d2 && isAfter(d1, d2)) {
3588
+ hasError = true;
3589
+ setError(startField, { type: "validate", message: "Start value must be before end value." });
3590
+ setError(endField, { type: "validate", message: "End value must be after start value." });
3591
+ return null;
3515
3592
  }
3516
3593
  }
3517
- return {
3518
- ...r,
3519
- value: startValue ?? "",
3520
- value2: endValue ?? ""
3521
- };
3594
+ return { ...r, value: v1, value2: v2 };
3522
3595
  }
3523
- return {
3524
- ...r,
3525
- value: startValue ?? ""
3526
- };
3596
+ return { ...r, value: v1 };
3527
3597
  });
3528
- if (hasRangeError) {
3529
- return;
3530
- }
3598
+ if (hasError) return;
3599
+ const cleanedRows = processedRows.filter(Boolean);
3531
3600
  const param = {
3532
- AND: rawRows.map((r) => {
3533
- const builder = getBuilder(r.fieldType);
3534
- return builder.build(r);
3535
- }).filter(Boolean)
3601
+ AND: cleanedRows.map((r) => getBuilder(r.fieldType).build(r)).filter(Boolean)
3536
3602
  };
3537
- if (onSearch) {
3538
- onSearch(param);
3539
- }
3603
+ if (onSearch) onSearch(transformFilterKeys(param, filterFieldMap));
3540
3604
  }, [
3541
- clearErrors,
3542
- getValues,
3543
- onSearch,
3605
+ rows,
3544
3606
  operatorsForField,
3607
+ getValues,
3545
3608
  parseRangeValue,
3546
- rows,
3547
3609
  setError,
3548
- setOperatorErrors
3610
+ setOperatorErrors,
3611
+ filterFieldMap,
3612
+ onSearch
3549
3613
  ]);
3550
3614
  return /* @__PURE__ */ jsx(
3551
3615
  ExpandCollapse_default,
@@ -3583,9 +3647,7 @@ var AdvanceSearch = ({
3583
3647
  unregister(`value_${row.id}`);
3584
3648
  unregister(`value2_${row.id}`);
3585
3649
  },
3586
- onClearValue: (which) => {
3587
- clearValue(row.id, which);
3588
- },
3650
+ onClearValue: (which) => clearValue(row.id, which),
3589
3651
  disableAdd: limitRows !== void 0 && rows.length >= limitRows
3590
3652
  },
3591
3653
  row.id
@@ -3610,7 +3672,7 @@ var AdvanceSearch = ({
3610
3672
  Button,
3611
3673
  {
3612
3674
  type: "submit",
3613
- className: "w-full bg-[#379a2a] text-white hover:bg-[#2f7c21] md:w-auto md:min-w-[120px]",
3675
+ className: "w-full bg-sus-green-2 text-white hover:bg-[#2f7c21] md:w-auto md:min-w-[120px]",
3614
3676
  "data-testid": "advsearch-btn-search",
3615
3677
  children: "Search"
3616
3678
  }
@@ -6547,8 +6609,8 @@ var GridSettingsModal = ({
6547
6609
  }
6548
6610
  }, [isOpen, currentColumns, form]);
6549
6611
  const addColumn = async () => {
6550
- const isValid5 = await trigger("columns");
6551
- if (isValid5) {
6612
+ const isValid6 = await trigger("columns");
6613
+ if (isValid6) {
6552
6614
  append({ id: "" });
6553
6615
  requestAnimationFrame(() => {
6554
6616
  const container = scrollRef.current;