@translationstudio/translationstudio-strapi-extension 4.3.0 → 5.0.1

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.
@@ -2,8 +2,8 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { Modal, Typography, Button, Box, Alert, TextInput, Table, Thead, Tr, Th, Tbody, Td, Badge, SimpleMenu, IconButton, MenuItem, Radio, Checkbox, DatePicker, ProgressBar, Main, Grid, Flex } from "@strapi/design-system";
3
3
  import { useState, useEffect, useMemo } from "react";
4
4
  import { getFetchClient } from "@strapi/strapi/admin";
5
- import { g as getThemeColors, a as groupHistoryData, G as GetStatusColor, b as GetStatusText, f as formatDate, h as handleHistoryResponse, u as useThemeMode, c as getStoredEmail, d as getSubmitLabel, s as setStoredEmail, v as validateDueDate, e as createEntryUid, i as determineEntryName, j as createTranslationPayload, k as createSuccessMessage, l as createErrorMessage, m as createGeneralErrorMessage, n as getElementStatus, T as TranslationstudioLogo } from "./index-Csx0xlBL.mjs";
6
- import { Trash, More, Bell } from "@strapi/icons";
5
+ import { g as getThemeColors, G as GetStatusColor, a as GetStatusText, f as formatDate, b as getStoredEmail, c as getSubmitLabel, s as setStoredEmail, v as validateDueDate, d as createEntryUid, e as determineEntryName, h as createTranslationPayload, i as createSuccessMessage, j as createErrorMessage, k as createGeneralErrorMessage, T as TranslationstudioLogo, l as handleHistoryResponse, m as groupHistoryData } from "./index-DKeIuEy_.mjs";
6
+ import { Trash, More, Bell, PaperPlane, Earth } from "@strapi/icons";
7
7
  const getSearchableText = (item) => {
8
8
  return `${item["project-name"]} ${item["element-name"]} ${item["element-uid"]} ${item.targetLanguages.join(" ")} ${item.combinedStatus.text}`.toLowerCase();
9
9
  };
@@ -88,7 +88,6 @@ function DeleteHistoryEntryRequest({ item, onDeleted, onClose }) {
88
88
  ] }) });
89
89
  }
90
90
  const DEBOUNCE_DELAY = 500;
91
- const LoadingState = () => /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "beta", children: "Loading history..." }) });
92
91
  const EmptyState = ({ hasSearchTerm }) => /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", style: { color: "#666" }, children: hasSearchTerm ? "No matching translation history found." : "No translation history available." }) });
93
92
  const SearchInput = ({ value, onChange }) => /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(
94
93
  TextInput,
@@ -178,10 +177,7 @@ const HistoryRow = ({ item, onDelete, secondaryColor }) => /* @__PURE__ */ jsxs(
178
177
  /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", children: formatDate(item.timeUpdated) }) }),
179
178
  /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(SimpleMenu, { label: "Actions", tag: IconButton, icon: /* @__PURE__ */ jsx(More, {}), children: /* @__PURE__ */ jsx(MenuItem, { startIcon: /* @__PURE__ */ jsx(Bell, {}), variant: "danger", onSelect: () => onDelete(item), children: "Delete history entry" }) }) })
180
179
  ] });
