@mhosaic/feedback 0.18.1 → 0.20.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.
@@ -169,6 +169,24 @@ function createApiClient(options) {
169
169
  }
170
170
  return response.json();
171
171
  }
172
+ async function reopenUnresolved(reportId, externalId) {
173
+ const response = await fetcher(
174
+ `${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
175
+ {
176
+ method: "PATCH",
177
+ headers: {
178
+ ...widgetHeaders(externalId),
179
+ "Content-Type": "application/json"
180
+ },
181
+ body: JSON.stringify({ status: "in_progress" })
182
+ }
183
+ );
184
+ if (!response.ok) {
185
+ const text = await response.text().catch(() => "");
186
+ throw new Error(`reopenUnresolved failed: ${response.status} ${text}`);
187
+ }
188
+ return response.json();
189
+ }
172
190
  function boardQueryString(filters) {
173
191
  if (!filters) return "";
174
192
  const params = new URLSearchParams();
@@ -177,6 +195,7 @@ function createApiClient(options) {
177
195
  filters.severity?.forEach((s) => params.append("severity", s));
178
196
  if (filters.q) params.set("q", filters.q);
179
197
  if (filters.mine) params.set("mine", "1");
198
+ if (filters.ordering) params.set("ordering", filters.ordering);
180
199
  if (filters.page && filters.page > 1) params.set("page", String(filters.page));
181
200
  const qs = params.toString();
182
201
  return qs ? `?${qs}` : "";
@@ -238,6 +257,7 @@ function createApiClient(options) {
238
257
  getReport,
239
258
  addComment,
240
259
  closeAsResolved,
260
+ reopenUnresolved,
241
261
  listBoard,
242
262
  fetchBoardKpis
243
263
  };
@@ -585,6 +605,12 @@ var DEFAULT_STRINGS = {
585
605
  "board.kpi.closed": "Closed",
586
606
  "board.kpi.rejected": "Rejected",
587
607
  "board.kpi.resolution_rate": "Resolution rate",
608
+ "board.sort": "Sort",
609
+ "board.sort.recent": "Newest first",
610
+ "board.sort.oldest": "Oldest first",
611
+ "board.sort.updated": "Recently active",
612
+ "board.sort.severity": "Severity",
613
+ "board.sort.status": "Status",
588
614
  "board.filter.status": "Status",
589
615
  "board.filter.type": "Type",
590
616
  "board.filter.severity": "Severity",
@@ -594,7 +620,8 @@ var DEFAULT_STRINGS = {
594
620
  "board.list.empty.title": "Nothing here yet",
595
621
  "board.list.empty.description": "When reports land, they\u2019ll show up here.",
596
622
  "board.list.loading": "Loading\u2026",
597
- "board.list.error": "Couldn\u2019t load reports. Try again.",
623
+ "board.list.error": "Couldn\u2019t load reports.",
624
+ "board.retry": "Try again",
598
625
  "board.list.you": "you",
599
626
  "board.list.count": "{n} of {total}",
600
627
  "board.detail.empty": "Pick a report on the left to see its full thread.",
@@ -629,12 +656,15 @@ var DEFAULT_STRINGS = {
629
656
  "detail.compose_sending": "Sending\u2026",
630
657
  "detail.close_cta": "Mark as resolved",
631
658
  "detail.close_busy": "Marking\u2026",
659
+ "detail.reopen_cta": "Still not fixed",
660
+ "detail.reopen_busy": "Reopening\u2026",
632
661
  "detail.teammate_notice": "Viewing a teammate\u2019s report \u2014 replies are private to the submitter.",
633
662
  "detail.load_failed.title": "Report not available",
634
663
  "detail.load_failed.body": "It may have been deleted, or you no longer have access to it.",
635
664
  "detail.load_failed.cta": "Back",
636
665
  "detail.send_failed": "Couldn\u2019t send your reply. Try again.",
637
666
  "detail.close_failed": "Couldn\u2019t mark as resolved. Try again.",
667
+ "detail.reopen_failed": "Couldn\u2019t reopen the report. Try again.",
638
668
  "detail.history": "Status history",
639
669
  "detail.context.submitted_at": "Submitted",
640
670
  "detail.context.page": "Page",
@@ -724,6 +754,12 @@ var FRENCH_STRINGS = {
724
754
  "board.kpi.closed": "Ferm\xE9",
725
755
  "board.kpi.rejected": "Refus\xE9",
726
756
  "board.kpi.resolution_rate": "Taux de r\xE9solution",
757
+ "board.sort": "Trier",
758
+ "board.sort.recent": "Plus r\xE9cents",
759
+ "board.sort.oldest": "Plus anciens",
760
+ "board.sort.updated": "Activit\xE9 r\xE9cente",
761
+ "board.sort.severity": "S\xE9v\xE9rit\xE9",
762
+ "board.sort.status": "Statut",
727
763
  "board.filter.status": "Statut",
728
764
  "board.filter.type": "Type",
729
765
  "board.filter.severity": "S\xE9v\xE9rit\xE9",
@@ -733,7 +769,8 @@ var FRENCH_STRINGS = {
733
769
  "board.list.empty.title": "Rien \xE0 voir ici",
734
770
  "board.list.empty.description": "Les rapports appara\xEEtront ici d\xE8s qu\u2019ils arrivent.",
735
771
  "board.list.loading": "Chargement\u2026",
736
- "board.list.error": "Impossible de charger les rapports. R\xE9essayez.",
772
+ "board.list.error": "Impossible de charger les rapports.",
773
+ "board.retry": "R\xE9essayer",
737
774
  "board.list.you": "vous",
738
775
  "board.list.count": "{n} sur {total}",
739
776
  "board.detail.empty": "S\xE9lectionnez un rapport \xE0 gauche pour voir le fil complet.",
@@ -768,12 +805,15 @@ var FRENCH_STRINGS = {
768
805
  "detail.compose_sending": "Envoi\u2026",
769
806
  "detail.close_cta": "Marquer comme r\xE9solu",
770
807
  "detail.close_busy": "Validation\u2026",
808
+ "detail.reopen_cta": "Toujours pas r\xE9gl\xE9",
809
+ "detail.reopen_busy": "R\xE9ouverture\u2026",
771
810
  "detail.teammate_notice": "Vous consultez le rapport d\u2019un co\xE9quipier \u2014 les r\xE9ponses sont priv\xE9es au soumetteur.",
772
811
  "detail.load_failed.title": "Rapport indisponible",
773
812
  "detail.load_failed.body": "Il a peut-\xEAtre \xE9t\xE9 supprim\xE9, ou vous n\u2019y avez plus acc\xE8s.",
774
813
  "detail.load_failed.cta": "Retour",
775
814
  "detail.send_failed": "Impossible d\u2019envoyer votre r\xE9ponse. R\xE9essayez.",
776
815
  "detail.close_failed": "Impossible de marquer comme r\xE9solu. R\xE9essayez.",
816
+ "detail.reopen_failed": "Impossible de rouvrir le rapport. R\xE9essayez.",
777
817
  "detail.history": "Historique du statut",
778
818
  "detail.context.submitted_at": "Envoy\xE9",
779
819
  "detail.context.page": "Page",
@@ -828,6 +868,25 @@ import { useCallback, useEffect as useEffect8, useState as useState7 } from "pre
828
868
  // src/widget/BoardView.tsx
829
869
  import { useEffect as useEffect2, useMemo, useState as useState2 } from "preact/hooks";
830
870
 
871
+ // src/widget/boardViewStore.ts
872
+ var PREFIX = "mhosaic.boardView.";
873
+ function loadBoardView(externalId) {
874
+ try {
875
+ const raw = localStorage.getItem(PREFIX + externalId);
876
+ if (!raw) return {};
877
+ const parsed = JSON.parse(raw);
878
+ return parsed && typeof parsed === "object" ? parsed : {};
879
+ } catch {
880
+ return {};
881
+ }
882
+ }
883
+ function saveBoardView(externalId, view) {
884
+ try {
885
+ localStorage.setItem(PREFIX + externalId, JSON.stringify(view));
886
+ } catch {
887
+ }
888
+ }
889
+
831
890
  // src/widget/ReportDetailView.tsx
832
891
  import { useEffect, useRef, useState } from "preact/hooks";
833
892
 
@@ -913,6 +972,7 @@ function ReportDetailView({
913
972
  const [composeBody, setComposeBody] = useState("");
914
973
  const [sending, setSending] = useState(false);
915
974
  const [closing, setClosing] = useState(false);
975
+ const [reopening, setReopening] = useState(false);
916
976
  const mountedRef = useRef(true);
917
977
  const fetchDetail = async () => {
918
978
  try {
@@ -958,7 +1018,7 @@ function ReportDetailView({
958
1018
  }
959
1019
  };
960
1020
  const handleClose = async () => {
961
- if (closing) return;
1021
+ if (closing || reopening) return;
962
1022
  setClosing(true);
963
1023
  try {
964
1024
  const next = await api.closeAsResolved(reportId, externalId);
@@ -973,6 +1033,22 @@ function ReportDetailView({
973
1033
  if (mountedRef.current) setClosing(false);
974
1034
  }
975
1035
  };
1036
+ const handleReopen = async () => {
1037
+ if (closing || reopening) return;
1038
+ setReopening(true);
1039
+ try {
1040
+ const next = await api.reopenUnresolved(reportId, externalId);
1041
+ if (!mountedRef.current) return;
1042
+ setDetail(next);
1043
+ } catch (err) {
1044
+ if (typeof console !== "undefined")
1045
+ console.warn("[mhosaic] reopenUnresolved:", err);
1046
+ if (!mountedRef.current) return;
1047
+ setError(strings["detail.reopen_failed"]);
1048
+ } finally {
1049
+ if (mountedRef.current) setReopening(false);
1050
+ }
1051
+ };
976
1052
  if (!detail && !error) {
977
1053
  return /* @__PURE__ */ jsx2("div", { class: "mine-loading", children: strings["mine.loading"] });
978
1054
  }
@@ -1050,10 +1126,20 @@ function ReportDetailView({
1050
1126
  type: "button",
1051
1127
  class: "btn",
1052
1128
  onClick: handleClose,
1053
- disabled: closing,
1129
+ disabled: closing || reopening,
1054
1130
  children: closing ? strings["detail.close_busy"] : strings["detail.close_cta"]
1055
1131
  }
1056
1132
  ),
1133
+ showCloseCta && /* @__PURE__ */ jsx2(
1134
+ "button",
1135
+ {
1136
+ type: "button",
1137
+ class: "btn btn--ghost",
1138
+ onClick: handleReopen,
1139
+ disabled: closing || reopening,
1140
+ children: reopening ? strings["detail.reopen_busy"] : strings["detail.reopen_cta"]
1141
+ }
1142
+ ),
1057
1143
  /* @__PURE__ */ jsx2(
1058
1144
  "button",
1059
1145
  {
@@ -1239,6 +1325,7 @@ function StatusHistorySection({ rows, strings }) {
1239
1325
  // src/widget/BoardView.tsx
1240
1326
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
1241
1327
  var POLL_MS2 = 3e4;
1328
+ var SORT_OPTIONS = ["recent", "oldest", "updated", "severity", "status"];
1242
1329
  var STATUSES = [
1243
1330
  "new",
1244
1331
  "in_progress",
@@ -1249,13 +1336,17 @@ var STATUSES = [
1249
1336
  var TYPES = ["bug", "feature", "question", "praise", "typo"];
1250
1337
  var SEVERITIES = ["blocker", "high", "medium", "low"];
1251
1338
  function BoardView({ api, externalId, strings }) {
1252
- const [filters, setFilters] = useState2({});
1339
+ const [filters, setFilters] = useState2(() => loadBoardView(externalId));
1340
+ useEffect2(() => {
1341
+ saveBoardView(externalId, filters);
1342
+ }, [externalId, filtersHash(filters)]);
1253
1343
  const [page, setPage] = useState2(null);
1254
1344
  const [kpis, setKpis] = useState2(null);
1255
1345
  const [loading, setLoading] = useState2(true);
1256
1346
  const [error, setError] = useState2(null);
1257
1347
  const [selectedId, setSelectedId] = useState2(null);
1258
1348
  const [detailKey, setDetailKey] = useState2(0);
1349
+ const [reloadTick, setReloadTick] = useState2(0);
1259
1350
  const activeFilterCount = useMemo(() => {
1260
1351
  return (filters.status?.length ?? 0) + (filters.type?.length ?? 0) + (filters.severity?.length ?? 0) + (filters.q ? 1 : 0) + (filters.mine ? 1 : 0);
1261
1352
  }, [filters]);
@@ -1286,7 +1377,7 @@ function BoardView({ api, externalId, strings }) {
1286
1377
  cancelled = true;
1287
1378
  if (timer) clearTimeout(timer);
1288
1379
  };
1289
- }, [api, externalId, filtersHash(filters)]);
1380
+ }, [api, externalId, filtersHash(filters), reloadTick]);
1290
1381
  const selectedRow = useMemo(() => {
1291
1382
  if (!selectedId || !page) return null;
1292
1383
  return page.results.find((r) => r.id === selectedId) ?? null;
@@ -1308,7 +1399,22 @@ function BoardView({ api, externalId, strings }) {
1308
1399
  ),
1309
1400
  /* @__PURE__ */ jsxs3("div", { class: "board-body", children: [
1310
1401
  /* @__PURE__ */ jsxs3("div", { class: "board-list-wrap", "aria-busy": loading && !page, children: [
1311
- error && /* @__PURE__ */ jsx3("div", { class: "board-error", children: strings["board.list.error"] }),
1402
+ error && /* @__PURE__ */ jsxs3("div", { class: "board-error", role: "alert", children: [
1403
+ /* @__PURE__ */ jsx3("span", { children: strings["board.list.error"] }),
1404
+ /* @__PURE__ */ jsx3(
1405
+ "button",
1406
+ {
1407
+ type: "button",
1408
+ class: "btn btn--ghost",
1409
+ onClick: () => {
1410
+ setError(null);
1411
+ setLoading(true);
1412
+ setReloadTick((t) => t + 1);
1413
+ },
1414
+ children: strings["board.retry"]
1415
+ }
1416
+ )
1417
+ ] }),
1312
1418
  !error && loading && !page && /* @__PURE__ */ jsx3(BoardListSkeleton, {}),
1313
1419
  !error && page && page.results.length === 0 && !loading && /* @__PURE__ */ jsx3(BoardEmpty, { strings }),
1314
1420
  !error && page && page.results.length > 0 && /* @__PURE__ */ jsx3(
@@ -1425,6 +1531,7 @@ function BoardFilters({
1425
1531
  onChange({ ...filters, severity: [value] });
1426
1532
  }
1427
1533
  };
1534
+ const setOrdering = (value) => onChange({ ...filters, ordering: value });
1428
1535
  const setSearch = (q) => onChange({ ...filters, q });
1429
1536
  const toggleMine = () => {
1430
1537
  if (filters.mine) {
@@ -1437,6 +1544,16 @@ function BoardFilters({
1437
1544
  };
1438
1545
  const clear = () => onChange({});
1439
1546
  return /* @__PURE__ */ jsxs3("div", { class: "board-filters", children: [
1547
+ /* @__PURE__ */ jsx3(
1548
+ "select",
1549
+ {
1550
+ class: "board-filter-select",
1551
+ "aria-label": strings["board.sort"],
1552
+ value: filters.ordering ?? "recent",
1553
+ onChange: (e) => setOrdering(e.target.value),
1554
+ children: SORT_OPTIONS.map((o) => /* @__PURE__ */ jsx3("option", { value: o, children: strings[`board.sort.${o}`] }, o))
1555
+ }
1556
+ ),
1440
1557
  /* @__PURE__ */ jsxs3(
1441
1558
  "select",
1442
1559
  {
@@ -1600,7 +1717,8 @@ function filtersHash(f) {
1600
1717
  t: f.type?.slice().sort(),
1601
1718
  sv: f.severity?.slice().sort(),
1602
1719
  q: f.q ?? "",
1603
- m: Boolean(f.mine)
1720
+ m: Boolean(f.mine),
1721
+ o: f.ordering ?? ""
1604
1722
  });
1605
1723
  }
1606
1724
  function formatRelative(iso) {
@@ -4759,8 +4877,8 @@ function createFeedback(config) {
4759
4877
  capture_method: captureMethod,
4760
4878
  technical_context
4761
4879
  };
4762
- if ("0.18.1") {
4763
- payload.widget_version = "0.18.1";
4880
+ if ("0.20.0") {
4881
+ payload.widget_version = "0.20.0";
4764
4882
  }
4765
4883
  if (manualScreenshot) payload.screenshot = manualScreenshot;
4766
4884
  if (values.synthetic) payload.synthetic = true;
@@ -4853,4 +4971,4 @@ function createFeedback(config) {
4853
4971
  export {
4854
4972
  createFeedback
4855
4973
  };
4856
- //# sourceMappingURL=chunk-5FVEQYA5.mjs.map
4974
+ //# sourceMappingURL=chunk-YV7LMW22.mjs.map