@olinfo/quizms-firebase 4.1.3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/cli.js +14 -14
  2. package/dist/cli.js.map +1 -1
  3. package/dist/package.json.js +1 -1
  4. package/dist/src/cli/definalize.js +17 -17
  5. package/dist/src/cli/definalize.js.map +1 -1
  6. package/dist/src/cli/export.js +42 -34
  7. package/dist/src/cli/export.js.map +1 -1
  8. package/dist/src/cli/files/firestore-indexes.json.js +1 -1
  9. package/dist/src/cli/files/firestore.rules.js +27 -27
  10. package/dist/src/cli/files/firestore.rules.js.map +1 -1
  11. package/dist/src/cli/files/functions-index.js +3 -3
  12. package/dist/src/cli/files/functions-index.js.map +1 -1
  13. package/dist/src/cli/files/storage.rules.js +3 -3
  14. package/dist/src/cli/files/storage.rules.js.map +1 -1
  15. package/dist/src/cli/import.js +65 -71
  16. package/dist/src/cli/import.js.map +1 -1
  17. package/dist/src/cli/update-scores.js +7 -7
  18. package/dist/src/cli/update-scores.js.map +1 -1
  19. package/dist/src/cli/utils/converters-admin.js +23 -22
  20. package/dist/src/cli/utils/converters-admin.js.map +1 -1
  21. package/dist/src/functions/api/index.js +18 -18
  22. package/dist/src/functions/api/index.js.map +1 -1
  23. package/dist/src/functions/api/queries.js +21 -21
  24. package/dist/src/functions/api/queries.js.map +1 -1
  25. package/dist/src/functions/api/student.js +46 -47
  26. package/dist/src/functions/api/student.js.map +1 -1
  27. package/dist/src/functions/api/teacher.js +92 -95
  28. package/dist/src/functions/api/teacher.js.map +1 -1
  29. package/dist/src/functions/submissions.js +11 -7
  30. package/dist/src/functions/submissions.js.map +1 -1
  31. package/dist/src/functions/update-scores.js +14 -14
  32. package/dist/src/functions/update-scores.js.map +1 -1
  33. package/dist/src/models/submission.js +13 -0
  34. package/dist/src/models/submission.js.map +1 -0
  35. package/dist/src/web/admin/announcements.js +45 -42
  36. package/dist/src/web/admin/announcements.js.map +1 -1
  37. package/dist/src/web/admin/dashboard.js +43 -43
  38. package/dist/src/web/admin/dashboard.js.map +1 -1
  39. package/dist/src/web/admin/export.js +56 -52
  40. package/dist/src/web/admin/export.js.map +1 -1
  41. package/dist/src/web/admin/provider.js +28 -28
  42. package/dist/src/web/admin/provider.js.map +1 -1
  43. package/dist/src/web/admin/school-table.js +20 -24
  44. package/dist/src/web/admin/school-table.js.map +1 -1
  45. package/dist/src/web/common/api.js +25 -25
  46. package/dist/src/web/common/api.js.map +1 -1
  47. package/dist/src/web/common/converters.js +38 -39
  48. package/dist/src/web/common/converters.js.map +1 -1
  49. package/dist/src/web/entry/student-statement.js +1 -1
  50. package/dist/src/web/entry/student-statement.js.map +1 -1
  51. package/dist/src/web/entry/student.js +80 -75
  52. package/dist/src/web/entry/student.js.map +1 -1
  53. package/dist/src/web/entry/teacher.js +63 -62
  54. package/dist/src/web/entry/teacher.js.map +1 -1
  55. package/package.json +13 -13