181
- const HistoryMenu = () => {
182
- const [historyData, setHistoryData] = useState([]);
183
- const [isLoading, setIsLoading] = useState(true);
184
- const [error, setError] = useState(null);
180
+ const HistoryMenu = ({ groupedHistoryData, onRemoveHistoryItem }) => {
185
181
  const [errorMessage, setErrorMessage] = useState("");
186
182
  const [succcessMessage, setSucccessMessage] = useState("");
187
183
  const [searchTerm, setSearchTerm] = useState("");
@@ -190,67 +186,19 @@ const HistoryMenu = () => {
190
186
  field: "time-imported",
191
187
  direction: "desc"
192
188
  });
193
- const { get, post } = getFetchClient();
189
+ const { post } = getFetchClient();
194
190
  const themeColors = getThemeColors();
195
191
  const debouncedSearchTerm = useDebounce(searchTerm, DEBOUNCE_DELAY);
196
- useEffect(() => {
197
- const fetchHistory = async () => {
198
- setIsLoading(true);
199
- setError(null);
200
- try {
201
- const response = await get("/translationstudio/history");
202
- const result = handleHistoryResponse(response.data);
203
- if (result.isError) {
204
- setError(result.errorMessage || "Failed to fetch translation history.");
205
- } else if (result.historyData && Array.isArray(result.historyData) && result.historyData.length > 0) {
206
- const res = result.historyData;
207
- res.sort((a, b) => a["time-updated"] - b["time-updated"]);
208
- setHistoryData(res);
209
- }
210
- } catch (error2) {
211
- console.error("Failed to fetch history:", error2);
212
- setError("Failed to fetch translation history.");
213
- setHistoryData([]);
214
- } finally {
215
- setIsLoading(false);
216
- }
217
- };
218
- fetchHistory();
219
- }, [setIsLoading, setError, setHistoryData]);
220
192
  const processedData = useMemo(() => {
221
- const grouped = groupHistoryData(historyData);
222
- const filtered = filterBySearchTerm(grouped, debouncedSearchTerm, getSearchableText);
193
+ const filtered = filterBySearchTerm(groupedHistoryData, debouncedSearchTerm, getSearchableText);
223
194
  return sortItems(filtered, sortState);
224
- }, [historyData, debouncedSearchTerm, sortState]);
195
+ }, [groupedHistoryData, debouncedSearchTerm, sortState]);
225
196
  const handleSort = (field) => {
226
197
  setSortState((currentState) => getNextSortState(currentState, field));
227
198
  };
228
199
  const handleSearchChange = (e) => {
229
200
  setSearchTerm(e.target.value);
230
201
  };
231
- if (isLoading) {
232
- return /* @__PURE__ */ jsx(LoadingState, {});
233
- }
234
- if (error) {
235
- return /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "beta", textColor: "danger600", children: error }) });
236
- }
237
- const removeHistoryEntry = function(item) {
238
- if (!item || historyData.length === 0)
239
- return;
240
- if (historyData.length === 1) {
241
- setHistoryData([]);
242
- return;
243
- }
244
- let index = -1;
245
- for (let i = 0; i < historyData.length && index === -1; i++) {
246
- if (historyData[i].id === item.id)
247
- index = i;
248
- }
249
- if (index === -1)
250
- return;
251
- historyData.splice(index, 1);
252
- setHistoryData([...historyData]);
253
- };
254
202
  const onDeleteHistoyInfo = function(elem) {
255
203
  if (!errorMessage)
256
204
  setErrorMessage("");
@@ -263,14 +211,14 @@ const HistoryMenu = () => {
263
211
  }).then((res) => {
264
212
  if (res.status !== 204)
265
213
  throw new Error("Could not delete entry");
266
- removeHistoryEntry(deleteItem);
214
+ onRemoveHistoryItem(deleteItem.id);
267
215
  setSucccessMessage("Successfully removed history entry.");
268
216
  }).catch(() => setErrorMessage("Could not delete history entry " + deleteItem["element-name"])).finally(() => setDeleteItem(null));
269
217
  };
270
218
  const hasData = processedData.length > 0;
271
219
  const hasSearchTerm = Boolean(debouncedSearchTerm.trim());
