@greatapps/common 1.1.536 → 1.1.540

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.
@@ -1,6 +1,7 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Slot } from "@radix-ui/react-slot";
4
+ import { IconLoader2 } from "@tabler/icons-react";
4
5
  import { cva } from "class-variance-authority";
5
6
  import { cn } from "../../../infra/utils/clsx";
6
7
  import { useIsDefaultWhitelabel } from "../../../providers/whitelabel.provider";
@@ -34,19 +35,28 @@ function Button({
34
35
  variant,
35
36
  size,
36
37
  asChild = false,
38
+ loading = false,
39
+ children,
40
+ disabled,
37
41
  ...props
38
42
  }) {
39
43
  const isDefaultWl = useIsDefaultWhitelabel();
40
44
  const Comp = asChild ? Slot : "button";
45
+ const content = asChild ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
46
+ loading && /* @__PURE__ */ jsx(IconLoader2, { className: "size-4 animate-spin" }),
47
+ children
48
+ ] });
41
49
  return /* @__PURE__ */ jsx(
42
50
  Comp,
43
51
  {
44
52
  "data-slot": "button",
53
+ disabled: disabled || loading,
45
54
  className: cn(
46
55
  buttonVariants({ variant, size, className }),
47
56
  variant === "brand" && (isDefaultWl ? "bg-cyan-300 hover:bg-cyan-200" : "bg-zinc-300 hover:bg-zinc-200")
48
57
  ),
49
- ...props
58
+ ...props,
59
+ children: content
50
60
  }
51
61
  );
52
62
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/ui/buttons/Button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../../../infra/utils/clsx\"\nimport { useIsDefaultWhitelabel } from \"../../../providers/whitelabel.provider\"\n\nconst buttonVariants = cva(\n \"paragraph-small-semibold cursor-pointer flex items-center justify-center gap-1.5 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n {\n variants: {\n variant: {\n default: \" bg-gray-950 text-white hover:bg-gray-950/90\",\n secondary:\n \"bg-white text-gray-950 border-gray-200 hover:border-gray-400 border\",\n ghost:\n \"text-gray-600 hover:bg-accent hover:text-gray-950 dark:hover:bg-accent/50\",\n brand: \"text-gray-950\",\n },\n size: {\n default: \"h-12 px-4 py-2 has-[>svg]:px-3\",\n sm: \"rounded-md gap-1.5 px-2 py-2 has-[>svg]:px-2.5\",\n lg: \"rounded-md px-6 has-[>svg]:px-4\",\n icon: \"size-9\",\n \"icon-sm\": \"size-8\",\n \"icon-lg\": \"size-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nfunction Button({\n className,\n variant,\n size,\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean\n }) {\n const isDefaultWl = useIsDefaultWhitelabel();\n const Comp = asChild ? Slot : \"button\"\n\n return (\n <Comp\n data-slot=\"button\"\n className={cn(\n buttonVariants({ variant, size, className }),\n variant === \"brand\" && (isDefaultWl ? \"bg-cyan-300 hover:bg-cyan-200\" : \"bg-zinc-300 hover:bg-zinc-200\")\n )}\n {...props}\n />\n )\n}\n\nexport { Button, buttonVariants }\n"],"mappings":";AAkDI;AA/CJ,SAAS,YAAY;AACrB,SAAS,WAA8B;AACvC,SAAS,UAAU;AACnB,SAAS,8BAA8B;AAEvC,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,WACE;AAAA,QACF,OACE;AAAA,QACF,OAAO;AAAA,MACT;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,WAAW;AAAA,QACX,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,OAAO;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,GAAG;AACL,GAGK;AACH,QAAM,cAAc,uBAAuB;AAC3C,QAAM,OAAO,UAAU,OAAO;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC;AAAA,QAC3C,YAAY,YAAY,cAAc,kCAAkC;AAAA,MAC1E;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/ui/buttons/Button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { IconLoader2 } from \"@tabler/icons-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../../../infra/utils/clsx\"\nimport { useIsDefaultWhitelabel } from \"../../../providers/whitelabel.provider\"\n\nconst buttonVariants = cva(\n \"paragraph-small-semibold cursor-pointer flex items-center justify-center gap-1.5 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n {\n variants: {\n variant: {\n default: \" bg-gray-950 text-white hover:bg-gray-950/90\",\n secondary:\n \"bg-white text-gray-950 border-gray-200 hover:border-gray-400 border\",\n ghost:\n \"text-gray-600 hover:bg-accent hover:text-gray-950 dark:hover:bg-accent/50\",\n brand: \"text-gray-950\",\n },\n size: {\n default: \"h-12 px-4 py-2 has-[>svg]:px-3\",\n sm: \"rounded-md gap-1.5 px-2 py-2 has-[>svg]:px-2.5\",\n lg: \"rounded-md px-6 has-[>svg]:px-4\",\n icon: \"size-9\",\n \"icon-sm\": \"size-8\",\n \"icon-lg\": \"size-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nfunction Button({\n className,\n variant,\n size,\n asChild = false,\n loading = false,\n children,\n disabled,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean\n loading?: boolean\n }) {\n const isDefaultWl = useIsDefaultWhitelabel();\n const Comp = asChild ? Slot : \"button\"\n\n const content = asChild ? children : (\n <>\n {loading && <IconLoader2 className=\"size-4 animate-spin\" />}\n {children}\n </>\n )\n\n return (\n <Comp\n data-slot=\"button\"\n disabled={disabled || loading}\n className={cn(\n buttonVariants({ variant, size, className }),\n variant === \"brand\" && (isDefaultWl ? \"bg-cyan-300 hover:bg-cyan-200\" : \"bg-zinc-300 hover:bg-zinc-200\")\n )}\n {...props}\n >\n {content}\n </Comp>\n )\n}\n\nexport { Button, buttonVariants }\n"],"mappings":";AAuDI,mBACc,KADd;AApDJ,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAC5B,SAAS,WAA8B;AACvC,SAAS,UAAU;AACnB,SAAS,8BAA8B;AAEvC,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,WACE;AAAA,QACF,OACE;AAAA,QACF,OAAO;AAAA,MACT;AAAA,MACA,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,WAAW;AAAA,QACX,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,OAAO;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAIK;AACH,QAAM,cAAc,uBAAuB;AAC3C,QAAM,OAAO,UAAU,OAAO;AAE9B,QAAM,UAAU,UAAU,WACxB,iCACG;AAAA,eAAW,oBAAC,eAAY,WAAU,uBAAsB;AAAA,IACxD;AAAA,KACH;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,UAAU,YAAY;AAAA,MACtB,WAAW;AAAA,QACT,eAAe,EAAE,SAAS,MAAM,UAAU,CAAC;AAAA,QAC3C,YAAY,YAAY,cAAc,kCAAkC;AAAA,MAC1E;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -3,6 +3,69 @@ function insertSorted(data, newItem, compareFn) {
3
3
  if (index === -1) return [...data, newItem];
