@olinfo/quizms-firebase 4.0.0-canary.20251130

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 (94) hide show
  1. package/bin/bin.js +4 -0
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +36 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/entry.d.ts +4 -0
  6. package/dist/entry.js +19 -0
  7. package/dist/entry.js.map +1 -0
  8. package/dist/functions.d.ts +14 -0
  9. package/dist/functions.js +104 -0
  10. package/dist/functions.js.map +1 -0
  11. package/dist/package.json.js +5 -0
  12. package/dist/package.json.js.map +1 -0
  13. package/dist/src/cli/definalize.js +23 -0
  14. package/dist/src/cli/definalize.js.map +1 -0
  15. package/dist/src/cli/export.js +54 -0
  16. package/dist/src/cli/export.js.map +1 -0
  17. package/dist/src/cli/files/firestore-indexes.json.js +10 -0
  18. package/dist/src/cli/files/firestore-indexes.json.js.map +1 -0
  19. package/dist/src/cli/files/firestore.rules.js +191 -0
  20. package/dist/src/cli/files/firestore.rules.js.map +1 -0
  21. package/dist/src/cli/files/storage.rules.js +35 -0
  22. package/dist/src/cli/files/storage.rules.js.map +1 -0
  23. package/dist/src/cli/import.js +140 -0
  24. package/dist/src/cli/import.js.map +1 -0
  25. package/dist/src/cli/init.js +140 -0
  26. package/dist/src/cli/init.js.map +1 -0
  27. package/dist/src/cli/update-scores.js +37 -0
  28. package/dist/src/cli/update-scores.js.map +1 -0
  29. package/dist/src/cli/utils/collection.js +55 -0
  30. package/dist/src/cli/utils/collection.js.map +1 -0
  31. package/dist/src/cli/utils/converters-admin.js +41 -0
  32. package/dist/src/cli/utils/converters-admin.js.map +1 -0
  33. package/dist/src/cli/utils/initialize.js +31 -0
  34. package/dist/src/cli/utils/initialize.js.map +1 -0
  35. package/dist/src/cli/utils/rest-api.js +23 -0
  36. package/dist/src/cli/utils/rest-api.js.map +1 -0
  37. package/dist/src/cli/utils/storage.js +41 -0
  38. package/dist/src/cli/utils/storage.js.map +1 -0
  39. package/dist/src/cli/utils/users.js +30 -0
  40. package/dist/src/cli/utils/users.js.map +1 -0
  41. package/dist/src/models/user.js +12 -0
  42. package/dist/src/models/user.js.map +1 -0
  43. package/dist/src/vite/entry.js +33 -0
  44. package/dist/src/vite/entry.js.map +1 -0
  45. package/dist/src/vite/preset.js +30 -0
  46. package/dist/src/vite/preset.js.map +1 -0
  47. package/dist/src/web/admin/announcements.js +85 -0
  48. package/dist/src/web/admin/announcements.js.map +1 -0
  49. package/dist/src/web/admin/contest-settings.js +16 -0
  50. package/dist/src/web/admin/contest-settings.js.map +1 -0
  51. package/dist/src/web/admin/context.js +11 -0
  52. package/dist/src/web/admin/context.js.map +1 -0
  53. package/dist/src/web/admin/dashboard.js +85 -0
  54. package/dist/src/web/admin/dashboard.js.map +1 -0
  55. package/dist/src/web/admin/export.js +59 -0
  56. package/dist/src/web/admin/export.js.map +1 -0
  57. package/dist/src/web/admin/layout.js +38 -0
  58. package/dist/src/web/admin/layout.js.map +1 -0
  59. package/dist/src/web/admin/provider.js +30 -0
  60. package/dist/src/web/admin/provider.js.map +1 -0
  61. package/dist/src/web/admin/school-table.js +79 -0
  62. package/dist/src/web/admin/school-table.js.map +1 -0
  63. package/dist/src/web/common/api.js +43 -0
  64. package/dist/src/web/common/api.js.map +1 -0
  65. package/dist/src/web/common/base-login.js +26 -0
  66. package/dist/src/web/common/base-login.js.map +1 -0
  67. package/dist/src/web/common/converters.js +57 -0
  68. package/dist/src/web/common/converters.js.map +1 -0
  69. package/dist/src/web/common/query.js +16 -0
  70. package/dist/src/web/common/query.js.map +1 -0
  71. package/dist/src/web/common/token-login.js +40 -0
  72. package/dist/src/web/common/token-login.js.map +1 -0
  73. package/dist/src/web/entry/admin.js +27 -0
  74. package/dist/src/web/entry/admin.js.map +1 -0
  75. package/dist/src/web/entry/student-statement.js +28 -0
  76. package/dist/src/web/entry/student-statement.js.map +1 -0
  77. package/dist/src/web/entry/student.js +126 -0
  78. package/dist/src/web/entry/student.js.map +1 -0
  79. package/dist/src/web/entry/teacher.js +88 -0
  80. package/dist/src/web/entry/teacher.js.map +1 -0
  81. package/dist/src/web/hooks/auth.js +26 -0
  82. package/dist/src/web/hooks/auth.js.map +1 -0
  83. package/dist/src/web/hooks/collection.js +68 -0
  84. package/dist/src/web/hooks/collection.js.map +1 -0
  85. package/dist/src/web/hooks/count.js +22 -0
  86. package/dist/src/web/hooks/count.js.map +1 -0
  87. package/dist/src/web/hooks/document.js +61 -0
  88. package/dist/src/web/hooks/document.js.map +1 -0
  89. package/dist/src/web/hooks/subscription.js +42 -0
  90. package/dist/src/web/hooks/subscription.js.map +1 -0
  91. package/dist/vite.d.ts +4 -0
  92. package/dist/vite.js +7 -0
  93. package/dist/vite.js.map +1 -0
  94. package/package.json +83 -0