272
220
  return /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 4, style: { width: "100%" }, children: !hasData && !hasSearchTerm ? /* @__PURE__ */ jsx(EmptyState, { hasSearchTerm: false }) : /* @__PURE__ */ jsxs(Fragment, { children: [
273
- historyData.length > 10 && /* @__PURE__ */ jsx(SearchInput, { value: searchTerm, onChange: handleSearchChange }),
221
+ groupedHistoryData.length > 10 && /* @__PURE__ */ jsx(SearchInput, { value: searchTerm, onChange: handleSearchChange }),
274
222
  errorMessage && /* @__PURE__ */ jsx(Alert, { title: errorMessage, variant: "danger", style: { marginBottom: "2em" }, children: errorMessage }),
275
223
  succcessMessage && /* @__PURE__ */ jsx(Alert, { title: succcessMessage, variant: "success", style: { marginBottom: "2em" }, children: succcessMessage }),
276
224
  hasData ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -315,16 +263,7 @@ const LanguageSelector = ({
315
263
  name: "languages",
316
264
  "aria-label": "translationstudio settings",
317
265
  children: [
318
- /* @__PURE__ */ jsx(
319
- Typography,
320
- {
321
- variant: "omega",
322
- tag: "label",
323
- paddingBottom: 2,
324
- style: { color: themeColors.primaryText },
325
- children: "Translation Options"
326
- }
327
- ),
266
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", tag: "label", paddingBottom: 2, style: { color: themeColors.primaryText }, children: "Available translation options:" }),
328
267
  languages.map((lang) => /* @__PURE__ */ jsx(Radio.Item, { value: lang.name, children: lang.name }, lang.id))
329
268
  ]
330
269
  }
@@ -402,6 +341,7 @@ const AlertMessage = ({
402
341
  const BulkTranslationPanel = ({
403
342
  contentType,
404
343
  selectedEntries,
344
+ onClose,
405
345
  onTranslationComplete
406
346
  }) => {
407
347
  const [languages, setLanguages] = useState([]);
@@ -418,20 +358,16 @@ const BulkTranslationPanel = ({
418
358
  const [tsIsAvailable, setTsIsAvailable] = useState(true);
419
359
  const [isProcessing, setIsProcessing] = useState(false);
420
360
  const [progress, setProgress] = useState(0);
421
- const [isLoadingMappings, setIsLoadingMappings] = useState(false);
361
+ const [isLoadingMappings, setIsLoadingMappings] = useState(true);
422
362
  const [mappingsError, setMappingsError] = useState(false);
423
363
  const themeColors = getThemeColors();
424
- useThemeMode();
425
364
  const { get, post } = getFetchClient();
426
365
  const selectedLang = languages.find((lang) => lang.name === selectedOption);
427
366
  const isMachineTranslation = selectedLang?.machine ?? false;
428
367
  useEffect(() => {
429
368
  if (!contentType) return;
430
- setIsLoadingMappings(true);
431
- setMappingsError(false);
432
369
  get("/translationstudio/getLicense").then((response) => {
433
- if (typeof response.data.license !== "string") return false;
434
- return response.data.license !== "";
370
+ return response.status === 204;
435
371
  }).then((hasLicense) => {
436
372
  setLicenseValid(hasLicense);
437
373
  if (!hasLicense) throw new Error("No license set");
@@ -452,14 +388,15 @@ const BulkTranslationPanel = ({
452
388
  }).finally(() => {
453
389
  setIsLoadingMappings(false);
454
390
  });
455
- }, [contentType]);
391
+ }, [contentType, setLicenseValid, setTsIsAvailable, setLanguages, setMappingsError, setIsLoadingMappings]);
456
392
  useEffect(() => {
457
- if (!contentType || !isEmail) return;
393
+ if (!contentType || !isEmail)
394
+ return;
458
395
  const savedEmail = getStoredEmail();
459
396
  if (savedEmail) {
460
397
  setEmail(savedEmail);
461
398
  }
462
- }, [isEmail]);
399
+ }, [contentType, isEmail, setEmail]);
463
400
  const handleUrgentChange = () => {
464
401
  if (isUrgent) {
465
402
  setIsUrgent(false);
@@ -586,76 +523,70 @@ const BulkTranslationPanel = ({
586
523
  const pluginIsAvailable = () => {
587
524
  return !isLoadingMappings && licenseValid !== null && tsIsAvailable === true && !mappingsError;
588
525
  };
589
- return /* @__PURE__ */ jsxs(Fragment, { children: [
590
- /* @__PURE__ */ jsx(
591
- AlertMessage,
592
- {
593
- show: showAlert,
594
- type: alertType,
595
- message: alertMessage,
596
- onClose: () => setShowAlert(false)
597
- }
598
- ),
599
- /* @__PURE__ */ jsx(
600
- Box,
601
- {
602
- padding: 4,
603
- style: {
604
- width: "100%",
605
- backgroundColor: themeColors.cardBackground,
606
- borderRadius: "4px"
607
- },
608
- children: !pluginIsAvailable() ? renderNotAvailable() : /* @__PURE__ */ jsxs(Fragment, { children: [
609
- /* @__PURE__ */ jsx(
610
- LanguageSelector,
611
- {
612
- languages,
613
- selectedOption,
614
- onSelectionChange: handleRadioSelection,
615
- themeColors
616
- }
617
- ),
618
- !isMachineTranslation && selectedOption !== "" && /* @__PURE__ */ jsx(
619
- AdditionalSettings,
620
- {
621
- isUrgent,
622
- isEmail,
623
- email,
624
- dueDate,
625
- onUrgentChange: handleUrgentChange,
626
- onEmailChange: handleEmailChange,
627
- onEmailInputChange: handleEmailInputChange,
628
- onDueDateChange: handleDueDateChange,
629
- themeColors
630
- }
631
- ),
632
- isProcessing && /* @__PURE__ */ jsx(ProgressIndicator, { progress }),
633
- /* @__PURE__ */ jsx(
634
- Button,
635
- {
636
- fullWidth: true,
637
- onClick: handleBulkTranslationRequest,
638
- disabled: !selectedOption || isProcessing,
639
- loading: isProcessing,
640
- startIcon: /* @__PURE__ */ jsx(
641
- "svg",
642
- {
643
- width: "20",
644
- height: "20",
645
- viewBox: "0 0 24 24",
646
- fill: "none",
647
- xmlns: "http://www.w3.org/2000/svg",
648
- children: /* @__PURE__ */ jsx("path", { d: "M2,21L23,12L2,3V10L17,12L2,14V21Z", fill: "currentColor" })
649
- }
650
- ),
651
- style: { height: "auto" },
652
- children: getSubmitLabel(selectedEntries.length, isUrgent, isMachineTranslation)
653
- }
654
- )
655
- ] })
656
- }
657
- )
658
- ] });
526
+ return /* @__PURE__ */ jsx(Modal.Root, { open: true, onOpenChange: () => onClose(), children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
527
+ /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: "Translation request" }) }),
528
+ /* @__PURE__ */ jsxs(Modal.Body, { children: [
529
+ /* @__PURE__ */ jsx(
530
+ AlertMessage,
531
+ {
532
+ show: showAlert,
533
+ type: alertType,
534
+ message: alertMessage,
535
+ onClose: () => setShowAlert(false)
536
+ }
537
+ ),
538
+ /* @__PURE__ */ jsx(
539
+ Box,
540
+ {
541
+ padding: 4,
542
+ style: {
543
+ width: "100%",
544
+ backgroundColor: themeColors.cardBackground,
545
+ borderRadius: "4px"
546
+ },
547
+ children: !pluginIsAvailable() ? renderNotAvailable() : /* @__PURE__ */ jsxs(Fragment, { children: [
548
+ /* @__PURE__ */ jsx(
549
+ LanguageSelector,
550
+ {
551
+ languages,
552
+ selectedOption,
553
+ onSelectionChange: handleRadioSelection,
554
+ themeColors
555
+ }
556
+ ),
557
+ !isMachineTranslation && selectedOption !== "" && /* @__PURE__ */ jsx(
558
+ AdditionalSettings,
559
+ {
560
+ isUrgent,
561
+ isEmail,
562
+ email,
563
+ dueDate,
564
+ onUrgentChange: handleUrgentChange,
565
+ onEmailChange: handleEmailChange,
566
+ onEmailInputChange: handleEmailInputChange,
567
+ onDueDateChange: handleDueDateChange,
568
+ themeColors
569
+ }
570
+ ),
571
+ isProcessing && /* @__PURE__ */ jsx(ProgressIndicator, { progress })
572
+ ] })
573
+ }
574
+ )
575
+ ] }),
576
+ /* @__PURE__ */ jsxs(Modal.Footer, { children: [
577
+ /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", children: "Cancel" }),
578
+ /* @__PURE__ */ jsx(
579
+ Button,
580
+ {
581
+ onClick: handleBulkTranslationRequest,
582
+ disabled: !selectedOption || isProcessing,
583
+ loading: isProcessing,
584
+ startIcon: /* @__PURE__ */ jsx(PaperPlane, {}),
585
+ children: getSubmitLabel(selectedEntries.length, isUrgent, isMachineTranslation)
586
+ }
587
+ )
588
+ ] })
589
+ ] }) });
659
590
  };
660
591
  const filterAndTransformContentTypes = (data) => {
661
592
  return data.filter((type) => type.kind === "collectionType" || type.kind === "singleType").filter((type) => !type.uid.startsWith("admin::") && !type.uid.startsWith("plugin::")).map((type) => ({
@@ -815,14 +746,19 @@ const EntriesTable = ({
815
746
  }
816
747
  );
817
748
  };
818
- const getLangQueued = function(list) {
819
- return list.filter((e) => getElementStatus(e) === "queued").map((e) => e["target-language"]).sort().join(", ");
749
+ const getLangQueued = function(e) {
750
+ return e.queued.sort().join(", ");
751
+ };
752
+ const getLangInTranslation = function(e) {
753
+ return e.intranslation.sort().join(", ");
820
754
  };
821
- const getLangInTranslation = function(list) {
822
- return list.filter((e) => getElementStatus(e) === "intranslation").map((e) => e["target-language"]).sort().join(", ");
755
+ const getLangTranslated = function(e) {
756
+ return e.translated.sort().join(", ");
823
757
  };
824
- const getLangTranslated = function(list) {
825
- return list.filter((e) => getElementStatus(e) === "translated").map((e) => e["target-language"]).sort().join(", ");
758
+ const EmptyHistoryDataLanguageMapItem = {
759
+ queued: [],
760
+ intranslation: [],
761
+ translated: []
826
762
  };
827
763
  const EntryRow = ({
828
764
  entry,
@@ -833,7 +769,7 @@ const EntryRow = ({
833
769
  themeColors
834
770
  }) => {
835
771
  const entryId = getEntryId(entry);
836
- const history = historyData[entryId] ?? [];
772
+ const history = historyData[entryId] ?? EmptyHistoryDataLanguageMapItem;
837
773
  return /* @__PURE__ */ jsxs(Tr, { children: [
838
774
  /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(
839
775
  Checkbox,
@@ -866,8 +802,45 @@ const fetchContentEntriesData = async function(selectedContentType) {
866
802
  }
867
803
  return [];
868
804
  };
805
+ const extractDocumentId = function(id) {
806
+ const pos = id.lastIndexOf("#");
807
+ return pos === -1 ? id : id.substring(pos + 1);
808
+ };
809
+ const groupHistory = function(history) {
810
+ const map = {};
811
+ for (const e of history) {
812
+ const id = extractDocumentId(e["element-uid"]);
813
+ if (!map[id]) {
814
+ const data = {
815
+ queued: [],
816
+ intranslation: [],
817
+ translated: []
818
+ };
819
+ map[id] = data;
820
+ }
821
+ const entry = map[id];
822
+ switch (e.status) {
823
+ case "intranslation":
824
+ entry.intranslation.push(e.targetLanguage);
825
+ break;
826
+ case "translated":
827
+ entry.translated.push(e.targetLanguage);
828
+ break;
829
+ default:
830
+ entry.queued.push(e.targetLanguage);
831
+ break;
832
+ }
833
+ }
834
+ for (const key in map) {
835
+ const data = map[key];
836
+ data.intranslation.sort();
837
+ data.queued.sort();
838
+ data.translated.sort();
839
+ }
840
+ return map;
841
+ };
869
842
  const BulkTranslationMenu = ({
870
- historyData,
843
+ groupedHistoryData,
871
844
  isLoadingHistory,
872
845
  onTranslationComplete
873
846
  }) => {
@@ -877,7 +850,9 @@ const BulkTranslationMenu = ({
877
850
  const [selectedEntries, setSelectedEntries] = useState(/* @__PURE__ */ new Set());
878
851
  const [isLoadingContentTypes, setIsLoadingContentTypes] = useState(false);
879
852
  const [isLoadingEntries, setIsLoadingEntries] = useState(false);
853
+ const [showTranslation, setShowTranslation] = useState(false);
880
854
  const themeColors = getThemeColors();
855
+ const historyItemMap = useMemo(() => groupHistory(groupedHistoryData), [groupedHistoryData]);
881
856
  const { get } = getFetchClient();
882
857
  useEffect(() => {
883
858
  const fetchContentTypes = async () => {
@@ -943,7 +918,7 @@ const BulkTranslationMenu = ({
943
918
  if (contentTypes.length === 0) {
944
919
  return /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 4, style: { overflow: "hidden", display: "flex", flexDirection: "column" }, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", paddingBottom: 3, style: { fontWeight: "bold" }, children: "No content types available." }) });
945
920
  }
946
- return /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 4, style: { overflow: "hidden", display: "flex", flexDirection: "column" }, children: /* @__PURE__ */ jsxs(Box, { style: { flex: 1, display: "flex", gap: "16px", overflow: "hidden" }, children: [
921
+ return /* @__PURE__ */ jsx(Box, { paddingTop: 4, paddingBottom: 4, style: { display: "flex", flexDirection: "column" }, children: /* @__PURE__ */ jsxs(Box, { style: { flex: 1, display: "flex", gap: "16px" }, children: [
947
922
  /* @__PURE__ */ jsx(
948
923
  ContentTypesList,
949
924
  {
@@ -960,9 +935,7 @@ const BulkTranslationMenu = ({
960
935
  style: {
961
936
  flex: 1,
962
937
  display: "flex",
963
- flexDirection: "column",
964
- minWidth: "400px",
965
- width: "60vw"
938
+ flexDirection: "column"
966
939
  },
967
940
  children: [
968
941
  /* @__PURE__ */ jsx(Typography, { variant: "omega", paddingBottom: 3, style: { fontWeight: "bold" }, children: "Entries" }),
@@ -972,7 +945,7 @@ const BulkTranslationMenu = ({
972
945
  entries,
973
946
  isLoading: isLoadingEntries,
974
947
  selectedEntries,
975
- historyData,
948
+ historyData: historyItemMap,
976
949
  isLoadingHistory,
977
950
  onEntrySelection: handleEntrySelection,
978
951
  onSelectAll: handleSelectAll,
@@ -982,34 +955,55 @@ const BulkTranslationMenu = ({
982
955
  ]
983
956
  }
984
957
  ),
985
- /* @__PURE__ */ jsx(Box, { style: { width: "400px", flexShrink: 0, display: "flex", flexDirection: "column" }, children: selectedEntries.size > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
986
- /* @__PURE__ */ jsx(Typography, { variant: "omega", paddingBottom: 3, style: { fontWeight: "bold" }, children: "Translation Options" }),
987
- /* @__PURE__ */ jsx(Box, { style: { borderRadius: "4px", overflow: "hidden" }, children: /* @__PURE__ */ jsx(
988
- BulkTranslationPanel,
989
- {
990
- contentType: selectedContentTypeData,
991
- selectedEntries: Array.from(selectedEntries),
992
- onTranslationComplete: handleTranslationComplete
958
+ /* @__PURE__ */ jsxs(
959
+ Button,
960
+ {
961
+ disabled: selectedEntries.size === 0,
962
+ style: { position: "fixed", right: "5%", bottom: "5%" },
963
+ size: "L",
964
+ startIcon: /* @__PURE__ */ jsx(Earth, {}),
965
+ onClick: () => {
966
+ setShowTranslation(true);
967
+ },
968
+ children: [
969
+ "Translate ",
970
+ selectedEntries.size,
971
+ " ",
972
+ selectedEntries.size === 1 ? "entry" : "entries"
973
+ ]
974
+ }
975
+ ),
976
+ showTranslation && /* @__PURE__ */ jsx(
977
+ BulkTranslationPanel,
978
+ {
979
+ contentType: selectedContentTypeData,
980
+ selectedEntries: Array.from(selectedEntries),
981
+ onClose: () => setShowTranslation(false),
982
+ onTranslationComplete: () => {
983
+ handleTranslationComplete();
984
+ setShowTranslation(false);
993
985
  }
994
- ) })
995
- ] }) })
986
+ }
987
+ )
996
988
  ] }) });
997
989
  };
998
- const processHistoryData = function(list) {
999
- const map = {};
1000
- for (const elem of list) {
1001
- const id = elem["element-uid"].split("#");
1002
- if (id.length !== 2)
1003
- continue;
1004
- if (!map[id[0]])
1005
- map[id[0]] = [elem];
1006
- else
1007
- map[id[0]].push(elem);
1008
- }
1009
- return map;
990
+ const TranslationstudioLogoBox = function() {
991
+ return /* @__PURE__ */ jsx(Box, { style: { textAlign: "right", width: "100%" }, children: /* @__PURE__ */ jsx(
992
+ "picture",
993
+ {
994
+ style: {
995
+ width: "150px",
996
+ height: "auto",
997
+ display: "inline-block"
998
+ },
999
+ children: /* @__PURE__ */ jsx(TranslationstudioLogo, {})
1000
+ }
1001
+ ) });
1010
1002
  };
1011
1003
  const HistoryPage = () => {
1012
- const [historyData, setHistoryData] = useState({});
1004
+ const [historyData, setHistoryData] = useState([]);
1005
+ const [groupedHistoryData, setGroupedHistoryData] = useState([]);
1006
+ const [lastUpdated, setLastUpdated] = useState(0);
1013
1007
  const [isLoadingHistory, setIsLoadingHistory] = useState(true);
1014
1008
  const [activeTab, setActiveTab] = useState("history");
1015
1009
  const { get } = getFetchClient();
@@ -1020,50 +1014,77 @@ const HistoryPage = () => {
1020
1014
  const result = handleHistoryResponse(response.data);
1021
1015
  if (result.isError)
1022
1016
  throw new Error("Cold not fetch data");
1023
- if (result.historyData && Array.isArray(result.historyData))
1024
- setHistoryData(processHistoryData(result.historyData));
1017
+ if (result.historyData && Array.isArray(result.historyData)) {
1018
+ setHistoryData(result.historyData);
1019
+ setGroupedHistoryData(groupHistoryData(result.historyData));
1020
+ setLastUpdated(Date.now());
1021
+ }
1025
1022
  } catch (error) {
1026
1023
  console.error("Failed to fetch history:", error);
1027
- setHistoryData({});
1024
+ setHistoryData([]);
1025
+ setGroupedHistoryData([]);
1028
1026
  } finally {
1029
1027
  setIsLoadingHistory(false);
1030
1028
  }
1031
1029
  };
1032
1030
  fetchHistory();
1033
- }, [setHistoryData, setIsLoadingHistory]);
1031
+ }, [setHistoryData, setIsLoadingHistory, setGroupedHistoryData, setLastUpdated]);
1034
1032
  const refreshHistoryData = async () => {
1035
1033
  setIsLoadingHistory(true);
1036
1034
  try {
1037
1035
  const response = await get("/translationstudio/history");
1038
1036
  const result = handleHistoryResponse(response.data);
1039
- if (result.isError)
1040
- setHistoryData({});
1041
- else if (result.historyData && Array.isArray(result.historyData))
1042
- setHistoryData(processHistoryData(result.historyData));
1037
+ if (result.isError) {
1038
+ setGroupedHistoryData([]);
1039
+ setHistoryData([]);
1040
+ } else if (result.historyData && Array.isArray(result.historyData)) {
1041
+ setHistoryData(result.historyData);
1042
+ setGroupedHistoryData(groupHistoryData(result.historyData));
1043
+ setLastUpdated(Date.now());
1044
+ }
1043
1045
  } catch (error) {
1044
- setHistoryData({});
1046
+ setHistoryData([]);
1047
+ setGroupedHistoryData([]);
1045
1048
  } finally {
1046
1049
  setIsLoadingHistory(false);
1047
1050
  }
1048
1051
  };
1052
+ const onRefreshHistoryClick = function() {
1053
+ if (Date.now() - lastUpdated > 1e3 * 60 * 5)
1054
+ refreshHistoryData();
1055
+ };
1056
+ const onChangePage = function(input) {
1057
+ onRefreshHistoryClick();
1058
+ setActiveTab(input);
1059
+ };
1060
+ const removeHistoryEntry = function(id) {
1061
+ if (!id || historyData.length === 0)
1062
+ return;
1063
+ if (historyData.length === 1) {
1064
+ setHistoryData([]);
1065
+ return;
1066
+ }
1067
+ let index = -1;
1068
+ for (let i = 0; i < historyData.length && index === -1; i++) {
1069
+ if (historyData[i].id === id)
1070
+ index = i;
1071
+ }
1072
+ if (index === -1)
1073
+ return;
1074
+ historyData.splice(index, 1);
1075
+ const res = [...historyData];
1076
+ setHistoryData(res);
1077
+ setGroupedHistoryData(groupHistoryData(res));
1078
+ };
1049
1079
  return /* @__PURE__ */ jsx(Main, { children: /* @__PURE__ */ jsx(Box, { padding: 10, style: { minHeight: "90vh", marginTop: "5vh" }, children: /* @__PURE__ */ jsxs(Grid.Root, { children: [
1050
- /* @__PURE__ */ jsx(Grid.Item, { xs: 12, children: /* @__PURE__ */ jsx(Box, { style: { textAlign: "right", width: "100%" }, children: /* @__PURE__ */ jsx(
1051
- "picture",
1052
- {
1053
- style: {
1054
- width: "150px",
1055
- height: "auto",
1056
- display: "inline-block"
1057
- },
1058
- children: /* @__PURE__ */ jsx(TranslationstudioLogo, {})
1059
- }
1060
- ) }) }),
1080
+ /* @__PURE__ */ jsx(Grid.Item, { xs: 12, children: /* @__PURE__ */ jsx(TranslationstudioLogoBox, {}) }),
1061
1081
  /* @__PURE__ */ jsx(Grid.Item, { xs: 12, children: /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
1062
1082
  /* @__PURE__ */ jsx(
1063
1083
  Button,
1064
1084
  {
1065
1085
  variant: activeTab === "history" ? "default" : "tertiary",
1066
- onClick: () => setActiveTab("history"),
1086
+ disabled: isLoadingHistory,
1087
+ onClick: () => onChangePage("history"),
1067
1088
  children: "Translation History"
1068
1089
  }
1069
1090
  ),
@@ -1071,7 +1092,8 @@ const HistoryPage = () => {
1071
1092
  Button,
1072
1093
  {
1073
1094
  variant: activeTab === "bulk" ? "default" : "tertiary",
1074
- onClick: () => setActiveTab("bulk"),
1095
+ disabled: isLoadingHistory,
1096
+ onClick: () => onChangePage("bulk"),
1075
1097
  children: "Translate multiple entries"
1076
1098
  }
1077
1099
  )
@@ -1080,7 +1102,7 @@ const HistoryPage = () => {
1080
1102
  activeTab === "bulk" && /* @__PURE__ */ jsx(
1081
1103
  BulkTranslationMenu,
1082
1104
  {
1083
- historyData,
1105
+ groupedHistoryData,
1084
1106
  isLoadingHistory,
1085
1107
  onTranslationComplete: refreshHistoryData
1086
1108
  }
@@ -1088,9 +1110,8 @@ const HistoryPage = () => {
1088
1110
  activeTab === "history" && /* @__PURE__ */ jsx(
1089
1111
  HistoryMenu,
1090
1112
  {
1091
- historyData,
1092
- isLoadingHistory,
1093
- onRefresh: refreshHistoryData
1113
+ groupedHistoryData,
1114
+ onRemoveHistoryItem: (id) => removeHistoryEntry(id)
1094
1115
  }
1095
1116
  )
1096
1117
  ] })