@@ -1,63 +1,66 @@
1
- import { jsxs as o, Fragment as f, jsx as e } from "react/jsx-runtime";
2
- import { useRef as p, Fragment as u, 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 y, Modal as N, Form as b, 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";
1
+ import { jsxs as t, Fragment as d, jsx as e } from "react/jsx-runtime";
2
+ import { useRef as f, Fragment as p, useReducer as u } from "react";
3
+ import { randomId as g } from "@olinfo/quizms/utils";
4
+ import { DateTime as h, CardActions as A, Button as x, Modal as w, Form as y, SelectField as N, TextField as b, TextAreaField as z, SubmitButton as F } from "@olinfo/react-components";
5
+ import { Info as I, TriangleAlert as v, CircleX as C } from "lucide-react";
6
+ import M from "react-markdown";
7
+ import { announcementConverter as T } from "../common/converters.js";
8
8
  import "firebase/auth";
9
9
  import "lodash-es";
10
10
  import "react-error-boundary";
11
11
  import "swr";
12
12
  import "../common/base-login.js";
13
- import { useCollection as R } from "../hooks/collection.js";
13
+ import { useCollection as S } from "../hooks/collection.js";
14
14
  import "firebase/firestore";
15
15
  import { useAdmin as s } from "./context.js";
16
- function W() {
17
- const { contest: a } = s(), t = p(null), [r, n] = R("announcements", B, {
16
+ function U() {
17
+ const { contest: o } = s(), r = f(null), [l, n] = S("announcements", T, {
18
18
  arrayConstraints: {
19
- contestIds: a.id
19
+ contestIds: o.id
20
20
  },
21
21
  orderBy: ["createdAt", "desc"]
22
22
  });
23
- return /* @__PURE__ */ o(f, { children: [
24
- /* @__PURE__ */ e("div", { className: "prose mt-4 max-w-none", children: r.map((i) => /* @__PURE__ */ o(u, { children: [
25
- /* @__PURE__ */ o("h4", { className: "flex items-center gap-3", children: [
26
- i.level === "info" && /* @__PURE__ */ e(C, { size: 20, className: "text-info" }),
27
- i.level === "warning" && /* @__PURE__ */ e(M, { size: 20, className: "text-warning" }),
28
- i.level === "error" && /* @__PURE__ */ e(T, { size: 20, className: "text-error" }),
23
+ return /* @__PURE__ */ t(d, { children: [
24
+ /* @__PURE__ */ e("div", { className: "prose mt-4 max-w-none", children: l.map((i) => /* @__PURE__ */ t(p, { children: [
25
+ /* @__PURE__ */ t("h4", { className: "flex items-center gap-3", children: [
26
+ i.level === "info" && /* @__PURE__ */ e(I, { size: 20, className: "text-info" }),
27
+ i.level === "warning" && /* @__PURE__ */ e(v, { size: 20, className: "text-warning" }),
28
+ i.level === "error" && /* @__PURE__ */ e(C, { size: 20, className: "text-error" }),
29
29
  i.title
30
30
  ] }),
31
- /* @__PURE__ */ e("small", { children: /* @__PURE__ */ e(x, { date: i.createdAt }) }),
32
- /* @__PURE__ */ e(S, { children: i.body }),
31
+ /* @__PURE__ */ e("small", { children: /* @__PURE__ */ e(h, { date: i.createdAt }) }),
32
+ /* @__PURE__ */ e(M, { children: i.body }),
33
33
  /* @__PURE__ */ e("hr", { className: "my-5 last:hidden" })
34
34
  ] }, i.id)) }),
35
- /* @__PURE__ */ e(w, { children: /* @__PURE__ */ e(y, { className: "btn-primary", onClick: () => t.current?.showModal(), children: "Aggiungi comunicazione" }) }),
36
- /* @__PURE__ */ e(j, { ref: t, addAnnouncement: n })
35
+ /* @__PURE__ */ e(A, { children: /* @__PURE__ */ e(x, { className: "btn-primary", onClick: () => r.current?.showModal(), children: "Aggiungi comunicazione" }) }),
36
+ /* @__PURE__ */ e(B, { ref: r, addAnnouncement: n })
37
37
  ] });
38
38
  }
39
- const j = g(function({ addAnnouncement: t }, r) {
40
- const { contest: n } = s(), [i, c] = h((l) => l + 1, 0);
41
- return /* @__PURE__ */ e(N, { ref: r, title: "Aggiungi comunicazione", children: /* @__PURE__ */ o(b, { onSubmit: async ({
42
- level: l,
43
- title: m,
44
- body: d
39
+ function B({
40
+ addAnnouncement: o,
41
+ ref: r
42
+ }) {
43
+ const { contest: l } = s(), [n, i] = u((a) => a + 1, 0);
44
+ return /* @__PURE__ */ e(w, { ref: r, title: "Aggiungi comunicazione", children: /* @__PURE__ */ t(y, { onSubmit: async ({
45
+ level: a,
46
+ title: c,
47
+ body: m
45
48
  }) => {
46
49
  try {
47
- await t({
48
- id: A(),
50
+ await o({
51
+ id: g(),
49
52
  createdAt: /* @__PURE__ */ new Date(),
50
- contestIds: [n.id],
51
- level: l,
52
- title: m,
53
- body: d
54
- }), c();
53
+ contestIds: [l.id],
54
+ level: a,
55
+ title: c,
56
+ body: m
57
+ }), i();
55
58
  } finally {
56
- r && "current" in r && r.current?.close();
59
+ r.current?.close();
57
60
  }
58
61
  }, className: "!max-w-full", children: [
59
62
  /* @__PURE__ */ e(
60
- z,
63
+ N,
61
64
  {
62
65
  field: "level",
63
66
  label: "Priorità",
@@ -65,9 +68,9 @@ const j = g(function({ addAnnouncement: t }, r) {
65
68
  placeholder: "Seleziona la priorità"
66
69
  }
67
70
  ),
68
- /* @__PURE__ */ e(F, { field: "title", label: "Titolo", placeholder: "Inserisci il titolo" }),
71
+ /* @__PURE__ */ e(b, { field: "title", label: "Titolo", placeholder: "Inserisci il titolo" }),
69
72
  /* @__PURE__ */ e(
70
- I,
73
+ z,
71
74
  {
72
75
  field: "body",
73
76
  label: "Messaggio",
@@ -75,10 +78,10 @@ const j = g(function({ addAnnouncement: t }, r) {
75
78
  rows: 5
76
79
  }
77
80
  ),
78
- /* @__PURE__ */ e(v, { children: "Aggiungi" })
79
- ] }, i) });
80
- });
81
+ /* @__PURE__ */ e(F, { children: "Aggiungi" })
82
+ ] }, n) });
83
+ }
81
84
  export {
82
- W as default
85
+ U as default
83
86
  };
84
87
  //# sourceMappingURL=announcements.js.map
@@ -1 +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 });\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,EAAA,CAC9B;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;"}
1
+ {"version":3,"file":"announcements.js","sources":["../../../../src/web/admin/announcements.tsx"],"sourcesContent":["import { Fragment, type RefObject, 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 });\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\nfunction AnnouncementModal({\n addAnnouncement,\n ref,\n}: {\n addAnnouncement: (announcement: Announcement) => Promise<void>;\n ref: RefObject<HTMLDialogElement | null>;\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 ref.current?.close();\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","id","nextId","useReducer","prev","Modal","Form","level","title","body","randomId","SelectField","TextField","TextAreaField","SubmitButton"],"mappings":"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"}
@@ -1,102 +1,102 @@
1
- import { jsxs as o, jsx as t } from "react/jsx-runtime";
1
+ import { jsxs as i, jsx as t } from "react/jsx-runtime";
2
2
  import { useCallback as f, Suspense as p } from "react";
3
3
  import { Loading as h } from "@olinfo/quizms/components";
4
- import { Card as n, CardBody as s, CardActions as l } from "@olinfo/react-components";
4
+ import { Card as r, CardBody as s, CardActions as a } from "@olinfo/react-components";
5
5
  import { Link as b } from "wouter";
6
- import { participationConverter as g, studentConverter as m } from "../common/converters.js";
6
+ import { venueConverter as v, studentConverter as u } from "../common/converters.js";
7
7
  import { useCount as c } from "../hooks/count.js";
8
- import v from "./announcements.js";
8
+ import g from "./announcements.js";
9
9
  import S from "./contest-settings.js";
10
- import { useAdmin as u } from "./context.js";
11
- import d, { scoreboardFormatter as C, scoreboradHeader as I } from "./export.js";
10
+ import { useAdmin as m } from "./context.js";
11
+ import l, { scoreboardFormatter as C, scoreboradHeader as I } from "./export.js";
12
12
  function B() {
13
- const { contest: i, variants: r } = u(), a = f(
14
- (e) => C(i, r, e),
15
- [i, r]
13
+ const { contest: e, variants: n } = m(), d = f(
14
+ (o) => C(e, n, o),
15
+ [e, n]
16
16
  );
17
- return /* @__PURE__ */ o("div", { className: "flex flex-col gap-4", children: [
18
- /* @__PURE__ */ t(n, { children: /* @__PURE__ */ t(s, { title: "Impostazioni gara", children: /* @__PURE__ */ t(S, {}) }) }),
19
- /* @__PURE__ */ t(n, { children: /* @__PURE__ */ t(s, { title: "Comunicazioni", children: /* @__PURE__ */ t(v, {}) }) }),
20
- /* @__PURE__ */ t(n, { children: /* @__PURE__ */ o(s, { title: "Scuole", children: [
17
+ return /* @__PURE__ */ i("div", { className: "flex flex-col gap-4", children: [
18
+ /* @__PURE__ */ t(r, { children: /* @__PURE__ */ t(s, { title: "Impostazioni gara", children: /* @__PURE__ */ t(S, {}) }) }),
19
+ /* @__PURE__ */ t(r, { children: /* @__PURE__ */ t(s, { title: "Comunicazioni", children: /* @__PURE__ */ t(g, {}) }) }),
20
+ /* @__PURE__ */ t(r, { children: /* @__PURE__ */ i(s, { title: "Scuole", children: [
21
21
  /* @__PURE__ */ t(p, { fallback: /* @__PURE__ */ t(h, {}), children: /* @__PURE__ */ t(z, {}) }),
22
- /* @__PURE__ */ t(l, { children: /* @__PURE__ */ t(b, { href: "/schools", className: "btn btn-primary", children: "Gestione scuole" }) })
22
+ /* @__PURE__ */ t(a, { children: /* @__PURE__ */ t(b, { href: "/schools", className: "btn btn-primary", children: "Gestione scuole" }) })
23
23
  ] }) }),
24
- /* @__PURE__ */ t(n, { children: /* @__PURE__ */ t(s, { title: "Esportazione", children: /* @__PURE__ */ o(l, { children: [
24
+ /* @__PURE__ */ t(r, { children: /* @__PURE__ */ t(s, { title: "Esportazione", children: /* @__PURE__ */ i(a, { children: [
25
25
  /* @__PURE__ */ t(
26
- d,
26
+ l,
27
27
  {
28
28
  label: "scuole",
29
29
  description: "i dati di tutte le scuole",
30
- collection: "participations",
31
- converter: g,
32
- options: { constraints: { contestId: i.id, disabled: !1 } },
33
- formatter: (e) => JSON.stringify(e)
30
+ collection: "venues",
31
+ converter: v,
32
+ options: { constraints: { contestId: e.id, disabled: !1 } },
33
+ formatter: (o) => JSON.stringify(o)
34
34
  }
35
35
  ),
36
36
  /* @__PURE__ */ t(
37
- d,
37
+ l,
38
38
  {
39
39
  label: "studenti",
40
40
  description: "i dati di tutti gli studenti",
41
41
  collection: "students",
42
- converter: m,
42
+ converter: u,
43
43
  options: {
44
- constraints: { contestId: i.id, disabled: !1 },
44
+ constraints: { contestId: e.id, disabled: !1 },
45
45
  group: !0
46
46
  },
47
- formatter: (e) => JSON.stringify(e)
47
+ formatter: (o) => JSON.stringify(o)
48
48
  }
49
49
  ),
50
50
  /* @__PURE__ */ t(
51
- d,
51
+ l,
52
52
  {
53
53
  label: "classifica",
54
54
  description: "la classifica",
55
55
  collection: "students",
56
- suggestedName: `scoreboard-${i.id}.csv`,
57
- converter: m,
56
+ suggestedName: `scoreboard-${e.id}.csv`,
57
+ converter: u,
58
58
  options: {
59
- constraints: { contestId: i.id, disabled: !1 },
59
+ constraints: { contestId: e.id, disabled: !1 },
60
60
  group: !0
61
61
  },
62
- formatter: a,
63
- header: I(i)
62
+ formatter: d,
63
+ header: I(e)
64
64
  }
65
65
  )
66
66
  ] }) }) })
67
67
  ] });
68
68
  }
69
69
  function z() {
70
- const { contest: i } = u(), r = c("participations", {
70
+ const { contest: e } = m(), n = c("venues", {
71
71
  constraints: {
72
- contestId: i.id,
72
+ contestId: e.id,
73
73
  disabled: !1
74
74
  }
75
- }), a = c("participations", {
75
+ }), d = c("venues", {
76
76
  constraints: {
77
- contestId: i.id,
77
+ contestId: e.id,
78
78
  finalized: !0,
79
79
  disabled: !1
80
80
  }
81
- }), e = c("students", {
81
+ }), o = c("students", {
82
82
  constraints: {
83
- contestId: i.id,
83
+ contestId: e.id,
84
84
  disabled: !1
85
85
  },
86
86
  group: !0
87
87
  });
88
- return /* @__PURE__ */ o("div", { children: [
89
- /* @__PURE__ */ o("div", { children: [
88
+ return /* @__PURE__ */ i("div", { children: [
89
+ /* @__PURE__ */ i("div", { children: [
90
90
  "Scuole totali: ",
91
- r
91
+ n
92
92
  ] }),
93
- /* @__PURE__ */ o("div", { children: [
93
+ /* @__PURE__ */ i("div", { children: [
94
94
  "Scuole finalizzate: ",
95
- a
95
+ d
96
96
  ] }),
97
- /* @__PURE__ */ o("div", { children: [
97
+ /* @__PURE__ */ i("div", { children: [
98
98
  "Studenti totali: ",
99
- e
99
+ o
100
100
  ] })
101
101
  ] });
102
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","sources":["../../../../src/web/admin/dashboard.tsx"],"sourcesContent":["import { Suspense, useCallback } 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, { scoreboardFormatter, scoreboradHeader } from \"./export\";\n\nexport default function Dashboard() {\n const { contest, variants } = useAdmin();\n const contestScoreboardFormatter = useCallback(\n (student: Student) => scoreboardFormatter(contest, variants, student),\n [contest, variants],\n );\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=\"i dati di tutte le scuole\"\n collection=\"participations\"\n converter={participationConverter}\n options={{ constraints: { contestId: contest.id, disabled: false } }}\n formatter={(data: Participation) => JSON.stringify(data)}\n />\n <Export\n label=\"studenti\"\n description=\"i dati di tutti gli studenti\"\n collection=\"students\"\n converter={studentConverter}\n options={{\n constraints: { contestId: contest.id, disabled: false },\n group: true,\n }}\n formatter={(data: Student) => JSON.stringify(data)}\n />\n <Export\n label=\"classifica\"\n description=\"la classifica\"\n collection=\"students\"\n suggestedName={`scoreboard-${contest.id}.csv`}\n converter={studentConverter}\n options={{\n constraints: { contestId: contest.id, disabled: false },\n group: true,\n }}\n formatter={contestScoreboardFormatter}\n header={scoreboradHeader(contest)}\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","variants","useAdmin","contestScoreboardFormatter","useCallback","student","scoreboardFormatter","jsxs","jsx","Card","CardBody","ContestSettings","Announcements","Suspense","Loading","ContestInformation","CardActions","Link","Export","participationConverter","data","studentConverter","scoreboradHeader","totalSchools","useCount","finalizedSchools","totalStudents"],"mappings":";;;;;;;;;;;AAeA,SAAwBA,IAAY;AAClC,QAAM,EAAE,SAAAC,GAAS,UAAAC,EAAA,IAAaC,EAAA,GACxBC,IAA6BC;AAAA,IACjC,CAACC,MAAqBC,EAAoBN,GAASC,GAAUI,CAAO;AAAA,IACpE,CAACL,GAASC,CAAQ;AAAA,EAAA;AAGpB,SACE,gBAAAM,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,WAAWnB,EAAQ,IAAI,UAAU,KAAM;AAAA,UACjE,WAAW,CAACoB,MAAwB,KAAK,UAAUA,CAAI;AAAA,QAAA;AAAA,MAAA;AAAA,MAEzD,gBAAAZ;AAAA,QAACU;AAAA,QAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UACZ,YAAW;AAAA,UACX,WAAWG;AAAA,UACX,SAAS;AAAA,YACP,aAAa,EAAE,WAAWrB,EAAQ,IAAI,UAAU,GAAA;AAAA,YAChD,OAAO;AAAA,UAAA;AAAA,UAET,WAAW,CAACoB,MAAkB,KAAK,UAAUA,CAAI;AAAA,QAAA;AAAA,MAAA;AAAA,MAEnD,gBAAAZ;AAAA,QAACU;AAAA,QAAA;AAAA,UACC,OAAM;AAAA,UACN,aAAY;AAAA,UACZ,YAAW;AAAA,UACX,eAAe,cAAclB,EAAQ,EAAE;AAAA,UACvC,WAAWqB;AAAA,UACX,SAAS;AAAA,YACP,aAAa,EAAE,WAAWrB,EAAQ,IAAI,UAAU,GAAA;AAAA,YAChD,OAAO;AAAA,UAAA;AAAA,UAET,WAAWG;AAAA,UACX,QAAQmB,EAAiBtB,CAAO;AAAA,QAAA;AAAA,MAAA;AAAA,IAClC,EAAA,CACF,GACF,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;AAEA,SAASe,IAAqB;AAC5B,QAAM,EAAE,SAAAf,EAAA,IAAYE,EAAA,GAEdqB,IAAeC,EAAwB,kBAAkB;AAAA,IAC7D,aAAa;AAAA,MACX,WAAWxB,EAAQ;AAAA,MACnB,UAAU;AAAA,IAAA;AAAA,EACZ,CACD,GAEKyB,IAAmBD,EAAwB,kBAAkB;AAAA,IACjE,aAAa;AAAA,MACX,WAAWxB,EAAQ;AAAA,MACnB,WAAW;AAAA,MACX,UAAU;AAAA,IAAA;AAAA,EACZ,CACD,GAEK0B,IAAgBF,EAAkB,YAAY;AAAA,IAClD,aAAa;AAAA,MACX,WAAWxB,EAAQ;AAAA,MACnB,UAAU;AAAA,IAAA;AAAA,IAEZ,OAAO;AAAA,EAAA,CACR;AAED,2BACG,OAAA,EACC,UAAA;AAAA,IAAA,gBAAAO,EAAC,OAAA,EAAI,UAAA;AAAA,MAAA;AAAA,MAAgBgB;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;"}
1
+ {"version":3,"file":"dashboard.js","sources":["../../../../src/web/admin/dashboard.tsx"],"sourcesContent":["import { Suspense, useCallback } from \"react\";\n\nimport { Loading } from \"@olinfo/quizms/components\";\nimport type { Student, Venue } from \"@olinfo/quizms/models\";\nimport { Card, CardActions, CardBody } from \"@olinfo/react-components\";\nimport { Link } from \"wouter\";\n\nimport { studentConverter, venueConverter } 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, { scoreboardFormatter, scoreboradHeader } from \"./export\";\n\nexport default function Dashboard() {\n const { contest, variants } = useAdmin();\n const contestScoreboardFormatter = useCallback(\n (student: Student) => scoreboardFormatter(contest, variants, student),\n [contest, variants],\n );\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=\"i dati di tutte le scuole\"\n collection=\"venues\"\n converter={venueConverter}\n options={{ constraints: { contestId: contest.id, disabled: false } }}\n formatter={(data: Venue) => JSON.stringify(data)}\n />\n <Export\n label=\"studenti\"\n description=\"i dati di tutti gli studenti\"\n collection=\"students\"\n converter={studentConverter}\n options={{\n constraints: { contestId: contest.id, disabled: false },\n group: true,\n }}\n formatter={(data: Student) => JSON.stringify(data)}\n />\n <Export\n label=\"classifica\"\n description=\"la classifica\"\n collection=\"students\"\n suggestedName={`scoreboard-${contest.id}.csv`}\n converter={studentConverter}\n options={{\n constraints: { contestId: contest.id, disabled: false },\n group: true,\n }}\n formatter={contestScoreboardFormatter}\n header={scoreboradHeader(contest)}\n />\n </CardActions>\n </CardBody>\n </Card>\n </div>\n );\n}\n\nfunction ContestInformation() {\n const { contest } = useAdmin();\n\n const totalSchools = useCount<Venue>(\"venues\", {\n constraints: {\n contestId: contest.id,\n disabled: false,\n },\n });\n\n const finalizedSchools = useCount<Venue>(\"venues\", {\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","variants","useAdmin","contestScoreboardFormatter","useCallback","student","scoreboardFormatter","jsxs","jsx","Card","CardBody","ContestSettings","Announcements","Suspense","Loading","ContestInformation","CardActions","Link","Export","venueConverter","data","studentConverter","scoreboradHeader","totalSchools","useCount","finalizedSchools","totalStudents"],"mappings":"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"}
@@ -1,15 +1,15 @@
1
1
  import { jsxs as d, Fragment as h, jsx as p } from "react/jsx-runtime";
2
- import { useRef as g, forwardRef as v } from "react";
3
- import { formatUserData as x, unshuffleAnswer as $, calcProblemPoints as I } from "@olinfo/quizms/models";
4
- import { Button as w, Modal as y } from "@olinfo/react-components";
2
+ import { useRef as v } from "react";
3
+ import { formatUserData as g, displayAnswer as I, unshuffleAnswer as x, calcProblemPoints as y } from "@olinfo/quizms/models";
4
+ import { Button as w, Modal as $ } from "@olinfo/react-components";
5
5
  import { formatISO as E } from "date-fns";
6
- import { getDocs as S } from "firebase/firestore";
6
+ import { getDocs as A } from "firebase/firestore";
7
7
  import { TriangleAlert as P } from "lucide-react";
8
- import { useDb as j } from "../common/base-login.js";
9
- import k from "../common/query.js";
10
- import { useAdmin as A } from "./context.js";
11
- function Q(s) {
12
- const o = g(null);
8
+ import { useDb as S } from "../common/base-login.js";
9
+ import j from "../common/query.js";
10
+ import { useAdmin as k } from "./context.js";
11
+ function R(s) {
12
+ const o = v(null);
13
13
  return /* @__PURE__ */ d(h, { children: [
14
14
  /* @__PURE__ */ d(w, { className: "btn-primary", onClick: () => o.current?.showModal(), children: [
15
15
  "Esporta ",
@@ -18,33 +18,34 @@ function Q(s) {
18
18
  /* @__PURE__ */ p(D, { ref: o, ...s })
19
19
  ] });
20
20
  }
21
- const D = v(function({
22
- label: o,
23
- description: e,
24
- collection: r,
25
- converter: l,
26
- options: a,
27
- formatter: t,
28
- suggestedName: n,
29
- header: c
30
- }, i) {
31
- const { contest: f } = A(), u = j(), m = async () => {
32
- await N(u, r, l, a, t, n, c), i && "current" in i && i.current?.close();
21
+ function D({
22
+ label: s,
23
+ description: o,
24
+ collection: e,
25
+ converter: r,
26
+ options: c,
27
+ formatter: a,
28
+ suggestedName: t,
29
+ header: n,
30
+ ref: i
31
+ }) {
32
+ const { contest: l } = k(), m = S(), u = async () => {
33
+ await N(m, e, r, c, a, t, n), i.current?.close();
33
34
  };
34
- return /* @__PURE__ */ p(y, { ref: i, title: `Esporta ${o}`, children: window.showSaveFilePicker === void 0 ? /* @__PURE__ */ d("div", { className: "flex items-center gap-3", children: [
35
+ return /* @__PURE__ */ p($, { ref: i, title: `Esporta ${s}`, children: window.showSaveFilePicker === void 0 ? /* @__PURE__ */ d("div", { className: "flex items-center gap-3", children: [
35
36
  /* @__PURE__ */ p(P, { size: 20, className: "flex-none text-warning" }),
36
37
  /* @__PURE__ */ p("span", { children: "Il tuo browser non supporta l'esportazione dei dati. Usa un browser Chrome o Edge." })
37
38
  ] }) : /* @__PURE__ */ d(h, { children: [
38
39
  /* @__PURE__ */ d("div", { children: [
39
40
  "Stai per esportare ",
40
- e,
41
+ o,
41
42
  " della gara ",
42
- /* @__PURE__ */ p("b", { children: f.name }),
43
+ /* @__PURE__ */ p("b", { children: l.name }),
43
44
  ". Questa operazione esegue un grande numero di query al database che richiederà del tempo e potrà incidere sulla fatturazione del progetto."
44
45
  ] }),
45
- /* @__PURE__ */ p("div", { className: "mt-4 flex flex-wrap justify-center gap-2", children: /* @__PURE__ */ p(w, { className: "btn-primary", onClick: m, children: "Esporta" }) })
46
+ /* @__PURE__ */ p("div", { className: "mt-4 flex flex-wrap justify-center gap-2", children: /* @__PURE__ */ p(w, { className: "btn-primary", onClick: u, children: "Esporta" }) })
46
47
  ] }) });
47
- });
48
+ }
48
49
  function T(s) {
49
50
  const o = [];
50
51
  for (const e of s.userData)
@@ -62,50 +63,53 @@ function V(s, o, e) {
62
63
  const t = e.userData?.[a.name];
63
64
  t ? r.push(E(t, { representation: "date" })) : r.push("");
64
65
  } else
65
- r.push(x(e, a));
66
- r.push(e.participationId?.split("-")[0] ?? ""), r.push(e.variant ?? "");
67
- const l = {};
68
- if (e.answers !== void 0 && e.variant !== void 0 && o[e.variant] !== void 0) {
69
- const a = o[e.variant].schema;
66
+ r.push(g(e, a));
67
+ r.push(e.venueId?.split("-")[0] ?? ""), r.push(e.variantId ?? "");
68
+ const c = {};
69
+ if (e.answers !== void 0 && e.variantId !== void 0 && o[e.variantId] !== void 0) {
70
+ const a = o[e.variantId].schema;
70
71
  for (const t in a) {
71
- const n = a[t], c = n.originalId, i = e.answers[t];
72
- l[c] = [
73
- $(n, i) ?? "",
74
- I(n, i)
72
+ const n = a[t], i = n.originalId, l = e.answers[t];
73
+ c[i] = [
74
+ l?.value ? I({
75
+ type: l.type,
76
+ value: x(n, l.value)
77
+ }) : "",
78
+ y(n, l)
75
79
  ];
76
80
  }
77
81
  }
78
82
  for (const a of s.problemIds)
79
- if (l[a] !== void 0) {
80
- const [t, n] = l[a];
83
+ if (c[a] !== void 0) {
84
+ const [t, n] = c[a];
81
85
  r.push(`${t}`), r.push(`${n}`);
82
86
  } else
83
87
  r.push(""), r.push("");
84
88
  return r.push(`${e.score ?? ""}`), r.push(`${e.absent ? 1 : 0}`), r.map((a) => `"${a}"`).join(",");
85
89
  }
86
- async function N(s, o, e, r, l, a, t) {
87
- const c = await (await window.showSaveFilePicker({
90
+ async function N(s, o, e, r, c, a, t) {
91
+ const i = await (await window.showSaveFilePicker({
88
92
  suggestedName: a ?? `${o}.jsonl`
89
93
  })).createWritable();
90
- t !== void 0 && await c.write(t);
91
- const i = 1e4;
92
- let f;
94
+ t !== void 0 && await i.write(t);
95
+ const l = 1e4;
96
+ let m;
93
97
  for (; ; ) {
94
- const u = k(s, o, e, {
98
+ const u = j(s, o, e, {
95
99
  ...r,
96
- limit: i,
97
- startAfter: f
98
- }), m = await S(u);
99
- if (m.empty) break;
100
- for (const b of m.docs)
101
- await c.write(`${l(b.data())}
100
+ limit: l,
101
+ startAfter: m
102
+ }), f = await A(u);
103
+ if (f.empty) break;
104
+ for (const b of f.docs)
105
+ await i.write(`${c(b.data())}
102
106
  `);
103
- f = m.docs.at(-1);
107
+ m = f.docs.at(-1);
104
108
  }
105
- await c.close();
109
+ await i.close();
106
110
  }
107
111
  export {
108
- Q as default,
112
+ R as default,
109
113
  V as scoreboardFormatter,
110
114
  T as scoreboradHeader
111
115
  };
@@ -1 +1 @@
1
- {"version":3,"file":"export.js","sources":["../../../../src/web/admin/export.tsx"],"sourcesContent":["import { forwardRef, type Ref, useRef } from \"react\";\n\nimport {\n type Answer,\n type Contest,\n calcProblemPoints,\n formatUserData,\n type Student,\n unshuffleAnswer,\n type Variant,\n} from \"@olinfo/quizms/models\";\nimport { Button, Modal } from \"@olinfo/react-components\";\nimport { formatISO } from \"date-fns\";\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 suggestedName?: string;\n formatter: (data: T) => string;\n header?: string;\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 {\n label,\n description,\n collection,\n converter,\n options,\n formatter,\n suggestedName,\n header,\n }: 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, formatter, suggestedName, header);\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 {description} della gara <b>{contest.name}</b>. Questa operazione\n esegue un grande numero di query al database che richiederà del tempo e potrà incidere\n 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\nexport function scoreboradHeader(contest: Contest) {\n const row: string[] = [];\n for (const field of contest.userData) {\n row.push(field.label);\n }\n row.push(\"Scuola\", \"Variante\");\n for (const problemId of contest.problemIds) {\n row.push(`${problemId}A`, `${problemId}P`);\n }\n row.push(\"Punteggio\");\n row.push(\"Assente\");\n return `${row.map((s) => `\"${s}\"`).join(\",\")}\\n`;\n}\n\n// TODO: move logic to @olinfo/quizms\nexport function scoreboardFormatter(\n contest: Contest,\n variants: Record<string, Variant>,\n student: Student,\n) {\n const row: string[] = [];\n for (const field of contest.userData) {\n if (field.type === \"date\") {\n const value = student.userData?.[field.name];\n if (value) {\n row.push(formatISO(value, { representation: \"date\" }));\n } else {\n row.push(\"\");\n }\n } else {\n row.push(formatUserData(student, field));\n }\n }\n row.push(student.participationId?.split(\"-\")[0] ?? \"\");\n row.push(student.variant ?? \"\");\n const unshuflledProblems: Record<string, [Answer, number]> = {};\n if (\n student.answers !== undefined &&\n student.variant !== undefined &&\n variants[student.variant] !== undefined\n ) {\n const schema = variants[student.variant].schema;\n for (const problemId in schema) {\n const problem = schema[problemId];\n const originalId = problem.originalId;\n const answer = student.answers[problemId];\n unshuflledProblems[originalId] = [\n unshuffleAnswer(problem, answer) ?? \"\",\n calcProblemPoints(problem, answer),\n ];\n }\n }\n for (const problemId of contest.problemIds) {\n if (unshuflledProblems[problemId] !== undefined) {\n const [answer, points] = unshuflledProblems[problemId];\n row.push(`${answer}`);\n row.push(`${points}`);\n } else {\n row.push(\"\");\n row.push(\"\");\n }\n }\n row.push(`${student.score ?? \"\"}`);\n row.push(`${student.absent ? 1 : 0}`);\n return row.map((s) => `\"${s}\"`).join(\",\");\n}\n\nasync function saveExport<T>(\n db: Firestore,\n collection: string,\n converter: FirestoreDataConverter<T>,\n options: QueryOption<T>,\n formatter: (data: T) => string,\n suggestedName: string | undefined,\n header?: string,\n) {\n const handle = await window.showSaveFilePicker({\n suggestedName: suggestedName ?? `${collection}.jsonl`,\n });\n\n const writableStream = await handle.createWritable();\n\n if (header !== undefined) {\n await writableStream.write(header);\n }\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(`${formatter(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","formatter","suggestedName","header","contest","useAdmin","db","useDb","onExport","saveExport","Modal","TriangleAlert","scoreboradHeader","row","field","problemId","s","scoreboardFormatter","variants","student","value","formatISO","formatUserData","unshuflledProblems","schema","problem","originalId","answer","unshuffleAnswer","calcProblemPoints","points","writableStream","chunkSize","last","q","query","snapshot","getDocs","doc"],"mappings":"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"}
1
+ {"version":3,"file":"export.js","sources":["../../../../src/web/admin/export.tsx"],"sourcesContent":["import { type RefObject, useRef } from \"react\";\n\nimport {\n type Contest,\n calcProblemPoints,\n displayAnswer,\n formatUserData,\n type Student,\n unshuffleAnswer,\n type Variant,\n} from \"@olinfo/quizms/models\";\nimport { Button, Modal } from \"@olinfo/react-components\";\nimport { formatISO } from \"date-fns\";\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 suggestedName?: string;\n formatter: (data: T) => string;\n header?: string;\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\nfunction ExportModal({\n label,\n description,\n collection,\n converter,\n options,\n formatter,\n suggestedName,\n header,\n ref,\n}: Props<any> & { ref: RefObject<HTMLDialogElement | null> }) {\n const { contest } = useAdmin();\n const db = useDb();\n\n const onExport = async () => {\n await saveExport(db, collection, converter, options, formatter, suggestedName, header);\n ref.current?.close();\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 {description} della gara <b>{contest.name}</b>. Questa operazione\n esegue un grande numero di query al database che richiederà del tempo e potrà incidere\n 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\nexport function scoreboradHeader(contest: Contest) {\n const row: string[] = [];\n for (const field of contest.userData) {\n row.push(field.label);\n }\n row.push(\"Scuola\", \"Variante\");\n for (const problemId of contest.problemIds) {\n row.push(`${problemId}A`, `${problemId}P`);\n }\n row.push(\"Punteggio\");\n row.push(\"Assente\");\n return `${row.map((s) => `\"${s}\"`).join(\",\")}\\n`;\n}\n\n// TODO: move logic to @olinfo/quizms\nexport function scoreboardFormatter(\n contest: Contest,\n variants: Record<string, Variant>,\n student: Student,\n) {\n const row: string[] = [];\n for (const field of contest.userData) {\n if (field.type === \"date\") {\n const value = student.userData?.[field.name];\n if (value) {\n row.push(formatISO(value, { representation: \"date\" }));\n } else {\n row.push(\"\");\n }\n } else {\n row.push(formatUserData(student, field));\n }\n }\n row.push(student.venueId?.split(\"-\")[0] ?? \"\");\n row.push(student.variantId ?? \"\");\n const unshuflledProblems: Record<string, [string, number]> = {};\n if (\n student.answers !== undefined &&\n student.variantId !== undefined &&\n variants[student.variantId] !== undefined\n ) {\n const schema = variants[student.variantId].schema;\n for (const problemId in schema) {\n const problem = schema[problemId];\n const originalId = problem.originalId;\n const answer = student.answers[problemId];\n unshuflledProblems[originalId] = [\n answer?.value\n ? displayAnswer({\n type: answer.type,\n value: unshuffleAnswer(problem, answer.value) as any,\n })\n : \"\",\n calcProblemPoints(problem, answer),\n ];\n }\n }\n for (const problemId of contest.problemIds) {\n if (unshuflledProblems[problemId] !== undefined) {\n const [answer, points] = unshuflledProblems[problemId];\n row.push(`${answer}`);\n row.push(`${points}`);\n } else {\n row.push(\"\");\n row.push(\"\");\n }\n }\n row.push(`${student.score ?? \"\"}`);\n row.push(`${student.absent ? 1 : 0}`);\n return row.map((s) => `\"${s}\"`).join(\",\");\n}\n\nasync function saveExport<T>(\n db: Firestore,\n collection: string,\n converter: FirestoreDataConverter<T>,\n options: QueryOption<T>,\n formatter: (data: T) => string,\n suggestedName: string | undefined,\n header?: string,\n) {\n const handle = await window.showSaveFilePicker({\n suggestedName: suggestedName ?? `${collection}.jsonl`,\n });\n\n const writableStream = await handle.createWritable();\n\n if (header !== undefined) {\n await writableStream.write(header);\n }\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(`${formatter(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","label","description","collection","converter","options","formatter","suggestedName","header","contest","useAdmin","db","useDb","onExport","saveExport","Modal","TriangleAlert","scoreboradHeader","row","field","problemId","s","scoreboardFormatter","variants","student","value","formatISO","formatUserData","unshuflledProblems","schema","problem","originalId","answer","displayAnswer","unshuffleAnswer","calcProblemPoints","points","writableStream","chunkSize","last","q","query","snapshot","getDocs","doc"],"mappings":"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"}