@@ -0,0 +1,30 @@
1
+ import { confirm as m } from "@inquirer/prompts";
2
+ import { fatal as s } from "@olinfo/quizms/utils-node";
3
+ import { info as n } from "firebase-functions/logger";
4
+ import { upperFirst as f } from "lodash-es";
5
+ import l from "picocolors";
6
+ import { importCollection as p } from "./collection.js";
7
+ import { userConverter as u } from "./converters-admin.js";
8
+ async function A(e, o, i, t) {
9
+ if (t.delete) {
10
+ await m({
11
+ message: `You are about to delete all ${l.bold(
12
+ o
13
+ )} users. Any previous data will be lost, this operation cannot be undone. Are you really sure?`,
14
+ default: !1
15
+ }) || s("Command aborted.");
16
+ const a = e.collection("users");
17
+ await e.recursiveDelete(a), n(`${f(o)} users deleted!`);
18
+ }
19
+ await p(
20
+ e,
21
+ "users",
22
+ i.map((r) => ({ ...r, id: r.username, role: o })),
23
+ u,
24
+ { ...t, delete: void 0 }
25
+ );
26
+ }
27
+ export {
28
+ A as importUsers
29
+ };
30
+ //# sourceMappingURL=users.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.js","sources":["../../../../src/cli/utils/users.ts"],"sourcesContent":["import { confirm } from \"@inquirer/prompts\";\nimport { fatal } from \"@olinfo/quizms/utils-node\";\nimport type { Firestore } from \"firebase-admin/firestore\";\nimport { info } from \"firebase-functions/logger\";\nimport { upperFirst } from \"lodash-es\";\nimport pc from \"picocolors\";\n\nimport type { User } from \"~/models/user\";\n\nimport { importCollection } from \"./collection\";\nimport { userConverter } from \"./converters-admin\";\n\ntype ImportOptions = {\n delete?: true;\n skipExisting?: true;\n force?: true;\n};\n\nexport async function importUsers(\n db: Firestore,\n role: string,\n users: Omit<User, \"id\" | \"role\">[],\n options: ImportOptions,\n) {\n if (options.delete) {\n const confirmed = await confirm({\n message: `You are about to delete all ${pc.bold(\n role,\n )} users. Any previous data will be lost, this operation cannot be undone. Are you really sure?`,\n default: false,\n });\n if (!confirmed) {\n fatal(\"Command aborted.\");\n }\n\n const ref = db.collection(\"users\");\n await db.recursiveDelete(ref);\n info(`${upperFirst(role)} users deleted!`);\n }\n\n await importCollection(\n db,\n \"users\",\n users.map((u) => ({ ...u, id: u.username, role })),\n userConverter,\n { ...options, delete: undefined },\n );\n}\n"],"names":["importUsers","db","role","users","options","confirm","pc","fatal","ref","info","upperFirst","importCollection","u","userConverter"],"mappings":";;;;;;;AAkBA,eAAsBA,EACpBC,GACAC,GACAC,GACAC,GACA;AACA,MAAIA,EAAQ,QAAQ;AAOlB,IANkB,MAAMC,EAAQ;AAAA,MAC9B,SAAS,+BAA+BC,EAAG;AAAA,QACzCJ;AAAA,MAAA,CACD;AAAA,MACD,SAAS;AAAA,IAAA,CACV,KAECK,EAAM,kBAAkB;AAG1B,UAAMC,IAAMP,EAAG,WAAW,OAAO;AACjC,UAAMA,EAAG,gBAAgBO,CAAG,GAC5BC,EAAK,GAAGC,EAAWR,CAAI,CAAC,iBAAiB;AAAA,EAC3C;AAEA,QAAMS;AAAA,IACJV;AAAA,IACA;AAAA,IACAE,EAAM,IAAI,CAACS,OAAO,EAAE,GAAGA,GAAG,IAAIA,EAAE,UAAU,MAAAV,EAAA,EAAO;AAAA,IACjDW;AAAA,IACA,EAAE,GAAGT,GAAS,QAAQ,OAAA;AAAA,EAAU;AAEpC;"}
@@ -0,0 +1,12 @@
1
+ import r from "zod";
2
+ const n = r.object({
3
+ id: r.string(),
4
+ name: r.string(),
5
+ username: r.string(),
6
+ password: r.string(),
7
+ role: r.enum(["admin", "teacher"])
8
+ });
9
+ export {
10
+ n as userSchema
11
+ };
12
+ //# sourceMappingURL=user.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.js","sources":["../../../src/models/user.ts"],"sourcesContent":["import z from \"zod\";\n\nexport const userSchema = z.object({\n id: z.string(),\n name: z.string(),\n username: z.string(),\n password: z.string(),\n role: z.enum([\"admin\", \"teacher\"]),\n});\n\nexport type User = z.infer<typeof userSchema>;\n"],"names":["userSchema","z"],"mappings":";AAEO,MAAMA,IAAaC,EAAE,OAAO;AAAA,EACjC,IAAIA,EAAE,OAAA;AAAA,EACN,MAAMA,EAAE,OAAA;AAAA,EACR,UAAUA,EAAE,OAAA;AAAA,EACZ,UAAUA,EAAE,OAAA;AAAA,EACZ,MAAMA,EAAE,KAAK,CAAC,SAAS,SAAS,CAAC;AACnC,CAAC;"}
@@ -0,0 +1,33 @@
1
+ function i() {
2
+ return {
3
+ name: "quizms:firebase-entry",
4
+ api: {
5
+ quizmsDevRoutes: [
6
+ {
7
+ pathname: /^\/firebase\/admin(\/\w+(\/schools)?)?$/,
8
+ module: "@olinfo/quizms-firebase/entry"
9
+ },
10
+ {
11
+ pathname: /^\/firebase\/teacher(\/\w+(\/students(\/\w+)?)?)?$/,
12
+ module: "@olinfo/quizms-firebase/entry"
13
+ },
14
+ {
15
+ pathname: "/firebase",
16
+ module: "@olinfo/quizms-firebase/entry"
17
+ }
18
+ ]
19
+ },
20
+ resolveId(e) {
21
+ if (e === "virtual:quizms-firebase-config")
22
+ return `\0${e}`;
23
+ },
24
+ load(e) {
25
+ if (e === "\0virtual:quizms-firebase-config")
26
+ return 'export { default } from "~/firebase-config";';
27
+ }
28
+ };
29
+ }
30
+ export {
31
+ i as default
32
+ };
33
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sources":["../../../src/vite/entry.ts"],"sourcesContent":["import type { PluginOption } from \"vite\";\n\nexport default function firebaseEntry(): PluginOption {\n return {\n name: \"quizms:firebase-entry\",\n api: {\n quizmsDevRoutes: [\n {\n pathname: /^\\/firebase\\/admin(\\/\\w+(\\/schools)?)?$/,\n module: \"@olinfo/quizms-firebase/entry\",\n },\n {\n pathname: /^\\/firebase\\/teacher(\\/\\w+(\\/students(\\/\\w+)?)?)?$/,\n module: \"@olinfo/quizms-firebase/entry\",\n },\n {\n pathname: \"/firebase\",\n module: \"@olinfo/quizms-firebase/entry\",\n },\n ],\n },\n resolveId(id) {\n if (id === \"virtual:quizms-firebase-config\") {\n return `\\0${id}`;\n }\n },\n load(id) {\n if (id === \"\\0virtual:quizms-firebase-config\") {\n return `export { default } from \"~/firebase-config\";`;\n }\n },\n } satisfies PluginOption;\n}\n"],"names":["firebaseEntry","id"],"mappings":"AAEA,SAAwBA,IAA8B;AACpD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,MACH,iBAAiB;AAAA,QACf;AAAA,UACE,UAAU;AAAA,UACV,QAAQ;AAAA,QAAA;AAAA,QAEV;AAAA,UACE,UAAU;AAAA,UACV,QAAQ;AAAA,QAAA;AAAA,QAEV;AAAA,UACE,UAAU;AAAA,UACV,QAAQ;AAAA,QAAA;AAAA,MACV;AAAA,IACF;AAAA,IAEF,UAAUC,GAAI;AACZ,UAAIA,MAAO;AACT,eAAO,KAAKA,CAAE;AAAA,IAElB;AAAA,IACA,KAAKA,GAAI;AACP,UAAIA,MAAO;AACT,eAAO;AAAA,IAEX;AAAA,EAAA;AAEJ;"}
@@ -0,0 +1,30 @@
1
+ function e() {
2
+ return {
3
+ name: "quizms:firebase-preset",
4
+ apply: "build",
5
+ config() {
6
+ return {
7
+ environments: {
8
+ firebase: {
9
+ ssr: !1,
10
+ resolve: {
11
+ conditions: ["module", "browser", "import", "production"],
12
+ noExternal: !0
13
+ },
14
+ build: {
15
+ rollupOptions: {
16
+ input: {
17
+ index: "virtual:quizms-entry?@olinfo/quizms-firebase/entry"
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
23
+ };
24
+ }
25
+ };
26
+ }
27
+ export {
28
+ e as default
29
+ };
30
+ //# sourceMappingURL=preset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preset.js","sources":["../../../src/vite/preset.ts"],"sourcesContent":["import type { PluginOption } from \"vite\";\n\nexport default function firebasePreset(): PluginOption {\n return {\n name: \"quizms:firebase-preset\",\n apply: \"build\",\n config() {\n return {\n environments: {\n firebase: {\n ssr: false,\n resolve: {\n conditions: [\"module\", \"browser\", \"import\", \"production\"],\n noExternal: true,\n },\n build: {\n rollupOptions: {\n input: {\n index: \"virtual:quizms-entry?@olinfo/quizms-firebase/entry\",\n },\n },\n },\n },\n },\n };\n },\n } satisfies PluginOption;\n}\n"],"names":["firebasePreset"],"mappings":"AAEA,SAAwBA,IAA+B;AACrD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AACP,aAAO;AAAA,QACL,cAAc;AAAA,UACZ,UAAU;AAAA,YACR,KAAK;AAAA,YACL,SAAS;AAAA,cACP,YAAY,CAAC,UAAU,WAAW,UAAU,YAAY;AAAA,cACxD,YAAY;AAAA,YAAA;AAAA,YAEd,OAAO;AAAA,cACL,eAAe;AAAA,gBACb,OAAO;AAAA,kBACL,OAAO;AAAA,gBAAA;AAAA,cACT;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IAEJ;AAAA,EAAA;AAEJ;"}
@@ -0,0 +1,85 @@
1
+ import { jsxs as o, Fragment as u, jsx as e } from "react/jsx-runtime";
2
+ import { useRef as f, Fragment as p, forwardRef as g, useReducer as h } from "react";
3
+ import { randomId as A } from "@olinfo/quizms/utils";
4
+ import { DateTime as x, CardActions as w, Button as b, Modal as y, Form as N, SelectField as z, TextField as F, TextAreaField as I, SubmitButton as v } from "@olinfo/react-components";
5
+ import { Info as C, TriangleAlert as M, CircleX as T } from "lucide-react";
6
+ import S from "react-markdown";
7
+ import { announcementConverter as B } from "../common/converters.js";
8
+ import "firebase/auth";
9
+ import "lodash-es";
10
+ import "react-error-boundary";
11
+ import "swr";
12
+ import "../common/base-login.js";
13
+ import { useCollection as R } from "../hooks/collection.js";
14
+ import "firebase/firestore";
15
+ import { useAdmin as s } from "./context.js";
16
+ function W() {
17
+ const { contest: a } = s(), t = f(null), [r, n] = R("announcements", B, {
18
+ arrayConstraints: {
19
+ contestIds: a.id
20
+ },
21
+ orderBy: ["createdAt", "desc"],
22
+ subscribe: !0
23
+ });
24
+ return /* @__PURE__ */ o(u, { children: [
25
+ /* @__PURE__ */ e("div", { className: "prose mt-4 max-w-none", children: r.map((i) => /* @__PURE__ */ o(p, { children: [
26
+ /* @__PURE__ */ o("h4", { className: "flex items-center gap-3", children: [
27
+ i.level === "info" && /* @__PURE__ */ e(C, { size: 20, className: "text-info" }),
28
+ i.level === "warning" && /* @__PURE__ */ e(M, { size: 20, className: "text-warning" }),
29
+ i.level === "error" && /* @__PURE__ */ e(T, { size: 20, className: "text-error" }),
30
+ i.title
31
+ ] }),
32
+ /* @__PURE__ */ e("small", { children: /* @__PURE__ */ e(x, { date: i.createdAt }) }),
33
+ /* @__PURE__ */ e(S, { children: i.body }),
34
+ /* @__PURE__ */ e("hr", { className: "my-5 last:hidden" })
35
+ ] }, i.id)) }),
36
+ /* @__PURE__ */ e(w, { children: /* @__PURE__ */ e(b, { className: "btn-primary", onClick: () => t.current?.showModal(), children: "Aggiungi comunicazione" }) }),
37
+ /* @__PURE__ */ e(j, { ref: t, addAnnouncement: n })
38
+ ] });
39
+ }
40
+ const j = g(function({ addAnnouncement: t }, r) {
41
+ const { contest: n } = s(), [i, c] = h((l) => l + 1, 0);
42
+ return /* @__PURE__ */ e(y, { ref: r, title: "Aggiungi comunicazione", children: /* @__PURE__ */ o(N, { onSubmit: async ({
43
+ level: l,
44
+ title: m,
45
+ body: d
46
+ }) => {
47
+ try {
48
+ await t({
49
+ id: A(),
50
+ createdAt: /* @__PURE__ */ new Date(),
51
+ contestIds: [n.id],
52
+ level: l,
53
+ title: m,
54
+ body: d
55
+ }), c();
56
+ } finally {
57
+ r && "current" in r && r.current?.close();
58
+ }
59
+ }, className: "!max-w-full", children: [
60
+ /* @__PURE__ */ e(
61
+ z,
62
+ {
63
+ field: "level",
64
+ label: "Priorità",
65
+ options: { info: "Normale", warning: "Alta" },
66
+ placeholder: "Seleziona la priorità"
67
+ }
68
+ ),
69
+ /* @__PURE__ */ e(F, { field: "title", label: "Titolo", placeholder: "Inserisci il titolo" }),
70
+ /* @__PURE__ */ e(
71
+ I,
72
+ {
73
+ field: "body",
74
+ label: "Messaggio",
75
+ placeholder: "Inserisci il messaggio",
76
+ rows: 5
77
+ }
78
+ ),
79
+ /* @__PURE__ */ e(v, { children: "Aggiungi" })
80
+ ] }, i) });
81
+ });
82
+ export {
83
+ W as default
84
+ };
85
+ //# sourceMappingURL=announcements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"announcements.js","sources":["../../../../src/web/admin/announcements.tsx"],"sourcesContent":["import { Fragment, forwardRef, type Ref, useReducer, useRef } from \"react\";\n\nimport type { Announcement } from \"@olinfo/quizms/models\";\nimport { randomId } from \"@olinfo/quizms/utils\";\nimport {\n Button,\n CardActions,\n DateTime,\n Form,\n Modal,\n SelectField,\n SubmitButton,\n TextAreaField,\n TextField,\n} from \"@olinfo/react-components\";\nimport { CircleX, Info, TriangleAlert } from \"lucide-react\";\nimport Markdown from \"react-markdown\";\n\nimport { announcementConverter } from \"~/web/common/converters\";\nimport { useCollection } from \"~/web/hooks\";\n\nimport { useAdmin } from \"./context\";\n\nexport default function Announcements() {\n const { contest } = useAdmin();\n const ref = useRef<HTMLDialogElement>(null);\n\n const [announcements, addAnnouncement] = useCollection(\"announcements\", announcementConverter, {\n arrayConstraints: {\n contestIds: contest.id,\n },\n orderBy: [\"createdAt\", \"desc\"],\n subscribe: true,\n });\n\n return (\n <>\n <div className=\"prose mt-4 max-w-none\">\n {announcements.map((announcement) => (\n <Fragment key={announcement.id}>\n <h4 className=\"flex items-center gap-3\">\n {announcement.level === \"info\" && <Info size={20} className=\"text-info\" />}\n {announcement.level === \"warning\" && (\n <TriangleAlert size={20} className=\"text-warning\" />\n )}\n {announcement.level === \"error\" && <CircleX size={20} className=\"text-error\" />}\n {announcement.title}\n </h4>\n <small>\n <DateTime date={announcement.createdAt} />\n </small>\n <Markdown>{announcement.body}</Markdown>\n <hr className=\"my-5 last:hidden\" />\n </Fragment>\n ))}\n </div>\n <CardActions>\n <Button className=\"btn-primary\" onClick={() => ref.current?.showModal()}>\n Aggiungi comunicazione\n </Button>\n </CardActions>\n <AnnouncementModal ref={ref} addAnnouncement={addAnnouncement} />\n </>\n );\n}\n\nconst AnnouncementModal = forwardRef(function AnnouncementModal(\n { addAnnouncement }: { addAnnouncement: (announcement: Announcement) => Promise<void> },\n ref: Ref<HTMLDialogElement>,\n) {\n const { contest } = useAdmin();\n const [id, nextId] = useReducer((prev) => prev + 1, 0);\n\n const finalize = async ({\n level,\n title,\n body,\n }: Pick<Announcement, \"level\" | \"title\" | \"body\">) => {\n try {\n await addAnnouncement({\n id: randomId(),\n createdAt: new Date(),\n contestIds: [contest.id],\n level,\n title,\n body,\n });\n nextId();\n } finally {\n if (ref && \"current\" in ref) {\n ref.current?.close();\n }\n }\n };\n\n return (\n <Modal ref={ref} title=\"Aggiungi comunicazione\">\n <Form key={id} onSubmit={finalize} className=\"!max-w-full\">\n <SelectField\n field=\"level\"\n label=\"Priorità\"\n options={{ info: \"Normale\", warning: \"Alta\" }}\n placeholder=\"Seleziona la priorità\"\n />\n <TextField field=\"title\" label=\"Titolo\" placeholder=\"Inserisci il titolo\" />\n <TextAreaField\n field=\"body\"\n label=\"Messaggio\"\n placeholder=\"Inserisci il messaggio\"\n rows={5}\n />\n <SubmitButton>Aggiungi</SubmitButton>\n </Form>\n </Modal>\n );\n});\n"],"names":["Announcements","contest","useAdmin","ref","useRef","announcements","addAnnouncement","useCollection","announcementConverter","jsxs","Fragment","jsx","announcement","Info","TriangleAlert","CircleX","DateTime","Markdown","CardActions","Button","AnnouncementModal","forwardRef","id","nextId","useReducer","prev","Modal","Form","level","title","body","randomId","SelectField","TextField","TextAreaField","SubmitButton"],"mappings":";;;;;;;;;;;;;;;AAuBA,SAAwBA,IAAgB;AACtC,QAAM,EAAE,SAAAC,EAAA,IAAYC,EAAA,GACdC,IAAMC,EAA0B,IAAI,GAEpC,CAACC,GAAeC,CAAe,IAAIC,EAAc,iBAAiBC,GAAuB;AAAA,IAC7F,kBAAkB;AAAA,MAChB,YAAYP,EAAQ;AAAA,IAAA;AAAA,IAEtB,SAAS,CAAC,aAAa,MAAM;AAAA,IAC7B,WAAW;AAAA,EAAA,CACZ;AAED,SACE,gBAAAQ,EAAAC,GAAA,EACE,UAAA;AAAA,IAAA,gBAAAC,EAAC,OAAA,EAAI,WAAU,yBACZ,UAAAN,EAAc,IAAI,CAACO,MAClB,gBAAAH,EAACC,GAAA,EACC,UAAA;AAAA,MAAA,gBAAAD,EAAC,MAAA,EAAG,WAAU,2BACX,UAAA;AAAA,QAAAG,EAAa,UAAU,UAAU,gBAAAD,EAACE,KAAK,MAAM,IAAI,WAAU,aAAY;AAAA,QACvED,EAAa,UAAU,aACtB,gBAAAD,EAACG,KAAc,MAAM,IAAI,WAAU,gBAAe;AAAA,QAEnDF,EAAa,UAAU,WAAW,gBAAAD,EAACI,KAAQ,MAAM,IAAI,WAAU,cAAa;AAAA,QAC5EH,EAAa;AAAA,MAAA,GAChB;AAAA,wBACC,SAAA,EACC,UAAA,gBAAAD,EAACK,KAAS,MAAMJ,EAAa,WAAW,GAC1C;AAAA,MACA,gBAAAD,EAACM,GAAA,EAAU,UAAAL,EAAa,KAAA,CAAK;AAAA,MAC7B,gBAAAD,EAAC,MAAA,EAAG,WAAU,mBAAA,CAAmB;AAAA,IAAA,EAAA,GAbpBC,EAAa,EAc5B,CACD,GACH;AAAA,IACA,gBAAAD,EAACO,GAAA,EACC,UAAA,gBAAAP,EAACQ,GAAA,EAAO,WAAU,eAAc,SAAS,MAAMhB,EAAI,SAAS,UAAA,GAAa,oCAEzE,GACF;AAAA,IACA,gBAAAQ,EAACS,GAAA,EAAkB,KAAAjB,GAAU,iBAAAG,EAAA,CAAkC;AAAA,EAAA,GACjE;AAEJ;AAEA,MAAMc,IAAoBC,EAAW,SACnC,EAAE,iBAAAf,EAAA,GACFH,GACA;AACA,QAAM,EAAE,SAAAF,EAAA,IAAYC,EAAA,GACd,CAACoB,GAAIC,CAAM,IAAIC,EAAW,CAACC,MAASA,IAAO,GAAG,CAAC;AAwBrD,SACE,gBAAAd,EAACe,GAAA,EAAM,KAAAvB,GAAU,OAAM,0BACrB,4BAACwB,GAAA,EAAc,UAxBF,OAAO;AAAA,IACtB,OAAAC;AAAA,IACA,OAAAC;AAAA,IACA,MAAAC;AAAA,EAAA,MACoD;AACpD,QAAI;AACF,YAAMxB,EAAgB;AAAA,QACpB,IAAIyB,EAAA;AAAA,QACJ,+BAAe,KAAA;AAAA,QACf,YAAY,CAAC9B,EAAQ,EAAE;AAAA,QACvB,OAAA2B;AAAA,QACA,OAAAC;AAAA,QACA,MAAAC;AAAA,MAAA,CACD,GACDP,EAAA;AAAA,IACF,UAAA;AACE,MAAIpB,KAAO,aAAaA,KACtBA,EAAI,SAAS,MAAA;AAAA,IAEjB;AAAA,EACF,GAIuC,WAAU,eAC3C,UAAA;AAAA,IAAA,gBAAAQ;AAAA,MAACqB;AAAA,MAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAM;AAAA,QACN,SAAS,EAAE,MAAM,WAAW,SAAS,OAAA;AAAA,QACrC,aAAY;AAAA,MAAA;AAAA,IAAA;AAAA,sBAEbC,GAAA,EAAU,OAAM,SAAQ,OAAM,UAAS,aAAY,uBAAsB;AAAA,IAC1E,gBAAAtB;AAAA,MAACuB;AAAA,MAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAM;AAAA,QACN,aAAY;AAAA,QACZ,MAAM;AAAA,MAAA;AAAA,IAAA;AAAA,IAER,gBAAAvB,EAACwB,KAAa,UAAA,WAAA,CAAQ;AAAA,EAAA,EAAA,GAdbb,CAeX,GACF;AAEJ,CAAC;"}
@@ -0,0 +1,16 @@
1
+ import { jsxs as a, jsx as e } from "react/jsx-runtime";
2
+ import { Form as i, TextAreaField as n, SubmitButton as r } from "@olinfo/react-components";
3
+ import { useAdmin as l } from "./context.js";
4
+ function f() {
5
+ const { contest: t, setContest: s } = l();
6
+ return /* @__PURE__ */ a(i, { defaultValue: t, onSubmit: async (o) => {
7
+ await s({ ...t, ...o });
8
+ }, className: "!max-w-full", children: [
9
+ /* @__PURE__ */ e(n, { field: "instructions", label: "Istruzioni gara", rows: 8 }),
10
+ /* @__PURE__ */ e(r, { children: "Salva" })
11
+ ] }, t.id);
12
+ }
13
+ export {
14
+ f as default
15
+ };
16
+ //# sourceMappingURL=contest-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contest-settings.js","sources":["../../../../src/web/admin/contest-settings.tsx"],"sourcesContent":["import type { Contest } from \"@olinfo/quizms/models\";\nimport { Form, SubmitButton, TextAreaField } from \"@olinfo/react-components\";\n\nimport { useAdmin } from \"./context\";\n\nexport default function ContestSettings() {\n const { contest, setContest } = useAdmin();\n\n const save = async (newContest: Pick<Contest, \"instructions\">) => {\n await setContest({ ...contest, ...newContest });\n };\n\n return (\n <Form key={contest.id} defaultValue={contest} onSubmit={save} className=\"!max-w-full\">\n <TextAreaField field=\"instructions\" label=\"Istruzioni gara\" rows={8} />\n <SubmitButton>Salva</SubmitButton>\n </Form>\n );\n}\n"],"names":["ContestSettings","contest","setContest","useAdmin","Form","newContest","jsx","TextAreaField","SubmitButton"],"mappings":";;;AAKA,SAAwBA,IAAkB;AACxC,QAAM,EAAE,SAAAC,GAAS,YAAAC,EAAA,IAAeC,EAAA;AAMhC,2BACGC,GAAA,EAAsB,cAAcH,GAAS,UALnC,OAAOI,MAA8C;AAChE,UAAMH,EAAW,EAAE,GAAGD,GAAS,GAAGI,GAAY;AAAA,EAChD,GAGgE,WAAU,eACtE,UAAA;AAAA,IAAA,gBAAAC,EAACC,KAAc,OAAM,gBAAe,OAAM,mBAAkB,MAAM,GAAG;AAAA,IACrE,gBAAAD,EAACE,KAAa,UAAA,QAAA,CAAK;AAAA,EAAA,EAAA,GAFVP,EAAQ,EAGnB;AAEJ;"}
@@ -0,0 +1,11 @@
1
+ import { createContext as e, use as n } from "react";
2
+ const t = e({});
3
+ t.displayName = "AdminContext";
4
+ function i() {
5
+ return n(t);
6
+ }
7
+ export {
8
+ t as AdminContext,
9
+ i as useAdmin
10
+ };
11
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sources":["../../../../src/web/admin/context.tsx"],"sourcesContent":["import { createContext, use } from \"react\";\n\nimport type { Contest } from \"@olinfo/quizms/models\";\n\nexport type AdminContextProps = {\n name: string;\n contest: Contest;\n contests: Contest[];\n setContest: (contest: Contest) => Promise<void>;\n};\n\nexport const AdminContext = createContext<AdminContextProps>({} as AdminContextProps);\nAdminContext.displayName = \"AdminContext\";\n\nexport function useAdmin() {\n return use(AdminContext);\n}\n"],"names":["AdminContext","createContext","useAdmin","use"],"mappings":";AAWO,MAAMA,IAAeC,EAAiC,CAAA,CAAuB;AACpFD,EAAa,cAAc;AAEpB,SAASE,IAAW;AACzB,SAAOC,EAAIH,CAAY;AACzB;"}
@@ -0,0 +1,85 @@
1
+ import { jsxs as i, jsx as t } from "react/jsx-runtime";
2
+ import { Suspense as u } from "react";
3
+ import { Loading as m } from "@olinfo/quizms/components";
4
+ import { Card as e, CardBody as n, CardActions as s } from "@olinfo/react-components";
5
+ import { Link as f } from "wouter";
6
+ import { participationConverter as h, studentConverter as b } from "../common/converters.js";
7
+ import { useCount as r } from "../hooks/count.js";
8
+ import v from "./announcements.js";
9
+ import C from "./contest-settings.js";
10
+ import { useAdmin as c } from "./context.js";
11
+ import l from "./export.js";
12
+ function B() {
13
+ const { contest: o } = c();
14
+ return /* @__PURE__ */ i("div", { className: "flex flex-col gap-4", children: [
15
+ /* @__PURE__ */ t(e, { children: /* @__PURE__ */ t(n, { title: "Impostazioni gara", children: /* @__PURE__ */ t(C, {}) }) }),
16
+ /* @__PURE__ */ t(e, { children: /* @__PURE__ */ t(n, { title: "Comunicazioni", children: /* @__PURE__ */ t(v, {}) }) }),
17
+ /* @__PURE__ */ t(e, { children: /* @__PURE__ */ i(n, { title: "Scuole", children: [
18
+ /* @__PURE__ */ t(u, { fallback: /* @__PURE__ */ t(m, {}), children: /* @__PURE__ */ t(S, {}) }),
19
+ /* @__PURE__ */ t(s, { children: /* @__PURE__ */ t(f, { href: "/schools", className: "btn btn-primary", children: "Gestione scuole" }) })
20
+ ] }) }),
21
+ /* @__PURE__ */ t(e, { children: /* @__PURE__ */ t(n, { title: "Esportazione", children: /* @__PURE__ */ i(s, { children: [
22
+ /* @__PURE__ */ t(
23
+ l,
24
+ {
25
+ label: "scuole",
26
+ description: "tutte le scuole",
27
+ collection: "participations",
28
+ converter: h,
29
+ options: { constraints: { contestId: o.id, disabled: !1 } }
30
+ }
31
+ ),
32
+ /* @__PURE__ */ t(
33
+ l,
34
+ {
35
+ label: "studenti",
36
+ description: "tutti gli studenti",
37
+ collection: "students",
38
+ converter: b,
39
+ options: {
40
+ constraints: { contestId: o.id, disabled: !1 },
41
+ group: !0
42
+ }
43
+ }
44
+ )
45
+ ] }) }) })
46
+ ] });
47
+ }
48
+ function S() {
49
+ const { contest: o } = c(), d = r("participations", {
50
+ constraints: {
51
+ contestId: o.id,
52
+ disabled: !1
53
+ }
54
+ }), a = r("participations", {
55
+ constraints: {
56
+ contestId: o.id,
57
+ finalized: !0,
58
+ disabled: !1
59
+ }
60
+ }), p = r("students", {
61
+ constraints: {
62
+ contestId: o.id,
63
+ disabled: !1
64
+ },
65
+ group: !0
66
+ });
67
+ return /* @__PURE__ */ i("div", { children: [
68
+ /* @__PURE__ */ i("div", { children: [
69
+ "Scuole totali: ",
70
+ d
71
+ ] }),
72
+ /* @__PURE__ */ i("div", { children: [
73
+ "Scuole finalizzate: ",
74
+ a
75
+ ] }),
76
+ /* @__PURE__ */ i("div", { children: [
77
+ "Studenti totali: ",
78
+ p
79
+ ] })
80
+ ] });
81
+ }
82
+ export {
83
+ B as default
84
+ };
85
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sources":["../../../../src/web/admin/dashboard.tsx"],"sourcesContent":["import { Suspense } from \"react\";\n\nimport { Loading } from \"@olinfo/quizms/components\";\nimport type { Participation, Student } from \"@olinfo/quizms/models\";\nimport { Card, CardActions, CardBody } from \"@olinfo/react-components\";\nimport { Link } from \"wouter\";\n\nimport { participationConverter, studentConverter } from \"~/web/common/converters\";\nimport { useCount } from \"~/web/hooks/count\";\n\nimport Announcements from \"./announcements\";\nimport ContestSettings from \"./contest-settings\";\nimport { useAdmin } from \"./context\";\nimport Export from \"./export\";\n\nexport default function Dashboard() {\n const { contest } = useAdmin();\n\n return (\n <div className=\"flex flex-col gap-4\">\n <Card>\n <CardBody title=\"Impostazioni gara\">\n <ContestSettings />\n </CardBody>\n </Card>\n <Card>\n <CardBody title=\"Comunicazioni\">\n <Announcements />\n </CardBody>\n </Card>\n <Card>\n <CardBody title=\"Scuole\">\n <Suspense fallback={<Loading />}>\n <ContestInformation />\n </Suspense>\n <CardActions>\n <Link href=\"/schools\" className=\"btn btn-primary\">\n Gestione scuole\n </Link>\n </CardActions>\n </CardBody>\n </Card>\n <Card>\n <CardBody title=\"Esportazione\">\n <CardActions>\n <Export\n label=\"scuole\"\n description=\"tutte le scuole\"\n collection=\"participations\"\n converter={participationConverter}\n options={{ constraints: { contestId: contest.id, disabled: false } }}\n />\n <Export\n label=\"studenti\"\n description=\"tutti gli studenti\"\n collection=\"students\"\n converter={studentConverter}\n options={{\n constraints: { contestId: contest.id, disabled: false },\n group: true,\n }}\n />\n </CardActions>\n </CardBody>\n </Card>\n </div>\n );\n}\n\nfunction ContestInformation() {\n const { contest } = useAdmin();\n\n const totalSchools = useCount<Participation>(\"participations\", {\n constraints: {\n contestId: contest.id,\n disabled: false,\n },\n });\n\n const finalizedSchools = useCount<Participation>(\"participations\", {\n constraints: {\n contestId: contest.id,\n finalized: true,\n disabled: false,\n },\n });\n\n const totalStudents = useCount<Student>(\"students\", {\n constraints: {\n contestId: contest.id,\n disabled: false,\n },\n group: true,\n });\n\n return (\n <div>\n <div>Scuole totali: {totalSchools}</div>\n <div>Scuole finalizzate: {finalizedSchools}</div>\n <div>Studenti totali: {totalStudents}</div>\n </div>\n );\n}\n"],"names":["Dashboard","contest","useAdmin","jsxs","jsx","Card","CardBody","ContestSettings","Announcements","Suspense","Loading","ContestInformation","CardActions","Link","Export","participationConverter","studentConverter","totalSchools","useCount","finalizedSchools","totalStudents"],"mappings":";;;;;;;;;;;AAeA,SAAwBA,IAAY;AAClC,QAAM,EAAE,SAAAC,EAAA,IAAYC,EAAA;AAEpB,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAU,uBACb,UAAA;AAAA,IAAA,gBAAAC,EAACC,GAAA,EACC,4BAACC,GAAA,EAAS,OAAM,qBACd,UAAA,gBAAAF,EAACG,GAAA,CAAA,CAAgB,GACnB,EAAA,CACF;AAAA,IACA,gBAAAH,EAACC,KACC,UAAA,gBAAAD,EAACE,GAAA,EAAS,OAAM,iBACd,UAAA,gBAAAF,EAACI,GAAA,CAAA,CAAc,EAAA,CACjB,EAAA,CACF;AAAA,IACA,gBAAAJ,EAACC,GAAA,EACC,UAAA,gBAAAF,EAACG,GAAA,EAAS,OAAM,UACd,UAAA;AAAA,MAAA,gBAAAF,EAACK,KAAS,UAAU,gBAAAL,EAACM,KAAQ,GAC3B,UAAA,gBAAAN,EAACO,KAAmB,EAAA,CACtB;AAAA,MACA,gBAAAP,EAACQ,KACC,UAAA,gBAAAR,EAACS,GAAA,EAAK,MAAK,YAAW,WAAU,mBAAkB,UAAA,kBAAA,CAElD,EAAA,CACF;AAAA,IAAA,EAAA,CACF,EAAA,CACF;AAAA,sBACCR,GAAA,EACC,UAAA,gBAAAD,EAACE,KAAS,OAAM,gBACd,4BAACM,GAAA,EACC,UAAA;AAAA,MAAA,gBAAAR;AAAA,QAACU;AAAA,QAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UACZ,YAAW;AAAA,UACX,WAAWC;AAAA,UACX,SAAS,EAAE,aAAa,EAAE,WAAWd,EAAQ,IAAI,UAAU,GAAA,EAAM;AAAA,QAAE;AAAA,MAAA;AAAA,MAErE,gBAAAG;AAAA,QAACU;AAAA,QAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UACZ,YAAW;AAAA,UACX,WAAWE;AAAA,UACX,SAAS;AAAA,YACP,aAAa,EAAE,WAAWf,EAAQ,IAAI,UAAU,GAAA;AAAA,YAChD,OAAO;AAAA,UAAA;AAAA,QACT;AAAA,MAAA;AAAA,IACF,EAAA,CACF,GACF,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;AAEA,SAASU,IAAqB;AAC5B,QAAM,EAAE,SAAAV,EAAA,IAAYC,EAAA,GAEde,IAAeC,EAAwB,kBAAkB;AAAA,IAC7D,aAAa;AAAA,MACX,WAAWjB,EAAQ;AAAA,MACnB,UAAU;AAAA,IAAA;AAAA,EACZ,CACD,GAEKkB,IAAmBD,EAAwB,kBAAkB;AAAA,IACjE,aAAa;AAAA,MACX,WAAWjB,EAAQ;AAAA,MACnB,WAAW;AAAA,MACX,UAAU;AAAA,IAAA;AAAA,EACZ,CACD,GAEKmB,IAAgBF,EAAkB,YAAY;AAAA,IAClD,aAAa;AAAA,MACX,WAAWjB,EAAQ;AAAA,MACnB,UAAU;AAAA,IAAA;AAAA,IAEZ,OAAO;AAAA,EAAA,CACR;AAED,2BACG,OAAA,EACC,UAAA;AAAA,IAAA,gBAAAE,EAAC,OAAA,EAAI,UAAA;AAAA,MAAA;AAAA,MAAgBc;AAAA,IAAA,GAAa;AAAA,sBACjC,OAAA,EAAI,UAAA;AAAA,MAAA;AAAA,MAAqBE;AAAA,IAAA,GAAiB;AAAA,sBAC1C,OAAA,EAAI,UAAA;AAAA,MAAA;AAAA,MAAkBC;AAAA,IAAA,EAAA,CAAc;AAAA,EAAA,GACvC;AAEJ;"}
@@ -0,0 +1,59 @@
1
+ import { jsxs as i, Fragment as p, jsx as t } from "react/jsx-runtime";
2
+ import { useRef as h, forwardRef as w } from "react";
3
+ import { Button as u, Modal as g } from "@olinfo/react-components";
4
+ import { getDocs as x } from "firebase/firestore";
5
+ import { TriangleAlert as b } from "lucide-react";
6
+ import { useDb as y } from "../common/base-login.js";
7
+ import E from "../common/query.js";
8
+ import { useAdmin as v } from "./context.js";
9
+ function $(a) {
10
+ const e = h(null);
11
+ return /* @__PURE__ */ i(p, { children: [
12
+ /* @__PURE__ */ i(u, { className: "btn-primary", onClick: () => e.current?.showModal(), children: [
13
+ "Esporta ",
14
+ a.label
15
+ ] }),
16
+ /* @__PURE__ */ t(N, { ref: e, ...a })
17
+ ] });
18
+ }
19
+ const N = w(function({ label: e, description: l, collection: c, converter: m, options: n }, r) {
20
+ const { contest: s } = v(), d = y(), o = async () => {
21
+ await S(d, c, m, n), r && "current" in r && r.current?.close();
22
+ };
23
+ return /* @__PURE__ */ t(g, { ref: r, title: `Esporta ${e}`, children: window.showSaveFilePicker === void 0 ? /* @__PURE__ */ i("div", { className: "flex items-center gap-3", children: [
24
+ /* @__PURE__ */ t(b, { size: 20, className: "flex-none text-warning" }),
25
+ /* @__PURE__ */ t("span", { children: "Il tuo browser non supporta l'esportazione dei dati. Usa un browser Chrome o Edge." })
26
+ ] }) : /* @__PURE__ */ i(p, { children: [
27
+ /* @__PURE__ */ i("div", { children: [
28
+ "Stai per esportare i dati di ",
29
+ l,
30
+ " della gara ",
31
+ /* @__PURE__ */ t("b", { children: s.name }),
32
+ ". Questa operazione esegue un grande numero di query al database che richiederà del tempo e potrà incidere sulla fatturazione del progetto."
33
+ ] }),
34
+ /* @__PURE__ */ t("div", { className: "mt-4 flex flex-wrap justify-center gap-2", children: /* @__PURE__ */ t(u, { className: "btn-primary", onClick: o, children: "Esporta" }) })
35
+ ] }) });
36
+ });
37
+ async function S(a, e, l, c) {
38
+ const n = await (await window.showSaveFilePicker({
39
+ suggestedName: `${e}.jsonl`
40
+ })).createWritable(), r = 1e4;
41
+ let s;
42
+ for (; ; ) {
43
+ const d = E(a, e, l, {
44
+ ...c,
45
+ limit: r,
46
+ startAfter: s
47
+ }), o = await x(d);
48
+ if (o.empty) break;
49
+ for (const f of o.docs)
50
+ await n.write(`${JSON.stringify(f.data())}
51
+ `);
52
+ s = o.docs.at(-1);
53
+ }
54
+ await n.close();
55
+ }
56
+ export {
57
+ $ as default
58
+ };
59
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sources":["../../../../src/web/admin/export.tsx"],"sourcesContent":["import { forwardRef, type Ref, useRef } from \"react\";\n\nimport { Button, Modal } from \"@olinfo/react-components\";\nimport {\n type DocumentSnapshot,\n type Firestore,\n type FirestoreDataConverter,\n getDocs,\n} from \"firebase/firestore\";\nimport { TriangleAlert } from \"lucide-react\";\n\nimport { useDb } from \"~/web/common/base-login\";\nimport query, { type QueryOption } from \"~/web/common/query\";\n\nimport { useAdmin } from \"./context\";\n\ntype Props<T> = {\n label: string;\n description: string;\n collection: string;\n converter: FirestoreDataConverter<T>;\n options: QueryOption<T>;\n};\n\nexport default function Export<T>(props: Props<T>) {\n const ref = useRef<HTMLDialogElement>(null);\n\n return (\n <>\n <Button className=\"btn-primary\" onClick={() => ref.current?.showModal()}>\n Esporta {props.label}\n </Button>\n <ExportModal ref={ref} {...props} />\n </>\n );\n}\n\nconst ExportModal = forwardRef(function StudentExportModal(\n { label, description, collection, converter, options }: Props<any>,\n ref: Ref<HTMLDialogElement>,\n) {\n const { contest } = useAdmin();\n const db = useDb();\n\n const onExport = async () => {\n await saveExport(db, collection, converter, options);\n if (ref && \"current\" in ref) {\n ref.current?.close();\n }\n };\n\n return (\n <Modal ref={ref} title={`Esporta ${label}`}>\n {window.showSaveFilePicker === undefined ? (\n <div className=\"flex items-center gap-3\">\n <TriangleAlert size={20} className=\"flex-none text-warning\" />\n <span>\n Il tuo browser non supporta l&apos;esportazione dei dati. Usa un browser Chrome o Edge.\n </span>\n </div>\n ) : (\n <>\n <div>\n Stai per esportare i dati di {description} della gara <b>{contest.name}</b>. Questa\n operazione esegue un grande numero di query al database che richiederà del tempo e potrà\n incidere sulla fatturazione del progetto.\n </div>\n <div className=\"mt-4 flex flex-wrap justify-center gap-2\">\n <Button className=\"btn-primary\" onClick={onExport}>\n Esporta\n </Button>\n </div>\n </>\n )}\n </Modal>\n );\n});\n\nasync function saveExport<T>(\n db: Firestore,\n collection: string,\n converter: FirestoreDataConverter<T>,\n options: QueryOption<T>,\n) {\n const handle = await window.showSaveFilePicker({\n suggestedName: `${collection}.jsonl`,\n });\n\n const writableStream = await handle.createWritable();\n\n const chunkSize = 10_000;\n\n let last: DocumentSnapshot<T> | undefined;\n for (;;) {\n const q = query(db, collection, converter, {\n ...options,\n limit: chunkSize,\n startAfter: last,\n });\n\n const snapshot = await getDocs(q);\n if (snapshot.empty) break;\n\n for (const doc of snapshot.docs) {\n await writableStream.write(`${JSON.stringify(doc.data())}\\n`);\n }\n\n last = snapshot.docs.at(-1);\n }\n\n await writableStream.close();\n}\n"],"names":["Export","props","ref","useRef","jsxs","Fragment","Button","jsx","ExportModal","forwardRef","label","description","collection","converter","options","contest","useAdmin","db","useDb","onExport","saveExport","Modal","TriangleAlert","writableStream","chunkSize","last","q","query","snapshot","getDocs","doc"],"mappings":";;;;;;;;AAwBA,SAAwBA,EAAUC,GAAiB;AACjD,QAAMC,IAAMC,EAA0B,IAAI;AAE1C,SACE,gBAAAC,EAAAC,GAAA,EACE,UAAA;AAAA,IAAA,gBAAAD,EAACE,GAAA,EAAO,WAAU,eAAc,SAAS,MAAMJ,EAAI,SAAS,aAAa,UAAA;AAAA,MAAA;AAAA,MAC9DD,EAAM;AAAA,IAAA,GACjB;AAAA,IACA,gBAAAM,EAACC,GAAA,EAAY,KAAAN,GAAW,GAAGD,EAAA,CAAO;AAAA,EAAA,GACpC;AAEJ;AAEA,MAAMO,IAAcC,EAAW,SAC7B,EAAE,OAAAC,GAAO,aAAAC,GAAa,YAAAC,GAAY,WAAAC,GAAW,SAAAC,EAAA,GAC7CZ,GACA;AACA,QAAM,EAAE,SAAAa,EAAA,IAAYC,EAAA,GACdC,IAAKC,EAAA,GAELC,IAAW,YAAY;AAC3B,UAAMC,EAAWH,GAAIL,GAAYC,GAAWC,CAAO,GAC/CZ,KAAO,aAAaA,KACtBA,EAAI,SAAS,MAAA;AAAA,EAEjB;AAEA,SACE,gBAAAK,EAACc,GAAA,EAAM,KAAAnB,GAAU,OAAO,WAAWQ,CAAK,IACrC,UAAA,OAAO,uBAAuB,SAC7B,gBAAAN,EAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,IAAA,gBAAAG,EAACe,GAAA,EAAc,MAAM,IAAI,WAAU,0BAAyB;AAAA,IAC5D,gBAAAf,EAAC,UAAK,UAAA,qFAAA,CAEN;AAAA,EAAA,EAAA,CACF,IAEA,gBAAAH,EAAAC,GAAA,EACE,UAAA;AAAA,IAAA,gBAAAD,EAAC,OAAA,EAAI,UAAA;AAAA,MAAA;AAAA,MAC2BO;AAAA,MAAY;AAAA,MAAY,gBAAAJ,EAAC,KAAA,EAAG,UAAAQ,EAAQ,KAAA,CAAK;AAAA,MAAI;AAAA,IAAA,GAG7E;AAAA,IACA,gBAAAR,EAAC,OAAA,EAAI,WAAU,4CACb,UAAA,gBAAAA,EAACD,GAAA,EAAO,WAAU,eAAc,SAASa,GAAU,UAAA,UAAA,CAEnD,EAAA,CACF;AAAA,EAAA,EAAA,CACF,EAAA,CAEJ;AAEJ,CAAC;AAED,eAAeC,EACbH,GACAL,GACAC,GACAC,GACA;AAKA,QAAMS,IAAiB,OAJR,MAAM,OAAO,mBAAmB;AAAA,IAC7C,eAAe,GAAGX,CAAU;AAAA,EAAA,CAC7B,GAEmC,eAAA,GAE9BY,IAAY;AAElB,MAAIC;AACJ,aAAS;AACP,UAAMC,IAAIC,EAAMV,GAAIL,GAAYC,GAAW;AAAA,MACzC,GAAGC;AAAA,MACH,OAAOU;AAAA,MACP,YAAYC;AAAA,IAAA,CACb,GAEKG,IAAW,MAAMC,EAAQH,CAAC;AAChC,QAAIE,EAAS,MAAO;AAEpB,eAAWE,KAAOF,EAAS;AACzB,YAAML,EAAe,MAAM,GAAG,KAAK,UAAUO,EAAI,MAAM,CAAC;AAAA,CAAI;AAG9D,IAAAL,IAAOG,EAAS,KAAK,GAAG,EAAE;AAAA,EAC5B;AAEA,QAAML,EAAe,MAAA;AACvB;"}
@@ -0,0 +1,38 @@
1
+ import { jsxs as o, Fragment as m, jsx as e } from "react/jsx-runtime";
2
+ import { Suspense as s } from "react";
3
+ import { useMetadata as u, ErrorBoundary as p, Loading as f } from "@olinfo/quizms/components";
4
+ import { Navbar as h, NavbarBrand as b, NavbarMenu as x, NavbarSubmenu as N, NavbarMenuItem as v, NavbarContent as w, Dropdown as g, DropdownButton as D, DropdownMenu as B, DropdownItem as I, Button as L } from "@olinfo/react-components";
5
+ import M from "clsx";
6
+ import { LogOut as j } from "lucide-react";
7
+ import { useRoute as k, Link as y } from "wouter";
8
+ function R({ name: t, contests: n, logout: d, children: l }) {
9
+ const c = u(), [, i] = k("/:contestId/*"), a = n.find((r) => r.id === i?.contestId);
10
+ return /* @__PURE__ */ o(m, { children: [
11
+ /* @__PURE__ */ o(h, { color: "bg-error text-error-content", children: [
12
+ /* @__PURE__ */ e(b, { children: /* @__PURE__ */ e("div", { className: "flex items-center h-full font-bold", children: c.title }) }),
13
+ a && n.length >= 2 && /* @__PURE__ */ e(x, { children: /* @__PURE__ */ e(N, { title: a.name, children: n.map((r) => /* @__PURE__ */ e(v, { children: /* @__PURE__ */ e(
14
+ y,
15
+ {
16
+ className: M(a.id === r.id && "active"),
17
+ href: `/${r.id}/${i["*"]}`,
18
+ children: r.name
19
+ }
20
+ ) }, r.id)) }) }),
21
+ /* @__PURE__ */ e(w, { children: /* @__PURE__ */ e(C, { name: t, logout: d }) })
22
+ ] }),
23
+ /* @__PURE__ */ e("div", { className: "mx-auto flex w-full max-w-screen-xl grow flex-col p-4 pb-8", children: /* @__PURE__ */ e(p, { children: /* @__PURE__ */ e(s, { fallback: /* @__PURE__ */ e(f, {}), children: l }) }) })
24
+ ] });
25
+ }
26
+ function C({ name: t, logout: n }) {
27
+ return /* @__PURE__ */ o(g, { className: "dropdown-end", children: [
28
+ /* @__PURE__ */ e(D, { children: /* @__PURE__ */ e("div", { className: "truncate uppercase", children: t }) }),
29
+ /* @__PURE__ */ e(B, { children: /* @__PURE__ */ e(I, { children: /* @__PURE__ */ o(L, { className: "flex justify-between gap-4", onClick: n, children: [
30
+ "Esci ",
31
+ /* @__PURE__ */ e(j, { size: 20 })
32
+ ] }) }) })
33
+ ] });
34
+ }
35
+ export {
36
+ R as AdminLayout
37
+ };
38
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sources":["../../../../src/web/admin/layout.tsx"],"sourcesContent":["import { type ReactNode, Suspense } from \"react\";\n\nimport { ErrorBoundary, Loading, useMetadata } from \"@olinfo/quizms/components\";\nimport type { Contest } from \"@olinfo/quizms/models\";\nimport {\n Button,\n Dropdown,\n DropdownButton,\n DropdownItem,\n DropdownMenu,\n Navbar,\n NavbarBrand,\n NavbarContent,\n NavbarMenu,\n NavbarMenuItem,\n NavbarSubmenu,\n} from \"@olinfo/react-components\";\nimport clsx from \"clsx\";\nimport { LogOut } from \"lucide-react\";\nimport { Link, useRoute } from \"wouter\";\n\ntype Props = {\n name: string;\n contests: Contest[];\n logout: () => Promise<void>;\n children: ReactNode;\n};\n\nexport function AdminLayout({ name, contests, logout, children }: Props) {\n const metadata = useMetadata();\n const [, params] = useRoute(\"/:contestId/*\");\n const contest = contests.find((c) => c.id === params?.contestId);\n\n return (\n <>\n <Navbar color=\"bg-error text-error-content\">\n <NavbarBrand>\n <div className=\"flex items-center h-full font-bold\">{metadata.title}</div>\n </NavbarBrand>\n {contest && contests.length >= 2 && (\n <NavbarMenu>\n <NavbarSubmenu title={contest.name}>\n {contests.map((c) => (\n <NavbarMenuItem key={c.id}>\n <Link\n className={clsx(contest.id === c.id && \"active\")}\n href={`/${c.id}/${params![\"*\"]}`}>\n {c.name}\n </Link>\n </NavbarMenuItem>\n ))}\n </NavbarSubmenu>\n </NavbarMenu>\n )}\n <NavbarContent>\n <UserDropdown name={name} logout={logout} />\n </NavbarContent>\n </Navbar>\n <div className=\"mx-auto flex w-full max-w-screen-xl grow flex-col p-4 pb-8\">\n <ErrorBoundary>\n <Suspense fallback={<Loading />}>{children}</Suspense>\n </ErrorBoundary>\n </div>\n </>\n );\n}\n\nfunction UserDropdown({ name, logout }: { name: string; logout: () => Promise<void> }) {\n return (\n <Dropdown className=\"dropdown-end\">\n <DropdownButton>\n <div className=\"truncate uppercase\">{name}</div>\n </DropdownButton>\n <DropdownMenu>\n <DropdownItem>\n <Button className=\"flex justify-between gap-4\" onClick={logout}>\n Esci <LogOut size={20} />\n </Button>\n </DropdownItem>\n </DropdownMenu>\n </Dropdown>\n );\n}\n"],"names":["AdminLayout","name","contests","logout","children","metadata","useMetadata","params","useRoute","contest","c","jsxs","Fragment","Navbar","jsx","NavbarBrand","NavbarMenu","NavbarSubmenu","NavbarMenuItem","Link","clsx","NavbarContent","UserDropdown","ErrorBoundary","Suspense","Loading","Dropdown","DropdownButton","DropdownMenu","DropdownItem","Button","LogOut"],"mappings":";;;;;;;AA4BO,SAASA,EAAY,EAAE,MAAAC,GAAM,UAAAC,GAAU,QAAAC,GAAQ,UAAAC,KAAmB;AACvE,QAAMC,IAAWC,EAAA,GACX,GAAGC,CAAM,IAAIC,EAAS,eAAe,GACrCC,IAAUP,EAAS,KAAK,CAACQ,MAAMA,EAAE,OAAOH,GAAQ,SAAS;AAE/D,SACE,gBAAAI,EAAAC,GAAA,EACE,UAAA;AAAA,IAAA,gBAAAD,EAACE,GAAA,EAAO,OAAM,+BACZ,UAAA;AAAA,MAAA,gBAAAC,EAACC,KACC,UAAA,gBAAAD,EAAC,OAAA,EAAI,WAAU,sCAAsC,UAAAT,EAAS,OAAM,EAAA,CACtE;AAAA,MACCI,KAAWP,EAAS,UAAU,KAC7B,gBAAAY,EAACE,KACC,UAAA,gBAAAF,EAACG,GAAA,EAAc,OAAOR,EAAQ,MAC3B,UAAAP,EAAS,IAAI,CAACQ,wBACZQ,GAAA,EACC,UAAA,gBAAAJ;AAAA,QAACK;AAAA,QAAA;AAAA,UACC,WAAWC,EAAKX,EAAQ,OAAOC,EAAE,MAAM,QAAQ;AAAA,UAC/C,MAAM,IAAIA,EAAE,EAAE,IAAIH,EAAQ,GAAG,CAAC;AAAA,UAC7B,UAAAG,EAAE;AAAA,QAAA;AAAA,MAAA,EACL,GALmBA,EAAE,EAMvB,CACD,GACH,EAAA,CACF;AAAA,wBAEDW,GAAA,EACC,UAAA,gBAAAP,EAACQ,GAAA,EAAa,MAAArB,GAAY,QAAAE,GAAgB,EAAA,CAC5C;AAAA,IAAA,GACF;AAAA,IACA,gBAAAW,EAAC,OAAA,EAAI,WAAU,8DACb,4BAACS,GAAA,EACC,UAAA,gBAAAT,EAACU,GAAA,EAAS,UAAU,gBAAAV,EAACW,GAAA,CAAA,CAAQ,GAAK,UAAArB,GAAS,GAC7C,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;AAEA,SAASkB,EAAa,EAAE,MAAArB,GAAM,QAAAE,KAAyD;AACrF,SACE,gBAAAQ,EAACe,GAAA,EAAS,WAAU,gBAClB,UAAA;AAAA,IAAA,gBAAAZ,EAACa,KACC,UAAA,gBAAAb,EAAC,OAAA,EAAI,WAAU,sBAAsB,aAAK,EAAA,CAC5C;AAAA,IACA,gBAAAA,EAACc,KACC,UAAA,gBAAAd,EAACe,GAAA,EACC,4BAACC,GAAA,EAAO,WAAU,8BAA6B,SAAS3B,GAAQ,UAAA;AAAA,MAAA;AAAA,MACzD,gBAAAW,EAACiB,GAAA,EAAO,MAAM,GAAA,CAAI;AAAA,IAAA,EAAA,CACzB,GACF,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
@@ -0,0 +1,30 @@
1
+ import { jsxs as o, jsx as r } from "react/jsx-runtime";
2
+ import { lazy as c } from "react";
3
+ import { Route as a, Redirect as d, Link as m, useParams as h, Switch as f } from "wouter";
4
+ import { AdminContext as p } from "./context.js";
5
+ import { AdminLayout as s } from "./layout.js";
6
+ import { SchoolTable as u } from "./school-table.js";
7
+ function P({ name: i, contests: e, setContest: l, logout: n }) {
8
+ return /* @__PURE__ */ o(s, { name: i, contests: e, logout: n, children: [
9
+ /* @__PURE__ */ o(a, { path: "/", children: [
10
+ e.length === 1 && /* @__PURE__ */ r(d, { to: `/${e[0].id}` }),
11
+ /* @__PURE__ */ o("div", { className: "flex w-full grow flex-col items-center justify-center gap-4", children: [
12
+ /* @__PURE__ */ r("p", { className: "text-2xl", children: "Seleziona una gara" }),
13
+ /* @__PURE__ */ r("div", { className: "flex flex-wrap justify-center gap-2", children: e.map((t) => /* @__PURE__ */ r(m, { className: "btn btn-primary", href: `/${t.id}`, children: t.name }, t.id)) })
14
+ ] })
15
+ ] }),
16
+ /* @__PURE__ */ r(a, { path: "/:contestId", nest: !0, children: /* @__PURE__ */ r(v, { name: i, contests: e, setContest: l, logout: n }) })
17
+ ] });
18
+ }
19
+ const x = c(() => import("./dashboard.js"));
20
+ function v({ contests: i, ...e }) {
21
+ const { contestId: l } = h(), n = i.find((t) => t.id === l);
22
+ return n ? /* @__PURE__ */ r(p.Provider, { value: { ...e, contest: n, contests: i }, children: /* @__PURE__ */ o(f, { children: [
23
+ /* @__PURE__ */ r(a, { path: "/", children: /* @__PURE__ */ r(x, {}) }),
24
+ /* @__PURE__ */ r(a, { path: "/schools", children: /* @__PURE__ */ r(u, {}) })
25
+ ] }) }) : /* @__PURE__ */ r(d, { to: "/" });
26
+ }
27
+ export {
28
+ P as AdminProvider
29
+ };
30
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sources":["../../../../src/web/admin/provider.tsx"],"sourcesContent":["import { lazy } from \"react\";\n\nimport type { Contest } from \"@olinfo/quizms/models\";\nimport { Link, Redirect, Route, Switch, useParams } from \"wouter\";\n\nimport { AdminContext } from \"./context\";\nimport { AdminLayout } from \"./layout\";\nimport { SchoolTable } from \"./school-table\";\n\ntype AdminProviderProps = {\n name: string;\n contests: Contest[];\n setContest: (contest: Contest) => Promise<void>;\n logout: () => Promise<void>;\n};\n\nexport function AdminProvider({ name, contests, setContest, logout }: AdminProviderProps) {\n return (\n <AdminLayout name={name} contests={contests} logout={logout}>\n <Route path=\"/\">\n {contests.length === 1 && <Redirect to={`/${contests[0].id}`} />}\n <div className=\"flex w-full grow flex-col items-center justify-center gap-4\">\n <p className=\"text-2xl\">Seleziona una gara</p>\n <div className=\"flex flex-wrap justify-center gap-2\">\n {contests.map((c) => (\n <Link key={c.id} className=\"btn btn-primary\" href={`/${c.id}`}>\n {c.name}\n </Link>\n ))}\n </div>\n </div>\n </Route>\n <Route path=\"/:contestId\" nest>\n <ProviderInner name={name} contests={contests} setContest={setContest} logout={logout} />\n </Route>\n </AdminLayout>\n );\n}\n\nconst Dashboard = lazy(() => import(\"./dashboard\"));\n\nfunction ProviderInner({ contests, ...props }: AdminProviderProps) {\n const { contestId } = useParams();\n\n const contest = contests.find((c) => c.id === contestId);\n if (!contest) {\n return <Redirect to=\"/\" />;\n }\n\n return (\n <AdminContext.Provider value={{ ...props, contest, contests }}>\n <Switch>\n <Route path=\"/\">\n <Dashboard />\n </Route>\n <Route path=\"/schools\">\n <SchoolTable />\n </Route>\n </Switch>\n </AdminContext.Provider>\n );\n}\n"],"names":["AdminProvider","name","contests","setContest","logout","jsxs","AdminLayout","Route","jsx","Redirect","c","Link","ProviderInner","Dashboard","lazy","props","contestId","useParams","contest","AdminContext","Switch","SchoolTable"],"mappings":";;;;;;AAgBO,SAASA,EAAc,EAAE,MAAAC,GAAM,UAAAC,GAAU,YAAAC,GAAY,QAAAC,KAA8B;AACxF,SACE,gBAAAC,EAACC,GAAA,EAAY,MAAAL,GAAY,UAAAC,GAAoB,QAAAE,GAC3C,UAAA;AAAA,IAAA,gBAAAC,EAACE,GAAA,EAAM,MAAK,KACT,UAAA;AAAA,MAAAL,EAAS,WAAW,KAAK,gBAAAM,EAACC,GAAA,EAAS,IAAI,IAAIP,EAAS,CAAC,EAAE,EAAE,GAAA,CAAI;AAAA,MAC9D,gBAAAG,EAAC,OAAA,EAAI,WAAU,+DACb,UAAA;AAAA,QAAA,gBAAAG,EAAC,KAAA,EAAE,WAAU,YAAW,UAAA,sBAAkB;AAAA,QAC1C,gBAAAA,EAAC,SAAI,WAAU,uCACZ,YAAS,IAAI,CAACE,MACb,gBAAAF,EAACG,GAAA,EAAgB,WAAU,mBAAkB,MAAM,IAAID,EAAE,EAAE,IACxD,YAAE,KAAA,GADMA,EAAE,EAEb,CACD,EAAA,CACH;AAAA,MAAA,EAAA,CACF;AAAA,IAAA,GACF;AAAA,IACA,gBAAAF,EAACD,GAAA,EAAM,MAAK,eAAc,MAAI,IAC5B,UAAA,gBAAAC,EAACI,GAAA,EAAc,MAAAX,GAAY,UAAAC,GAAoB,YAAAC,GAAwB,QAAAC,EAAA,CAAgB,EAAA,CACzF;AAAA,EAAA,GACF;AAEJ;AAEA,MAAMS,IAAYC,EAAK,MAAM,OAAO,gBAAa,CAAC;AAElD,SAASF,EAAc,EAAE,UAAAV,GAAU,GAAGa,KAA6B;AACjE,QAAM,EAAE,WAAAC,EAAA,IAAcC,EAAA,GAEhBC,IAAUhB,EAAS,KAAK,CAACQ,MAAMA,EAAE,OAAOM,CAAS;AACvD,SAAKE,IAKH,gBAAAV,EAACW,EAAa,UAAb,EAAsB,OAAO,EAAE,GAAGJ,GAAO,SAAAG,GAAS,UAAAhB,EAAA,GACjD,UAAA,gBAAAG,EAACe,GAAA,EACC,UAAA;AAAA,IAAA,gBAAAZ,EAACD,GAAA,EAAM,MAAK,KACV,UAAA,gBAAAC,EAACK,KAAU,GACb;AAAA,sBACCN,GAAA,EAAM,MAAK,YACV,UAAA,gBAAAC,EAACa,KAAY,EAAA,CACf;AAAA,EAAA,EAAA,CACF,EAAA,CACF,IAbO,gBAAAb,EAACC,GAAA,EAAS,IAAG,IAAA,CAAI;AAe5B;"}
@@ -0,0 +1,79 @@
1
+ import { jsx as e } from "react/jsx-runtime";
2
+ import { useMemo as s, Suspense as r } from "react";
3
+ import { AgGrid as d, Loading as c } from "@olinfo/quizms/components";
4
+ import { participationConverter as f } from "../common/converters.js";
5
+ import "firebase/auth";
6
+ import "lodash-es";
7
+ import "react-error-boundary";
8
+ import "swr";
9
+ import "../common/base-login.js";
10
+ import { useCollection as m } from "../hooks/collection.js";
11
+ import "firebase/firestore";
12
+ import { useCount as p } from "../hooks/count.js";
13
+ import { useAdmin as u } from "./context.js";
14
+ function T() {
15
+ const { contest: i } = u(), [o, n] = m(
16
+ "participations",
17
+ f,
18
+ {
19
+ constraints: { contestId: i.id },
20
+ subscribe: !0
21
+ }
22
+ ), a = s(() => h(), []);
23
+ return /* @__PURE__ */ e(r, { fallback: /* @__PURE__ */ e(c, {}), children: /* @__PURE__ */ e("div", { className: "relative grow p-2", children: /* @__PURE__ */ e("div", { className: "absolute inset-0", children: /* @__PURE__ */ e(
24
+ d,
25
+ {
26
+ rowData: o,
27
+ getRowId: (t) => t.data.id,
28
+ columnDefs: a,
29
+ onCellEditRequest: async (t) => {
30
+ const l = t.data;
31
+ t.colDef.field === "finalized" && await n({ ...l, finalized: t.newValue }), t.api.refreshCells({ force: !0 });
32
+ }
33
+ }
34
+ ) }) }) });
35
+ }
36
+ function h() {
37
+ return [
38
+ {
39
+ field: "schoolId",
40
+ headerName: "ID",
41
+ width: 150,
42
+ filter: !0
43
+ },
44
+ {
45
+ field: "name",
46
+ headerName: "Nome",
47
+ minWidth: 200,
48
+ flex: 1,
49
+ filter: !0
50
+ },
51
+ {
52
+ field: "count",
53
+ headerName: "Studenti",
54
+ width: 100,
55
+ sortable: !1,
56
+ cellRenderer: ({ data: i }) => {
57
+ if (i)
58
+ return /* @__PURE__ */ e(r, { children: /* @__PURE__ */ e(b, { participationId: i.id }) });
59
+ }
60
+ },
61
+ {
62
+ field: "finalized",
63
+ headerName: "Finalizzato",
64
+ width: 100,
65
+ filter: !0,
66
+ cellDataType: "boolean",
67
+ editable: !0
68
+ }
69
+ ];
70
+ }
71
+ function b({ participationId: i }) {
72
+ return p(`participations/${i}/students`, {
73
+ constraints: { disabled: !1 }
74
+ });
75
+ }
76
+ export {
77
+ T as SchoolTable
78
+ };
79
+ //# sourceMappingURL=school-table.js.map