4
4
  return [...data.slice(0, index), newItem, ...data.slice(index)];
5
5
  }
6
+ function insertIntoPaginatedCache({
7
+ pages,
8
+ pageNumbers,
9
+ newItem,
10
+ pageSize,
11
+ compareFn
12
+ }) {
13
+ if (!pages.length) return pages;
14
+ if (!compareFn) {
15
+ const firstIdx = pageNumbers.indexOf(1);
16
+ if (firstIdx === -1) return pages;
17
+ const result2 = pages.slice();
18
+ let carry2 = newItem;
19
+ for (let i = firstIdx; i < result2.length && carry2 !== null; i++) {
20
+ const current = result2[i];
21
+ const nextData = [carry2, ...current.data];
22
+ const overflow = nextData.length > pageSize ? nextData[nextData.length - 1] : null;
23
+ result2[i] = {
24
+ ...current,
25
+ data: overflow !== null ? nextData.slice(0, pageSize) : nextData,
26
+ total: current.total + 1
27
+ };
28
+ carry2 = overflow;
29
+ }
30
+ return result2;
31
+ }
32
+ let targetIdx = -1;
33
+ for (let i = 0; i < pages.length; i++) {
34
+ const page = pages[i];
35
+ if (!page.data.length) continue;
36
+ const last = page.data[page.data.length - 1];
37
+ if (compareFn(newItem, last) <= 0) {
38
+ targetIdx = i;
39
+ break;
40
+ }
41
+ }
42
+ if (targetIdx === -1) {
43
+ const lastIdx = pages.length - 1;
44
+ const lastPage = pages[lastIdx];
45
+ if (lastPage.data.length < pageSize) {
46
+ targetIdx = lastIdx;
47
+ } else {
48
+ return pages.map((p) => ({ ...p, total: p.total + 1 }));
49
+ }
50
+ }
51
+ const result = pages.slice();
52
+ let carry = newItem;
53
+ for (let i = targetIdx; i < result.length && carry !== null; i++) {
54
+ const current = result[i];
55
+ const withInserted = insertSorted(current.data, carry, compareFn);
56
+ const overflow = withInserted.length > pageSize ? withInserted[withInserted.length - 1] : null;
57
+ result[i] = {
58
+ ...current,
59
+ data: overflow !== null ? withInserted.slice(0, pageSize) : withInserted,
60
+ total: current.total + 1
61
+ };
62
+ carry = overflow;
63
+ }
64
+ for (let i = 0; i < targetIdx; i++) {
65
+ result[i] = { ...result[i], total: result[i].total + 1 };
66
+ }
67
+ return result;
68
+ }
6
69
  function buildCompareFn(sort) {
7
70
  const fields = sort.split(",").map((f) => f.trim()).filter(Boolean);
8
71
  const parsedFields = fields.map((field) => {
@@ -29,6 +92,7 @@ function buildCompareFn(sort) {
29
92
  }
30
93
  export {
31
94
  buildCompareFn,
95
+ insertIntoPaginatedCache,
32
96
  insertSorted
33
97
  };
34
98
  //# sourceMappingURL=array.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/infra/utils/array.ts"],"sourcesContent":["/**\n * Insere um item em um array já ordenado, mantendo a ordenação\n * com base na função de comparação fornecida.\n */\nexport function insertSorted<T>(\n data: T[],\n newItem: T,\n compareFn: (a: T, b: T) => number\n): T[] {\n const index = data.findIndex((item) => compareFn(newItem, item) < 0);\n if (index === -1) return [...data, newItem];\n return [...data.slice(0, index), newItem, ...data.slice(index)];\n}\n\n/**\n * Cria uma função de comparação a partir de uma string de sort da API.\n * Suporta múltiplos campos separados por vírgula e prefixo `-` para desc.\n *\n * Exemplos:\n * - \"title\" → ascendente por title\n * - \"-title\" → descendente por title\n * - \"title,-datetime_add\" → ascendente por title, depois descendente por datetime_add\n */\nexport function buildCompareFn<T extends Record<string, unknown>>(\n sort: string\n): (a: T, b: T) => number {\n const fields = sort.split(',').map((f) => f.trim()).filter(Boolean);\n\n const parsedFields = fields.map((field) => {\n const desc = field.startsWith('-');\n const key = desc ? field.slice(1) : field;\n return { key, desc };\n });\n\n return (a: T, b: T) => {\n for (const { key, desc } of parsedFields) {\n const aVal = a[key];\n const bVal = b[key];\n\n let result: number;\n\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n result = aVal.localeCompare(bVal);\n } else if (typeof aVal === 'number' && typeof bVal === 'number') {\n result = aVal - bVal;\n } else {\n result = String(aVal ?? '').localeCompare(String(bVal ?? ''));\n }\n\n if (result !== 0) return desc ? -result : result;\n }\n return 0;\n };\n}\n"],"mappings":"AAIO,SAAS,aACd,MACA,SACA,WACK;AACL,QAAM,QAAQ,KAAK,UAAU,CAAC,SAAS,UAAU,SAAS,IAAI,IAAI,CAAC;AACnE,MAAI,UAAU,GAAI,QAAO,CAAC,GAAG,MAAM,OAAO;AAC1C,SAAO,CAAC,GAAG,KAAK,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,MAAM,KAAK,CAAC;AAChE;AAWO,SAAS,eACd,MACwB;AACxB,QAAM,SAAS,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAElE,QAAM,eAAe,OAAO,IAAI,CAAC,UAAU;AACzC,UAAM,OAAO,MAAM,WAAW,GAAG;AACjC,UAAM,MAAM,OAAO,MAAM,MAAM,CAAC,IAAI;AACpC,WAAO,EAAE,KAAK,KAAK;AAAA,EACrB,CAAC;AAED,SAAO,CAAC,GAAM,MAAS;AACrB,eAAW,EAAE,KAAK,KAAK,KAAK,cAAc;AACxC,YAAM,OAAO,EAAE,GAAG;AAClB,YAAM,OAAO,EAAE,GAAG;AAElB,UAAI;AAEJ,UAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU;AACxD,iBAAS,KAAK,cAAc,IAAI;AAAA,MAClC,WAAW,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU;AAC/D,iBAAS,OAAO;AAAA,MAClB,OAAO;AACL,iBAAS,OAAO,QAAQ,EAAE,EAAE,cAAc,OAAO,QAAQ,EAAE,CAAC;AAAA,MAC9D;AAEA,UAAI,WAAW,EAAG,QAAO,OAAO,CAAC,SAAS;AAAA,IAC5C;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/infra/utils/array.ts"],"sourcesContent":["/**\n * Insere um item em um array já ordenado, mantendo a ordenação\n * com base na função de comparação fornecida.\n */\nexport function insertSorted<T>(\n data: T[],\n newItem: T,\n compareFn: (a: T, b: T) => number\n): T[] {\n const index = data.findIndex((item) => compareFn(newItem, item) < 0);\n if (index === -1) return [...data, newItem];\n return [...data.slice(0, index), newItem, ...data.slice(index)];\n}\n\nexport interface PaginatedPageCache<T> {\n data: T[];\n total: number;\n}\n\nexport interface InsertIntoPaginatedCacheParams<T> {\n /** Páginas atuais do cache, ordenadas por número de página (1..N). */\n pages: PaginatedPageCache<T>[];\n /** Números das páginas correspondentes (mesmo tamanho de `pages`). */\n pageNumbers: number[];\n /** Novo item a inserir. */\n newItem: T;\n /** Tamanho fixo de cada página. */\n pageSize: number;\n /**\n * Função de comparação que mantém a ordem.\n * Se não fornecida, insere no topo da página 1 (sem reordenar).\n */\n compareFn?: (a: T, b: T) => number;\n}\n\n/**\n * Insere um item em um conjunto de páginas paginadas mantendo ordenação.\n *\n * Quando `compareFn` é fornecida, descobre em qual página o item deveria\n * cair (comparando com o último item de cada página). Insere na posição\n * correta e empurra o último item da página para a próxima, propagando\n * o overflow até a última página disponível.\n *\n * Páginas não-afetadas retornam a mesma referência.\n */\nexport function insertIntoPaginatedCache<T>({\n pages,\n pageNumbers,\n newItem,\n pageSize,\n compareFn,\n}: InsertIntoPaginatedCacheParams<T>): PaginatedPageCache<T>[] {\n if (!pages.length) return pages;\n\n // Sem sort: insere no topo da página 1 e propaga overflow\n if (!compareFn) {\n // Só atualiza se a página 1 estiver no cache\n const firstIdx = pageNumbers.indexOf(1);\n if (firstIdx === -1) return pages;\n\n const result: PaginatedPageCache<T>[] = pages.slice();\n let carry: T | null = newItem;\n for (let i = firstIdx; i < result.length && carry !== null; i++) {\n const current = result[i];\n const nextData: T[] = [carry, ...current.data];\n const overflow: T | null =\n nextData.length > pageSize ? nextData[nextData.length - 1] : null;\n result[i] = {\n ...current,\n data: overflow !== null ? nextData.slice(0, pageSize) : nextData,\n total: current.total + 1,\n };\n carry = overflow;\n }\n return result;\n }\n\n // Descobre em qual página o item cai\n let targetIdx = -1;\n for (let i = 0; i < pages.length; i++) {\n const page = pages[i];\n if (!page.data.length) continue;\n const last = page.data[page.data.length - 1];\n // Se newItem vem antes (ou igual) ao último item desta página, cai aqui\n if (compareFn(newItem, last) <= 0) {\n targetIdx = i;\n break;\n }\n }\n\n // Não coube em nenhuma página com itens — tenta a última página se for incompleta\n if (targetIdx === -1) {\n const lastIdx = pages.length - 1;\n const lastPage = pages[lastIdx];\n if (lastPage.data.length < pageSize) {\n targetIdx = lastIdx;\n } else {\n // Não cai em nenhuma página carregada — apenas incrementa total\n return pages.map((p) => ({ ...p, total: p.total + 1 }));\n }\n }\n\n // Insere no target e propaga overflow para as páginas seguintes\n const result: PaginatedPageCache<T>[] = pages.slice();\n let carry: T | null = newItem;\n\n for (let i = targetIdx; i < result.length && carry !== null; i++) {\n const current = result[i];\n const withInserted: T[] = insertSorted(current.data, carry, compareFn);\n const overflow: T | null =\n withInserted.length > pageSize ? withInserted[withInserted.length - 1] : null;\n result[i] = {\n ...current,\n data: overflow !== null ? withInserted.slice(0, pageSize) : withInserted,\n total: current.total + 1,\n };\n carry = overflow;\n }\n\n // Incrementa o total nas páginas anteriores ao target (não mudaram mas o total global cresceu)\n for (let i = 0; i < targetIdx; i++) {\n result[i] = { ...result[i], total: result[i].total + 1 };\n }\n\n return result;\n}\n\n/**\n * Cria uma função de comparação a partir de uma string de sort da API.\n * Suporta múltiplos campos separados por vírgula e prefixo `-` para desc.\n *\n * Exemplos:\n * - \"title\" → ascendente por title\n * - \"-title\" → descendente por title\n * - \"title,-datetime_add\" → ascendente por title, depois descendente por datetime_add\n */\nexport function buildCompareFn<T extends Record<string, unknown>>(\n sort: string\n): (a: T, b: T) => number {\n const fields = sort.split(',').map((f) => f.trim()).filter(Boolean);\n\n const parsedFields = fields.map((field) => {\n const desc = field.startsWith('-');\n const key = desc ? field.slice(1) : field;\n return { key, desc };\n });\n\n return (a: T, b: T) => {\n for (const { key, desc } of parsedFields) {\n const aVal = a[key];\n const bVal = b[key];\n\n let result: number;\n\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n result = aVal.localeCompare(bVal);\n } else if (typeof aVal === 'number' && typeof bVal === 'number') {\n result = aVal - bVal;\n } else {\n result = String(aVal ?? '').localeCompare(String(bVal ?? ''));\n }\n\n if (result !== 0) return desc ? -result : result;\n }\n return 0;\n };\n}\n"],"mappings":"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","names":["result","carry"]}
@@ -4,16 +4,16 @@ import { createProjectAction } from "../actions/create-project.action";
4
4
  import { withAction } from "../../../utils/withAction";
5
5
  import { useAuthQueryKey } from "../../../hooks/useAuthQueryKey";
6
6
  import { PROJECTS_BASE_KEY } from "./list-projects.hook";
7
- import { insertSorted, buildCompareFn } from "../../../infra/utils/array";
8
- function extractSort(query) {
7
+ import { buildCompareFn, insertIntoPaginatedCache } from "../../../infra/utils/array";
8
+ function extractQueryParams(query) {
9
9
  const key = query.queryKey;
10
10
  for (let i = key.length - 1; i >= 0; i--) {
11
11
  const segment = key[i];
12
- if (segment && typeof segment === "object" && "sort" in segment) {
13
- return segment.sort;
12
+ if (segment !== null && typeof segment === "object") {
13
+ return segment;
14
14
  }
15
15
  }
16
- return void 0;
16
+ return {};
17
17
  }
18
18
  function useCreateProject() {
19
19
  const queryClient = useQueryClient();
@@ -23,42 +23,65 @@ function useCreateProject() {
23
23
  onSuccess: (newProject) => {
24
24
  const cache = queryClient.getQueryCache();
25
25
  const queries = cache.findAll({ queryKey: projectsKey });
26
- for (const query of queries) {
27
- const sort = extractSort(query);
28
- const isInfinite = query.queryKey.includes("infinite");
29
- if (isInfinite) {
30
- queryClient.setQueryData(
31
- query.queryKey,
32
- (old) => {
33
- if (!old?.pages.length) return old;
34
- const pageSize = old.pages[0].data.length || 20;
35
- const allItems = old.pages.flatMap((page) => page.data);
36
- const withNew = sort ? insertSorted(allItems, newProject, buildCompareFn(sort)) : [newProject, ...allItems];
37
- const newTotal = (old.pages[0].total ?? 0) + 1;
38
- return {
39
- ...old,
40
- pages: old.pages.map((page, i) => ({
41
- ...page,
42
- data: withNew.slice(i * pageSize, (i + 1) * pageSize),
43
- total: newTotal
44
- }))
45
- };
46
- }
47
- );
26
+ const infiniteQueries = queries.filter((q) => q.queryKey.includes("infinite"));
27
+ for (const query of infiniteQueries) {
28
+ const { sort } = extractQueryParams(query);
29
+ const compareFn = sort ? buildCompareFn(sort) : void 0;
30
+ queryClient.setQueryData(query.queryKey, (old) => {
31
+ if (!old?.pages.length) return old;
32
+ const pageSize = old.pages[0].data.length || 20;
33
+ const newPages = insertIntoPaginatedCache({
34
+ pages: old.pages,
35
+ pageNumbers: old.pages.map((_, i) => i + 1),
36
+ newItem: newProject,
37
+ pageSize,
38
+ compareFn
39
+ });
40
+ return { ...old, pages: newPages.map((p, i) => ({ ...old.pages[i], ...p })) };
41
+ });
42
+ }
43
+ const paginatedQueries = queries.filter((q) => !q.queryKey.includes("infinite"));
44
+ const groups = /* @__PURE__ */ new Map();
45
+ for (const query of paginatedQueries) {
46
+ const params = extractQueryParams(query);
47
+ const { page: _page, ...rest } = params;
48
+ void _page;
49
+ const signature = JSON.stringify(rest);
50
+ const existing = groups.get(signature);
51
+ if (existing) {
52
+ existing.queries.push(query);
48
53
  } else {
49
- queryClient.setQueryData(
50
- query.queryKey,
51
- (old) => {
52
- if (!old) return old;
53
- const pageSize = old.data.length || 20;
54
- const withNew = sort ? insertSorted(old.data, newProject, buildCompareFn(sort)) : [newProject, ...old.data];
55
- return {
56
- ...old,
57
- data: withNew.slice(0, pageSize),
58
- total: old.total + 1
59
- };
60
- }
61
- );
54
+ groups.set(signature, { queries: [query], sort: params.sort });
55
+ }
56
+ }
57
+ for (const { queries: groupQueries, sort } of groups.values()) {
58
+ const compareFn = sort ? buildCompareFn(sort) : void 0;
59
+ const sortedByPage = groupQueries.map((q) => ({ query: q, page: extractQueryParams(q).page ?? 1 })).sort((a, b) => a.page - b.page);
60
+ const pages = [];
61
+ const pageNumbers = [];
62
+ for (const { query, page } of sortedByPage) {
63
+ const data = queryClient.getQueryData(query.queryKey);
64
+ if (data) {
65
+ pages.push(data);
66
+ pageNumbers.push(page);
67
+ }
68
+ }
69
+ if (!pages.length) continue;
70
+ const pageSize = pages[0].data.length || 20;
71
+ const newPages = insertIntoPaginatedCache({
72
+ pages,
73
+ pageNumbers,
74
+ newItem: newProject,
75
+ pageSize,
76
+ compareFn
77
+ });
78
+ for (let i = 0; i < sortedByPage.length; i++) {
79
+ if (newPages[i] !== pages[i]) {
80
+ queryClient.setQueryData(sortedByPage[i].query.queryKey, {
81
+ ...pages[i],
82
+ ...newPages[i]
83
+ });
84
+ }
62
85
  }
63
86
  }
64
87
  queryClient.invalidateQueries({ queryKey: projectsKey });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/projects/hooks/create-project.hook.ts"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient, InfiniteData, Query } from '@tanstack/react-query';\nimport { createProjectAction } from '../actions/create-project.action';\nimport { withAction } from '../../../utils/withAction';\nimport { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';\nimport { PROJECTS_BASE_KEY } from './list-projects.hook';\nimport { insertSorted, buildCompareFn } from '../../../infra/utils/array';\nimport type { CreateProjectRequest, Project, ProjectsPage } from '../types';\n\nfunction extractSort(query: Query): string | undefined {\n const key = query.queryKey;\n for (let i = key.length - 1; i >= 0; i--) {\n const segment = key[i];\n if (segment && typeof segment === 'object' && 'sort' in segment) {\n return (segment as Record<string, unknown>).sort as string | undefined;\n }\n }\n return undefined;\n}\n\nexport function useCreateProject() {\n const queryClient = useQueryClient();\n const projectsKey = useAuthQueryKey([...PROJECTS_BASE_KEY]);\n\n return useMutation({\n mutationFn: (data: CreateProjectRequest) =>\n withAction(createProjectAction)(data),\n\n onSuccess: (newProject: Project) => {\n const cache = queryClient.getQueryCache();\n const queries = cache.findAll({ queryKey: projectsKey });\n\n for (const query of queries) {\n const sort = extractSort(query);\n const isInfinite = query.queryKey.includes('infinite');\n\n if (isInfinite) {\n queryClient.setQueryData<InfiniteData<ProjectsPage>>(\n query.queryKey,\n (old) => {\n if (!old?.pages.length) return old;\n\n const pageSize = old.pages[0].data.length || 20;\n const allItems = old.pages.flatMap((page) => page.data);\n\n const withNew = sort\n ? insertSorted(allItems, newProject, buildCompareFn<Project>(sort))\n : [newProject, ...allItems];\n\n const newTotal = (old.pages[0].total ?? 0) + 1;\n\n return {\n ...old,\n pages: old.pages.map((page, i) => ({\n ...page,\n data: withNew.slice(i * pageSize, (i + 1) * pageSize),\n total: newTotal,\n })),\n };\n },\n );\n } else {\n queryClient.setQueryData<ProjectsPage>(\n query.queryKey,\n (old) => {\n if (!old) return old;\n\n const pageSize = old.data.length || 20;\n\n const withNew = sort\n ? insertSorted(old.data, newProject, buildCompareFn<Project>(sort))\n : [newProject, ...old.data];\n\n return {\n ...old,\n data: withNew.slice(0, pageSize),\n total: old.total + 1,\n };\n },\n );\n }\n }\n\n queryClient.invalidateQueries({ queryKey: projectsKey });\n },\n });\n}\n"],"mappings":";AAEA,SAAS,aAAa,sBAA2C;AACjE,SAAS,2BAA2B;AACpC,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,cAAc,sBAAsB;AAG7C,SAAS,YAAY,OAAkC;AACrD,QAAM,MAAM,MAAM;AAClB,WAAS,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK;AACxC,UAAM,UAAU,IAAI,CAAC;AACrB,QAAI,WAAW,OAAO,YAAY,YAAY,UAAU,SAAS;AAC/D,aAAQ,QAAoC;AAAA,IAC9C;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,mBAAmB;AACjC,QAAM,cAAc,eAAe;AACnC,QAAM,cAAc,gBAAgB,CAAC,GAAG,iBAAiB,CAAC;AAE1D,SAAO,YAAY;AAAA,IACjB,YAAY,CAAC,SACX,WAAW,mBAAmB,EAAE,IAAI;AAAA,IAEtC,WAAW,CAAC,eAAwB;AAClC,YAAM,QAAQ,YAAY,cAAc;AACxC,YAAM,UAAU,MAAM,QAAQ,EAAE,UAAU,YAAY,CAAC;AAEvD,iBAAW,SAAS,SAAS;AAC3B,cAAM,OAAO,YAAY,KAAK;AAC9B,cAAM,aAAa,MAAM,SAAS,SAAS,UAAU;AAErD,YAAI,YAAY;AACd,sBAAY;AAAA,YACV,MAAM;AAAA,YACN,CAAC,QAAQ;AACP,kBAAI,CAAC,KAAK,MAAM,OAAQ,QAAO;AAE/B,oBAAM,WAAW,IAAI,MAAM,CAAC,EAAE,KAAK,UAAU;AAC7C,oBAAM,WAAW,IAAI,MAAM,QAAQ,CAAC,SAAS,KAAK,IAAI;AAEtD,oBAAM,UAAU,OACZ,aAAa,UAAU,YAAY,eAAwB,IAAI,CAAC,IAChE,CAAC,YAAY,GAAG,QAAQ;AAE5B,oBAAM,YAAY,IAAI,MAAM,CAAC,EAAE,SAAS,KAAK;AAE7C,qBAAO;AAAA,gBACL,GAAG;AAAA,gBACH,OAAO,IAAI,MAAM,IAAI,CAAC,MAAM,OAAO;AAAA,kBACjC,GAAG;AAAA,kBACH,MAAM,QAAQ,MAAM,IAAI,WAAW,IAAI,KAAK,QAAQ;AAAA,kBACpD,OAAO;AAAA,gBACT,EAAE;AAAA,cACJ;AAAA,YACF;AAAA,UACF;AAAA,QACF,OAAO;AACL,sBAAY;AAAA,YACV,MAAM;AAAA,YACN,CAAC,QAAQ;AACP,kBAAI,CAAC,IAAK,QAAO;AAEjB,oBAAM,WAAW,IAAI,KAAK,UAAU;AAEpC,oBAAM,UAAU,OACZ,aAAa,IAAI,MAAM,YAAY,eAAwB,IAAI,CAAC,IAChE,CAAC,YAAY,GAAG,IAAI,IAAI;AAE5B,qBAAO;AAAA,gBACL,GAAG;AAAA,gBACH,MAAM,QAAQ,MAAM,GAAG,QAAQ;AAAA,gBAC/B,OAAO,IAAI,QAAQ;AAAA,cACrB;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,kBAAY,kBAAkB,EAAE,UAAU,YAAY,CAAC;AAAA,IACzD;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/projects/hooks/create-project.hook.ts"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient, InfiniteData, Query } from '@tanstack/react-query';\nimport { createProjectAction } from '../actions/create-project.action';\nimport { withAction } from '../../../utils/withAction';\nimport { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';\nimport { PROJECTS_BASE_KEY } from './list-projects.hook';\nimport { buildCompareFn, insertIntoPaginatedCache } from '../../../infra/utils/array';\nimport type { CreateProjectRequest, Project, ProjectsPage } from '../types';\n\ntype QueryParams = { sort?: string; page?: number; [key: string]: unknown };\n\nfunction extractQueryParams(query: Query): QueryParams {\n const key = query.queryKey;\n for (let i = key.length - 1; i >= 0; i--) {\n const segment = key[i];\n if (segment !== null && typeof segment === 'object') {\n return segment as QueryParams;\n }\n }\n return {};\n}\n\nexport function useCreateProject() {\n const queryClient = useQueryClient();\n const projectsKey = useAuthQueryKey([...PROJECTS_BASE_KEY]);\n\n return useMutation({\n mutationFn: (data: CreateProjectRequest) =>\n withAction(createProjectAction)(data),\n\n onSuccess: (newProject: Project) => {\n const cache = queryClient.getQueryCache();\n const queries = cache.findAll({ queryKey: projectsKey });\n\n // 1) Caches infinite: todas as páginas vivem no mesmo queryData\n const infiniteQueries = queries.filter((q) => q.queryKey.includes('infinite'));\n\n for (const query of infiniteQueries) {\n const { sort } = extractQueryParams(query);\n const compareFn = sort ? buildCompareFn<Project>(sort) : undefined;\n\n queryClient.setQueryData<InfiniteData<ProjectsPage>>(query.queryKey, (old) => {\n if (!old?.pages.length) return old;\n\n const pageSize = old.pages[0].data.length || 20;\n const newPages = insertIntoPaginatedCache({\n pages: old.pages,\n pageNumbers: old.pages.map((_, i) => i + 1),\n newItem: newProject,\n pageSize,\n compareFn,\n });\n\n return { ...old, pages: newPages.map((p, i) => ({ ...old.pages[i], ...p })) };\n });\n }\n\n // 2) Caches paginados: agrupa queries do mesmo \"sort\" e trata como conjunto\n const paginatedQueries = queries.filter((q) => !q.queryKey.includes('infinite'));\n\n // Agrupa por assinatura dos params (exceto page)\n const groups = new Map<string, { queries: Query[]; sort?: string }>();\n for (const query of paginatedQueries) {\n const params = extractQueryParams(query);\n const { page: _page, ...rest } = params;\n void _page;\n const signature = JSON.stringify(rest);\n const existing = groups.get(signature);\n if (existing) {\n existing.queries.push(query);\n } else {\n groups.set(signature, { queries: [query], sort: params.sort });\n }\n }\n\n for (const { queries: groupQueries, sort } of groups.values()) {\n const compareFn = sort ? buildCompareFn<Project>(sort) : undefined;\n\n // Ordena por número de página\n const sortedByPage = groupQueries\n .map((q) => ({ query: q, page: extractQueryParams(q).page ?? 1 }))\n .sort((a, b) => a.page - b.page);\n\n // Monta as páginas na ordem\n const pages: ProjectsPage[] = [];\n const pageNumbers: number[] = [];\n for (const { query, page } of sortedByPage) {\n const data = queryClient.getQueryData<ProjectsPage>(query.queryKey);\n if (data) {\n pages.push(data);\n pageNumbers.push(page);\n }\n }\n if (!pages.length) continue;\n\n const pageSize = pages[0].data.length || 20;\n const newPages = insertIntoPaginatedCache({\n pages,\n pageNumbers,\n newItem: newProject,\n pageSize,\n compareFn,\n });\n\n // Grava de volta cada página que mudou (referência diferente)\n for (let i = 0; i < sortedByPage.length; i++) {\n if (newPages[i] !== pages[i]) {\n queryClient.setQueryData<ProjectsPage>(sortedByPage[i].query.queryKey, {\n ...pages[i],\n ...newPages[i],\n });\n }\n }\n }\n\n queryClient.invalidateQueries({ queryKey: projectsKey });\n },\n });\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.536",
3
+ "version": "1.1.540",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from "react"
4
4
  import { Slot } from "@radix-ui/react-slot"
5
+ import { IconLoader2 } from "@tabler/icons-react"
5
6
  import { cva, type VariantProps } from "class-variance-authority"
6
7
  import { cn } from "../../../infra/utils/clsx"
7
8
  import { useIsDefaultWhitelabel } from "../../../providers/whitelabel.provider"
@@ -39,23 +40,37 @@ function Button({
39
40
  variant,
40
41
  size,
41
42
  asChild = false,
43
+ loading = false,
44
+ children,
45
+ disabled,
42
46
  ...props
43
47
  }: React.ComponentProps<"button"> &
44
48
  VariantProps<typeof buttonVariants> & {
45
49
  asChild?: boolean
50
+ loading?: boolean
46
51
  }) {
47
52
  const isDefaultWl = useIsDefaultWhitelabel();
48
53
  const Comp = asChild ? Slot : "button"
49
54
 
55
+ const content = asChild ? children : (
56
+ <>
57
+ {loading && <IconLoader2 className="size-4 animate-spin" />}
58
+ {children}
59
+ </>
60
+ )
61
+
50
62
  return (
51
63
  <Comp
52
64
  data-slot="button"
65
+ disabled={disabled || loading}
53
66
  className={cn(
54
67
  buttonVariants({ variant, size, className }),
55
68
  variant === "brand" && (isDefaultWl ? "bg-cyan-300 hover:bg-cyan-200" : "bg-zinc-300 hover:bg-zinc-200")
56
69
  )}
57
70
  {...props}
58
- />
71
+ >
72
+ {content}
73
+ </Comp>
59
74
  )
60
75
  }
61
76
 
@@ -12,6 +12,119 @@ export function insertSorted<T>(
12
12
  return [...data.slice(0, index), newItem, ...data.slice(index)];
13
13
  }
14
14
 
15
+ export interface PaginatedPageCache<T> {
16
+ data: T[];
17
+ total: number;
18
+ }
19
+
20
+ export interface InsertIntoPaginatedCacheParams<T> {
21
+ /** Páginas atuais do cache, ordenadas por número de página (1..N). */
22
+ pages: PaginatedPageCache<T>[];
23
+ /** Números das páginas correspondentes (mesmo tamanho de `pages`). */
24
+ pageNumbers: number[];
25
+ /** Novo item a inserir. */
26
+ newItem: T;
27
+ /** Tamanho fixo de cada página. */
28
+ pageSize: number;
29
+ /**
30
+ * Função de comparação que mantém a ordem.
31
+ * Se não fornecida, insere no topo da página 1 (sem reordenar).
32
+ */
33
+ compareFn?: (a: T, b: T) => number;
34
+ }
35
+
36
+ /**
37
+ * Insere um item em um conjunto de páginas paginadas mantendo ordenação.
38
+ *
39
+ * Quando `compareFn` é fornecida, descobre em qual página o item deveria
40
+ * cair (comparando com o último item de cada página). Insere na posição
41
+ * correta e empurra o último item da página para a próxima, propagando
42
+ * o overflow até a última página disponível.
43
+ *
44
+ * Páginas não-afetadas retornam a mesma referência.
45
+ */
46
+ export function insertIntoPaginatedCache<T>({
47
+ pages,
48
+ pageNumbers,
49
+ newItem,
50
+ pageSize,
51
+ compareFn,
52
+ }: InsertIntoPaginatedCacheParams<T>): PaginatedPageCache<T>[] {
53
+ if (!pages.length) return pages;
54
+
55
+ // Sem sort: insere no topo da página 1 e propaga overflow
56
+ if (!compareFn) {
57
+ // Só atualiza se a página 1 estiver no cache
58
+ const firstIdx = pageNumbers.indexOf(1);
59
+ if (firstIdx === -1) return pages;
60
+
61
+ const result: PaginatedPageCache<T>[] = pages.slice();
62
+ let carry: T | null = newItem;
63
+ for (let i = firstIdx; i < result.length && carry !== null; i++) {
64
+ const current = result[i];
65
+ const nextData: T[] = [carry, ...current.data];
66
+ const overflow: T | null =
67
+ nextData.length > pageSize ? nextData[nextData.length - 1] : null;
68
+ result[i] = {
69
+ ...current,
70
+ data: overflow !== null ? nextData.slice(0, pageSize) : nextData,
71
+ total: current.total + 1,
72
+ };
73
+ carry = overflow;
74
+ }
75
+ return result;
76
+ }
77
+
78
+ // Descobre em qual página o item cai
79
+ let targetIdx = -1;
80
+ for (let i = 0; i < pages.length; i++) {
81
+ const page = pages[i];
82
+ if (!page.data.length) continue;
83
+ const last = page.data[page.data.length - 1];
84
+ // Se newItem vem antes (ou igual) ao último item desta página, cai aqui
85
+ if (compareFn(newItem, last) <= 0) {
86
+ targetIdx = i;
87
+ break;
88
+ }
89
+ }
90
+
91
+ // Não coube em nenhuma página com itens — tenta a última página se for incompleta
92
+ if (targetIdx === -1) {
93
+ const lastIdx = pages.length - 1;
94
+ const lastPage = pages[lastIdx];
95
+ if (lastPage.data.length < pageSize) {
96
+ targetIdx = lastIdx;
97
+ } else {
98
+ // Não cai em nenhuma página carregada — apenas incrementa total
99
+ return pages.map((p) => ({ ...p, total: p.total + 1 }));
100
+ }
101
+ }
102
+
103
+ // Insere no target e propaga overflow para as páginas seguintes
104
+ const result: PaginatedPageCache<T>[] = pages.slice();
105
+ let carry: T | null = newItem;
106
+
107
+ for (let i = targetIdx; i < result.length && carry !== null; i++) {
108
+ const current = result[i];
109
+ const withInserted: T[] = insertSorted(current.data, carry, compareFn);
110
+ const overflow: T | null =
111
+ withInserted.length > pageSize ? withInserted[withInserted.length - 1] : null;
112
+ result[i] = {
113
+ ...current,
114
+ data: overflow !== null ? withInserted.slice(0, pageSize) : withInserted,
115
+ total: current.total + 1,
116
+ };
117
+ carry = overflow;
118
+ }
119
+
120
+ // Incrementa o total nas páginas anteriores ao target (não mudaram mas o total global cresceu)
121
+ for (let i = 0; i < targetIdx; i++) {
122
+ result[i] = { ...result[i], total: result[i].total + 1 };
123
+ }
124
+
125
+ return result;
126
+ }
127
+
15
128
  /**
16
129
  * Cria uma função de comparação a partir de uma string de sort da API.
17
130
  * Suporta múltiplos campos separados por vírgula e prefixo `-` para desc.
@@ -5,18 +5,20 @@ import { createProjectAction } from '../actions/create-project.action';
5
5
  import { withAction } from '../../../utils/withAction';
6
6
  import { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';
7
7
  import { PROJECTS_BASE_KEY } from './list-projects.hook';
8
- import { insertSorted, buildCompareFn } from '../../../infra/utils/array';
8
+ import { buildCompareFn, insertIntoPaginatedCache } from '../../../infra/utils/array';
9
9
  import type { CreateProjectRequest, Project, ProjectsPage } from '../types';
10
10
 
11
- function extractSort(query: Query): string | undefined {
11
+ type QueryParams = { sort?: string; page?: number; [key: string]: unknown };
12
+
13
+ function extractQueryParams(query: Query): QueryParams {
12
14
  const key = query.queryKey;
13
15
  for (let i = key.length - 1; i >= 0; i--) {
14
16
  const segment = key[i];
15
- if (segment && typeof segment === 'object' && 'sort' in segment) {
16
- return (segment as Record<string, unknown>).sort as string | undefined;
17
+ if (segment !== null && typeof segment === 'object') {
18
+ return segment as QueryParams;
17
19
  }
18
20
  }
19
- return undefined;
21
+ return {};
20
22
  }
21
23
 
22
24
  export function useCreateProject() {
@@ -31,54 +33,84 @@ export function useCreateProject() {
31
33
  const cache = queryClient.getQueryCache();
32
34
  const queries = cache.findAll({ queryKey: projectsKey });
33
35
 
34
- for (const query of queries) {
35
- const sort = extractSort(query);
36
- const isInfinite = query.queryKey.includes('infinite');
37
-
38
- if (isInfinite) {
39
- queryClient.setQueryData<InfiniteData<ProjectsPage>>(
40
- query.queryKey,
41
- (old) => {
42
- if (!old?.pages.length) return old;
43
-
44
- const pageSize = old.pages[0].data.length || 20;
45
- const allItems = old.pages.flatMap((page) => page.data);
46
-
47
- const withNew = sort
48
- ? insertSorted(allItems, newProject, buildCompareFn<Project>(sort))
49
- : [newProject, ...allItems];
50
-
51
- const newTotal = (old.pages[0].total ?? 0) + 1;
52
-
53
- return {
54
- ...old,
55
- pages: old.pages.map((page, i) => ({
56
- ...page,
57
- data: withNew.slice(i * pageSize, (i + 1) * pageSize),
58
- total: newTotal,
59
- })),
60
- };
61
- },
62
- );
36
+ // 1) Caches infinite: todas as páginas vivem no mesmo queryData
37
+ const infiniteQueries = queries.filter((q) => q.queryKey.includes('infinite'));
38
+
39
+ for (const query of infiniteQueries) {
40
+ const { sort } = extractQueryParams(query);
41
+ const compareFn = sort ? buildCompareFn<Project>(sort) : undefined;
42
+
43
+ queryClient.setQueryData<InfiniteData<ProjectsPage>>(query.queryKey, (old) => {
44
+ if (!old?.pages.length) return old;
45
+
46
+ const pageSize = old.pages[0].data.length || 20;
47
+ const newPages = insertIntoPaginatedCache({
48
+ pages: old.pages,
49
+ pageNumbers: old.pages.map((_, i) => i + 1),
50
+ newItem: newProject,
51
+ pageSize,
52
+ compareFn,
53
+ });
54
+
55
+ return { ...old, pages: newPages.map((p, i) => ({ ...old.pages[i], ...p })) };
56
+ });
57
+ }
58
+
59
+ // 2) Caches paginados: agrupa queries do mesmo "sort" e trata como conjunto
60
+ const paginatedQueries = queries.filter((q) => !q.queryKey.includes('infinite'));
61
+
62
+ // Agrupa por assinatura dos params (exceto page)
63
+ const groups = new Map<string, { queries: Query[]; sort?: string }>();
64
+ for (const query of paginatedQueries) {
65
+ const params = extractQueryParams(query);
66
+ const { page: _page, ...rest } = params;
67
+ void _page;
68
+ const signature = JSON.stringify(rest);
69
+ const existing = groups.get(signature);
70
+ if (existing) {
71
+ existing.queries.push(query);
63
72
  } else {
64
- queryClient.setQueryData<ProjectsPage>(
65
- query.queryKey,
66
- (old) => {
67
- if (!old) return old;
68
-
69
- const pageSize = old.data.length || 20;
70
-
71
- const withNew = sort
72
- ? insertSorted(old.data, newProject, buildCompareFn<Project>(sort))
73
- : [newProject, ...old.data];
74
-
75
- return {
76
- ...old,
77
- data: withNew.slice(0, pageSize),
78
- total: old.total + 1,
79
- };
80
- },
81
- );
73
+ groups.set(signature, { queries: [query], sort: params.sort });
74
+ }
75
+ }
76
+
77
+ for (const { queries: groupQueries, sort } of groups.values()) {
78
+ const compareFn = sort ? buildCompareFn<Project>(sort) : undefined;
79
+
80
+ // Ordena por número de página
81
+ const sortedByPage = groupQueries
82
+ .map((q) => ({ query: q, page: extractQueryParams(q).page ?? 1 }))
83
+ .sort((a, b) => a.page - b.page);
84
+
85
+ // Monta as páginas na ordem
86
+ const pages: ProjectsPage[] = [];
87
+ const pageNumbers: number[] = [];
88
+ for (const { query, page } of sortedByPage) {
89
+ const data = queryClient.getQueryData<ProjectsPage>(query.queryKey);
90
+ if (data) {
91
+ pages.push(data);
92
+ pageNumbers.push(page);
93
+ }
94
+ }
95
+ if (!pages.length) continue;
96
+
97
+ const pageSize = pages[0].data.length || 20;
98
+ const newPages = insertIntoPaginatedCache({
99
+ pages,
100
+ pageNumbers,
101
+ newItem: newProject,
102
+ pageSize,
103
+ compareFn,
104
+ });
105
+
106
+ // Grava de volta cada página que mudou (referência diferente)
107
+ for (let i = 0; i < sortedByPage.length; i++) {
108
+ if (newPages[i] !== pages[i]) {
109
+ queryClient.setQueryData<ProjectsPage>(sortedByPage[i].query.queryKey, {
110
+ ...pages[i],
111
+ ...newPages[i],
112
+ });
113
+ }
82
114
  }
83
115
  }
84
116