jcicl 1.0.59 → 1.0.61

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.
@@ -0,0 +1,24 @@
1
+ import { DataPageControls, DataPageResult } from './useDataPage';
2
+ import { WithLoadingProps } from '../WithLoading/WithLoading';
3
+ export interface DataPageProps<T> {
4
+ /** Async function that fetches data. Throw for fatal error. Return withWarnings() for partial success. */
5
+ fetchFn: () => Promise<T | DataPageResult<T>>;
6
+ /** useEffect dependency array. Default: [] (fetch once on mount). */
7
+ deps?: unknown[];
8
+ /** Render function for happy path. Receives data + controls. Read-only pages can ignore the 2nd arg. */
9
+ children: (data: T, controls: DataPageControls<T>) => React.ReactNode;
10
+ /** Shown on empty data. String → centered <p>. ReactNode → rendered directly. Omit for interactive empty. */
11
+ emptyState?: React.ReactNode | string;
12
+ /** Shown on fatal error. String → centered red <p>. Defaults to the caught error message. */
13
+ errorState?: React.ReactNode | string;
14
+ /** Data-dependent guards. Each receives data, returns ReactNode to short-circuit or null to continue. */
15
+ guards?: ((data: T) => React.ReactNode | null)[];
16
+ /** Determines if data is "empty". Default: array length === 0 or nullish. */
17
+ isEmpty?: (data: T) => boolean;
18
+ /** Props forwarded to the internal WithLoading wrapper (e.g. size, style). */
19
+ loadingProps?: Omit<WithLoadingProps, 'loading' | 'children'>;
20
+ /** Custom warning banner renderer. Default: orange centered <p> per warning. Pass null to suppress. */
21
+ renderWarnings?: ((warnings: string[]) => React.ReactNode) | null;
22
+ }
23
+ declare function DataPage<T>({ fetchFn, deps, children, emptyState, errorState, guards, isEmpty, loadingProps, renderWarnings, }: DataPageProps<T>): import("react/jsx-runtime").JSX.Element;
24
+ export default DataPage;
@@ -0,0 +1,34 @@
1
+ import { jsx as e, Fragment as i, jsxs as A } from "react/jsx-runtime";
2
+ import B, { defaultIsEmpty as P } from "./useDataPage.js";
3
+ import k from "../WithLoading/WithLoading.js";
4
+ const c = (n, t) => typeof n == "string" ? /* @__PURE__ */ e("p", { style: { textAlign: "center", color: t }, children: n }) : /* @__PURE__ */ e(i, { children: n }), v = (n) => n.map((t, o) => /* @__PURE__ */ e("p", { style: { textAlign: "center", color: "darkorange" }, children: t }, o));
5
+ function L({
6
+ fetchFn: n,
7
+ deps: t,
8
+ children: o,
9
+ emptyState: s,
10
+ errorState: d,
11
+ guards: u,
12
+ isEmpty: m = P,
13
+ loadingProps: p,
14
+ renderWarnings: f
15
+ }) {
16
+ const { data: r, loading: l, error: g, warnings: a, setData: x, refetch: y } = B({ fetchFn: n, deps: t }), D = { setData: x, refetch: y, warnings: a };
17
+ if (!l && g)
18
+ return /* @__PURE__ */ e(i, { children: c(d || g, "red") });
19
+ if (!l && r !== null && m(r) && s !== void 0)
20
+ return /* @__PURE__ */ e(i, { children: c(s) });
21
+ if (!l && r !== null && u)
22
+ for (const w of u) {
23
+ const h = w(r);
24
+ if (h !== null) return /* @__PURE__ */ e(i, { children: h });
25
+ }
26
+ const j = a.length > 0 && f !== null ? (f ?? v)(a) : null;
27
+ return /* @__PURE__ */ A(k, { loading: l, ...p, children: [
28
+ j,
29
+ r !== null ? o(r, D) : null
30
+ ] });
31
+ }
32
+ export {
33
+ L as default
34
+ };
@@ -0,0 +1,2 @@
1
+ export { default, type DataPageProps } from './DataPage';
2
+ export { default as useDataPage, withWarnings, type UseDataPageOptions, type UseDataPageReturn, type DataPageControls, type DataPageResult, } from './useDataPage';
@@ -0,0 +1,7 @@
1
+ import { default as t } from "./DataPage.js";
2
+ import { default as f, withWarnings as o } from "./useDataPage.js";
3
+ export {
4
+ t as default,
5
+ f as useDataPage,
6
+ o as withWarnings
7
+ };
@@ -0,0 +1,33 @@
1
+ declare const DATA_PAGE_RESULT: unique symbol;
2
+ export interface DataPageResult<T> {
3
+ [DATA_PAGE_RESULT]: true;
4
+ data: T;
5
+ warnings: string[];
6
+ }
7
+ /** Wrap a fetchFn return value to signal partial success with warnings. */
8
+ export declare function withWarnings<T>(data: T, warnings: string[]): DataPageResult<T>;
9
+ export interface DataPageControls<T> {
10
+ /** Update local data without re-fetching. For optimistic CRUD (add/edit/delete). */
11
+ setData: (updater: (prev: T) => T) => void;
12
+ /** Re-run fetchFn from scratch (resets to loading state). */
13
+ refetch: () => void;
14
+ /** Warnings from partial success (empty array if none). */
15
+ warnings: string[];
16
+ }
17
+ export interface UseDataPageOptions<T> {
18
+ /** Async function that fetches data. Throw for fatal error. Return withWarnings() for partial success. */
19
+ fetchFn: () => Promise<T | DataPageResult<T>>;
20
+ /** useEffect dependency array. Default: [] (fetch once on mount). */
21
+ deps?: unknown[];
22
+ }
23
+ export interface UseDataPageReturn<T> {
24
+ data: T | null;
25
+ loading: boolean;
26
+ error: string | null;
27
+ warnings: string[];
28
+ setData: (updater: (prev: T) => T) => void;
29
+ refetch: () => void;
30
+ }
31
+ export declare const defaultIsEmpty: <T>(data: T) => boolean;
32
+ declare function useDataPage<T>({ fetchFn, deps }: UseDataPageOptions<T>): UseDataPageReturn<T>;
33
+ export default useDataPage;
@@ -0,0 +1,38 @@
1
+ import { useState as n, useRef as R, useEffect as b, useCallback as d } from "react";
2
+ const g = Symbol("DataPageResult");
3
+ function S(e, a) {
4
+ return { [g]: !0, data: e, warnings: a };
5
+ }
6
+ const x = (e) => e == null ? !0 : Array.isArray(e) ? e.length === 0 : !1;
7
+ function F({ fetchFn: e, deps: a = [] }) {
8
+ const [y, c] = n(null), [h, u] = n(!0), [w, o] = n(null), [A, l] = n([]), [E, m] = n(0), f = R(e);
9
+ f.current = e, b(() => {
10
+ let t = !1;
11
+ return u(!0), o(null), l([]), (async () => {
12
+ try {
13
+ const r = await f.current();
14
+ if (t) return;
15
+ if (r && typeof r == "object" && g in r) {
16
+ const i = r;
17
+ c(i.data), l(i.warnings);
18
+ } else
19
+ c(r);
20
+ } catch (r) {
21
+ t || o(r instanceof Error ? r.message : "An unexpected error occurred");
22
+ } finally {
23
+ t || u(!1);
24
+ }
25
+ })(), () => {
26
+ t = !0;
27
+ };
28
+ }, [...a, E]);
29
+ const p = d((t) => {
30
+ c((s) => s !== null ? t(s) : s);
31
+ }, []), D = d(() => m((t) => t + 1), []);
32
+ return { data: y, loading: h, error: w, warnings: A, setData: p, refetch: D };
33
+ }
34
+ export {
35
+ F as default,
36
+ x as defaultIsEmpty,
37
+ S as withWarnings
38
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jcicl",
3
3
  "private": false,
4
- "version": "1.0.59",
4
+ "version": "1.0.61",
5
5
  "description": "Component library for the websites of Johnson County Iowa",
6
6
  "license": "MIT",
7
7
  "homepage": "https://devops.jc.net/JCIT/Business%20Solutions%20Delivery/_git/JCComponentLibrary?path=%2FREADME.md&version=GBmaster",
package/utils.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare const isValidZipCode: (zip: string) => boolean;
6
6
  export declare const isValidCityName: (city: string) => boolean;
7
7
  export declare const formatDate: (date: string, style?: "mmddyyyy") => string;
8
8
  export declare const formatDateTime: (dateTime: string) => string;
9
+ export declare const calculateAge: (birthDateString: string) => number;
9
10
  export declare const formatAddress: (address: {
10
11
  line1: string;
11
12
  line2?: string | null;
package/utils.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import a from "./theme.js";
2
- import { u as m } from "./.chunks/useMediaQuery.js";
3
- const D = () => m(`(max-width: ${a.screenSizes.mobile})`), S = () => m(`(max-width: ${a.screenSizes.tablet})`), w = (t) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t.trim()), R = (t) => {
2
+ import { u as l } from "./.chunks/useMediaQuery.js";
3
+ const Z = () => l(`(max-width: ${a.screenSizes.mobile})`), w = () => l(`(max-width: ${a.screenSizes.tablet})`), R = (t) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t.trim()), T = (t) => {
4
4
  if (!t) return !1;
5
5
  const e = t.replace(/\D/g, "");
6
6
  return !!(t.trim() == e && e.length >= 10 && e.length <= 16 || /^\s*(?:\+?1\s*(?:[-\s]\s*)?)?(?:\(\s*\d{3}\s*\)|\d{3})(?:\s*[-\s]\s*)?\d{3}(?:\s*[-\s]\s*)?\d{4}(?:\s*(?:#|x\.?|ext\.?|extension)\s*\d{1,6})?\s*$/.test(t.trim()));
7
- }, b = (t) => /^\d{5}(?:\s*-?\s*\d{4})?$/.test(t), C = (t) => /^[a-zA-Z\s'.-]+$/.test(t), T = (t, e = "mmddyyyy") => e === "mmddyyyy" ? (/* @__PURE__ */ new Date(t.replace(" ", "T").replace("Z", "") + "Z")).toLocaleDateString("en-US", {
7
+ }, C = (t) => /^\d{5}(?:\s*-?\s*\d{4})?$/.test(t), S = (t) => /^[a-zA-Z\s'.-]+$/.test(t), b = (t, e = "mmddyyyy") => e === "mmddyyyy" ? (/* @__PURE__ */ new Date(t.replace(" ", "T").replace("Z", "") + "Z")).toLocaleDateString("en-US", {
8
8
  timeZone: "America/Chicago",
9
9
  month: "numeric",
10
10
  day: "numeric",
11
11
  year: "numeric"
12
- }) : t, z = (t) => {
12
+ }) : t, A = (t) => {
13
13
  try {
14
14
  return (/* @__PURE__ */ new Date(t.replace(" ", "T").replace("Z", "") + "Z")).toLocaleString("en-US", {
15
15
  timeZone: "America/Chicago",
@@ -23,10 +23,16 @@ const D = () => m(`(max-width: ${a.screenSizes.mobile})`), S = () => m(`(max-wid
23
23
  } catch {
24
24
  return t;
25
25
  }
26
- }, A = (t) => {
27
- const { line1: e, line2: n, city: s, state: i, zip: o } = t;
26
+ }, z = (t) => {
27
+ t = t.slice(0, 10) + "T00:00:00";
28
+ const e = /* @__PURE__ */ new Date(), i = new Date(t);
29
+ let s = e.getFullYear() - i.getFullYear();
30
+ const n = e.getMonth() - i.getMonth();
31
+ return (n < 0 || n === 0 && e.getDate() < i.getDate()) && s--, s;
32
+ }, N = (t) => {
33
+ const { line1: e, line2: i, city: s, state: n, zip: o } = t;
28
34
  let r = e || "";
29
- return n && (r.length > 0 && (r += ", "), r += n), s && (r.length > 0 && (r += ", "), r += s), i && (r.length > 0 && (r += ", "), r += i), o && (r += ` ${p(o)[0]}`), r;
35
+ return i && (r.length > 0 && (r += ", "), r += i), s && (r.length > 0 && (r += ", "), r += s), n && (r.length > 0 && (r += ", "), r += n), o && (r += ` ${p(o)[0]}`), r;
30
36
  }, $ = (t) => {
31
37
  if (t > 3 && t < 21) return "th";
32
38
  switch (t % 10) {
@@ -39,7 +45,7 @@ const D = () => m(`(max-width: ${a.screenSizes.mobile})`), S = () => m(`(max-wid
39
45
  default:
40
46
  return "th";
41
47
  }
42
- }, N = (t) => {
48
+ }, P = (t) => {
43
49
  try {
44
50
  const e = /* @__PURE__ */ new Date(t.replace(" ", "T").replace("Z", "") + "Z"), s = new Intl.DateTimeFormat("en-US", {
45
51
  timeZone: "America/Chicago",
@@ -49,37 +55,38 @@ const D = () => m(`(max-width: ${a.screenSizes.mobile})`), S = () => m(`(max-wid
49
55
  hour: "numeric",
50
56
  minute: "2-digit",
51
57
  hour12: !0
52
- }).formatToParts(e), i = (g) => {
58
+ }).formatToParts(e), n = (h) => {
53
59
  var c;
54
- return ((c = s.find((y) => y.type === g)) == null ? void 0 : c.value) ?? "";
55
- }, o = i("month") + ".", r = Number(i("day")), u = i("year"), l = i("hour"), d = i("minute"), f = i("dayPeriod").toLowerCase(), h = $(r);
56
- return `${o} ${r}${h}, ${u}, ${l}:${d}${f}`;
60
+ return ((c = s.find((y) => y.type === h)) == null ? void 0 : c.value) ?? "";
61
+ }, o = n("month") + ".", r = Number(n("day")), u = n("year"), m = n("hour"), d = n("minute"), f = n("dayPeriod").toLowerCase(), g = $(r);
62
+ return `${o} ${r}${g}, ${u}, ${m}:${d}${f}`;
57
63
  } catch {
58
64
  return t;
59
65
  }
60
- }, P = (t) => {
66
+ }, v = (t) => {
61
67
  if (t == null) return ["", void 0];
62
68
  let e = t.replace(/\D/g, "") || "";
63
69
  e.at(0) == "1" && (e = e.slice(1));
64
- const n = e.slice(0, 3), s = e.slice(3, 6), i = e.slice(6, 10), o = e.slice(10, 16);
70
+ const i = e.slice(0, 3), s = e.slice(3, 6), n = e.slice(6, 10), o = e.slice(10, 16);
65
71
  let r = e;
66
- return e.length >= 4 && (r = `(${n}) ${e.slice(3)}`), e.length >= 7 && (r = `(${n}) ${s}-${e.slice(6)}`), e.length > 10 && (r = `(${n}) ${s}-${i} ext. ${o}`), [r, e.slice(0, 16)];
72
+ return e.length >= 4 && (r = `(${i}) ${e.slice(3)}`), e.length >= 7 && (r = `(${i}) ${s}-${e.slice(6)}`), e.length > 10 && (r = `(${i}) ${s}-${n} ext. ${o}`), [r, e.slice(0, 16)];
67
73
  }, p = (t) => {
68
74
  if (!t) return ["", void 0];
69
75
  const e = t.replace(/\D/g, "");
70
76
  return e.length <= 5 ? [e, e] : [`${e.slice(0, 5)}-${e.slice(5, 9)}`, e.slice(0, 9)];
71
77
  };
72
78
  export {
73
- A as formatAddress,
74
- T as formatDate,
75
- z as formatDateTime,
76
- P as formatPhoneNumber,
77
- N as formatTimestamp,
79
+ z as calculateAge,
80
+ N as formatAddress,
81
+ b as formatDate,
82
+ A as formatDateTime,
83
+ v as formatPhoneNumber,
84
+ P as formatTimestamp,
78
85
  p as formatZipCode,
79
- D as isMobile,
80
- S as isTablet,
81
- C as isValidCityName,
82
- w as isValidEmail,
83
- R as isValidPhoneNumber,
84
- b as isValidZipCode
86
+ Z as isMobile,
87
+ w as isTablet,
88
+ S as isValidCityName,
89
+ R as isValidEmail,
90
+ T as isValidPhoneNumber,
91
+ C as isValidZipCode
85
92
  };