@serhii.olishevskyi/docs 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,32 @@
1
- // src/services/session.service.ts
2
- var getUserLastSeenAt = async (supabase, userId) => {
3
- const { data, error } = await supabase.from("user_sessions").select("last_seen_at").eq("user_id", userId).order("last_seen_at", { ascending: false }).limit(1).maybeSingle();
4
- if (error || !data) return null;
5
- return data.last_seen_at;
6
- };
1
+ // src/UI/components/data.display/UserPopoverComponent.tsx
2
+ import { useEffect as useEffect8, useState as useState12 } from "react";
3
+ import {
4
+ Popover,
5
+ Box as Box4,
6
+ Avatar,
7
+ Typography as Typography2,
8
+ Divider,
9
+ IconButton as IconButton4,
10
+ CircularProgress as CircularProgress2,
11
+ Stack,
12
+ Badge
13
+ } from "@mui/material";
14
+ import {
15
+ NotificationsNone as NotificationIcon,
16
+ AssignmentOutlined as TaskIcon,
17
+ ChatBubble as ChatIcon,
18
+ Work as WorkIcon,
19
+ CalendarToday as CalendarIcon,
20
+ Phone as PhoneIcon,
21
+ Close as CloseIcon,
22
+ Email as EmailIcon,
23
+ AccessTime as AccessTimeIcon,
24
+ Cake as CakeIcon
25
+ } from "@mui/icons-material";
26
+
27
+ // src/UI/components/data.display/UserStatusChipComponent.tsx
28
+ import { Chip, IconButton as IconButton2 } from "@mui/material";
29
+ import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
7
30
 
8
31
  // src/context/SupabaseProvider.tsx
9
32
  import { createContext, useContext, useMemo } from "react";
@@ -34,20 +57,23 @@ var useSupabase = () => {
34
57
  return context.supabase;
35
58
  };
36
59
 
37
- // src/context/AuthProvider.tsx
38
- import { createContext as createContext2, useContext as useContext2, useEffect, useState, useCallback, useRef } from "react";
60
+ // src/context/TranslateProvider.tsx
61
+ import { createContext as createContext2, useContext as useContext2 } from "react";
39
62
  import { jsx as jsx2 } from "react/jsx-runtime";
40
- var AuthContext = createContext2(void 0);
41
- var useAuth = () => {
42
- const context = useContext2(AuthContext);
43
- if (!context) {
44
- throw new Error("useAuth should be used within the AuthProvider");
45
- }
46
- return context;
63
+ var TranslationNamespaceContext = createContext2({
64
+ namespace: "common"
65
+ // значение по умолчанию
66
+ });
67
+ var TranslateProvider = ({ namespace, children }) => {
68
+ return /* @__PURE__ */ jsx2(TranslationNamespaceContext.Provider, { value: { namespace }, children });
69
+ };
70
+ var useTranslationNamespace = (overrideNamespace) => {
71
+ const context = useContext2(TranslationNamespaceContext);
72
+ return overrideNamespace || context.namespace;
47
73
  };
48
74
 
49
75
  // src/context/TranslationEditProvider.tsx
50
- import { createContext as createContext3, useContext as useContext3, useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
76
+ import { createContext as createContext4, useContext as useContext4, useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
51
77
 
52
78
  // node_modules/i18next/dist/esm/i18next.js
53
79
  var isString = (obj) => typeof obj === "string";
@@ -2435,9 +2461,30 @@ var SupabaseCacheBackend = _SupabaseCacheBackend;
2435
2461
  // src/i18n/config.ts
2436
2462
  var config_default = instance;
2437
2463
 
2438
- // src/context/TranslationEditProvider.tsx
2464
+ // src/context/AuthProvider.tsx
2465
+ import { createContext as createContext3, useContext as useContext3, useEffect, useState, useCallback, useRef } from "react";
2466
+
2467
+ // src/services/session.service.ts
2468
+ var getUserLastSeenAt = async (supabase, userId) => {
2469
+ const { data, error } = await supabase.from("user_sessions").select("last_seen_at").eq("user_id", userId).order("last_seen_at", { ascending: false }).limit(1).maybeSingle();
2470
+ if (error || !data) return null;
2471
+ return data.last_seen_at;
2472
+ };
2473
+
2474
+ // src/context/AuthProvider.tsx
2439
2475
  import { jsx as jsx3 } from "react/jsx-runtime";
2440
- var TranslationEditContext = createContext3(void 0);
2476
+ var AuthContext = createContext3(void 0);
2477
+ var useAuth = () => {
2478
+ const context = useContext3(AuthContext);
2479
+ if (!context) {
2480
+ throw new Error("useAuth should be used within the AuthProvider");
2481
+ }
2482
+ return context;
2483
+ };
2484
+
2485
+ // src/context/TranslationEditProvider.tsx
2486
+ import { jsx as jsx4 } from "react/jsx-runtime";
2487
+ var TranslationEditContext = createContext4(void 0);
2441
2488
  var TranslationEditProvider = ({ children }) => {
2442
2489
  const supabase = useSupabase();
2443
2490
  const { user } = useAuth();
@@ -2494,7 +2541,7 @@ var TranslationEditProvider = ({ children }) => {
2494
2541
  setIsTranslationMode(val);
2495
2542
  }
2496
2543
  };
2497
- return /* @__PURE__ */ jsx3(
2544
+ return /* @__PURE__ */ jsx4(
2498
2545
  TranslationEditContext.Provider,
2499
2546
  {
2500
2547
  value: {
@@ -2509,1081 +2556,1035 @@ var TranslationEditProvider = ({ children }) => {
2509
2556
  );
2510
2557
  };
2511
2558
  var useTranslationEdit = () => {
2512
- const context = useContext3(TranslationEditContext);
2559
+ const context = useContext4(TranslationEditContext);
2513
2560
  if (!context) throw new Error("useTranslationEdit must be used within TranslationEditProvider");
2514
2561
  return context;
2515
2562
  };
2516
2563
 
2517
- // src/context/AIProvider.tsx
2518
- import { createContext as createContext4, useContext as useContext4, useMemo as useMemo2 } from "react";
2519
- import { GoogleGenAI } from "@google/genai";
2520
- import { jsx as jsx4 } from "react/jsx-runtime";
2521
- var AIContext = createContext4(null);
2522
- var AIProvider = ({
2523
- children,
2524
- config,
2525
- apiKey: directApiKey
2526
- }) => {
2527
- const apiKey = directApiKey || config?.apiKey;
2528
- const provider = config?.provider || "gemini";
2529
- const modelName = config?.modelName || (provider === "gemini" ? "gemini-2.5-flash" : "gpt-4o-mini");
2530
- const contextValue = useMemo2(() => {
2531
- return {
2532
- apiKey,
2533
- provider,
2534
- modelName,
2535
- generate: async ({
2536
- prompt,
2537
- systemInstruction,
2538
- jsonMode = false,
2539
- temperature
2540
- }) => {
2541
- if (!apiKey) {
2542
- console.warn("[AIProvider]: API Key \u043D\u0435 \u0437\u0430\u0434\u0430\u043D.");
2543
- return null;
2564
+ // src/context/LocalizationProvider.tsx
2565
+ import { createContext as createContext5, useContext as useContext5, useMemo as useMemo2, useEffect as useEffect3, useState as useState3 } from "react";
2566
+ import { useTranslation } from "react-i18next";
2567
+ import moment from "moment/min/moment-with-locales";
2568
+ import { jsx as jsx5 } from "react/jsx-runtime";
2569
+ var DEFAULT_DATETIME_FORMATS = {
2570
+ uk: "DD.MM.YYYY, HH:mm",
2571
+ de: "DD.MM.YYYY, HH:mm",
2572
+ en: "MM/DD/YYYY, hh:mm A",
2573
+ es: "DD/MM/YYYY, HH:mm",
2574
+ fr: "DD/MM/YYYY, HH:mm",
2575
+ it: "DD/MM/YYYY, HH:mm"
2576
+ };
2577
+ var DEFAULT_DATE_FORMATS = {
2578
+ uk: "DD.MM.YYYY",
2579
+ de: "DD.MM.YYYY",
2580
+ en: "MM/DD/YYYY",
2581
+ es: "DD/MM/YYYY",
2582
+ fr: "DD/MM/YYYY",
2583
+ it: "DD/MM/YYYY"
2584
+ };
2585
+ var DEFAULT_TIME_FORMATS = {
2586
+ uk: "HH:mm:ss",
2587
+ de: "HH:mm:ss",
2588
+ en: "hh:mm:ss A",
2589
+ es: "HH:mm:ss",
2590
+ fr: "HH:mm:ss",
2591
+ it: "HH:mm:ss"
2592
+ };
2593
+ var LocalizationContext = createContext5(void 0);
2594
+ var LocalizationProvider = ({ children }) => {
2595
+ const supabase = useSupabase();
2596
+ const { i18n } = useTranslation();
2597
+ const [languages, setLanguages] = useState3([]);
2598
+ const [isLoadingLangs, setIsLoadingLangs] = useState3(true);
2599
+ const [languagesError, setLanguagesError] = useState3(null);
2600
+ useEffect3(() => {
2601
+ let isMounted = true;
2602
+ const fetchLanguages = async () => {
2603
+ try {
2604
+ setIsLoadingLangs(true);
2605
+ setLanguagesError(null);
2606
+ const { data: langs, error: langErr } = await supabase.from("languages").select("code, name, svg_flag, enabled, created_at").eq("enabled", true).order("code", { ascending: true });
2607
+ if (langErr) {
2608
+ throw langErr;
2544
2609
  }
2545
- if (provider === "gemini") {
2546
- try {
2547
- const ai = new GoogleGenAI({ apiKey });
2548
- const response = await ai.models.generateContent({
2549
- model: modelName,
2550
- contents: prompt,
2551
- config: {
2552
- systemInstruction,
2553
- temperature,
2554
- responseMimeType: jsonMode ? "application/json" : "text/plain"
2555
- }
2556
- });
2557
- const rawText = response.text?.trim() || "";
2558
- if (jsonMode) {
2559
- const cleanedText = rawText.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
2560
- return JSON.parse(cleanedText);
2561
- }
2562
- return rawText;
2563
- } catch (err) {
2564
- console.error("[AIProvider Gemini Error]:", err);
2565
- return null;
2566
- }
2610
+ if (isMounted && langs) {
2611
+ setLanguages(langs);
2567
2612
  }
2568
- if (provider === "openai") {
2569
- try {
2570
- const messages = [];
2571
- if (systemInstruction) {
2572
- messages.push({ role: "system", content: systemInstruction });
2573
- }
2574
- messages.push({ role: "user", content: prompt });
2575
- const res = await fetch("https://api.openai.com/v1/chat/completions", {
2576
- method: "POST",
2577
- headers: {
2578
- "Content-Type": "application/json",
2579
- Authorization: `Bearer ${apiKey}`
2580
- },
2581
- body: JSON.stringify({
2582
- model: modelName,
2583
- messages,
2584
- temperature,
2585
- ...jsonMode ? { response_format: { type: "json_object" } } : {}
2586
- })
2587
- });
2588
- const data = await res.json();
2589
- const content = data.choices?.[0]?.message?.content?.trim() || "";
2590
- if (jsonMode) {
2591
- return JSON.parse(content);
2592
- }
2593
- return content;
2594
- } catch (err) {
2595
- console.error("[AIProvider OpenAI Error]:", err);
2596
- return null;
2597
- }
2613
+ } catch (err) {
2614
+ console.error("Failed to load languages from Supabase:", err);
2615
+ if (isMounted) {
2616
+ setLanguagesError(err.message || "Error loading languages");
2617
+ }
2618
+ } finally {
2619
+ if (isMounted) {
2620
+ setIsLoadingLangs(false);
2598
2621
  }
2599
- return null;
2600
2622
  }
2601
2623
  };
2602
- }, [apiKey, provider, modelName]);
2603
- return /* @__PURE__ */ jsx4(AIContext.Provider, { value: contextValue, children });
2624
+ fetchLanguages();
2625
+ return () => {
2626
+ isMounted = false;
2627
+ };
2628
+ }, [supabase]);
2629
+ const rawLang = i18n.language || "uk";
2630
+ const currentLang = rawLang.split("-")[0].toLowerCase();
2631
+ const getDateFormat = () => {
2632
+ return DEFAULT_DATE_FORMATS[currentLang] || "DD.MM.YYYY";
2633
+ };
2634
+ const getTimeFormat = () => {
2635
+ return DEFAULT_TIME_FORMATS[currentLang] || "HH:mm:ss";
2636
+ };
2637
+ const getDateTimeFormat = () => {
2638
+ return DEFAULT_DATETIME_FORMATS[currentLang] || "DD.MM.YYYY, HH:mm";
2639
+ };
2640
+ const formatDateTime = (date, customPattern) => {
2641
+ if (!date) return "\u2014";
2642
+ const pattern = customPattern || DEFAULT_DATETIME_FORMATS[currentLang] || "DD.MM.YYYY, HH:mm";
2643
+ return moment(date).locale(currentLang).format(pattern);
2644
+ };
2645
+ const formatDate = (date, customPattern) => {
2646
+ if (!date) return "\u2014";
2647
+ const pattern = customPattern || DEFAULT_DATE_FORMATS[currentLang] || "DD.MM.YYYY";
2648
+ return moment(date).locale(currentLang).format(pattern);
2649
+ };
2650
+ const formatTime = (date, customPattern) => {
2651
+ if (!date) return "\u2014";
2652
+ const pattern = customPattern || DEFAULT_TIME_FORMATS[currentLang] || "HH:mm:ss";
2653
+ return moment(date).locale(currentLang).format(pattern);
2654
+ };
2655
+ const formatRelativeTime = (date, withoutSuffix = false) => {
2656
+ if (!date) return "\u2014";
2657
+ return moment(date).locale(currentLang).fromNow(withoutSuffix);
2658
+ };
2659
+ const changeLanguage2 = async (lang) => {
2660
+ await i18n.changeLanguage(lang);
2661
+ };
2662
+ const value = useMemo2(
2663
+ () => ({
2664
+ currentLang,
2665
+ languages,
2666
+ isLoadingLangs,
2667
+ languagesError,
2668
+ changeLanguage: changeLanguage2,
2669
+ getDateFormat,
2670
+ getTimeFormat,
2671
+ getDateTimeFormat,
2672
+ formatDateTime,
2673
+ formatDate,
2674
+ formatTime,
2675
+ formatRelativeTime
2676
+ }),
2677
+ [currentLang, languages, isLoadingLangs, languagesError]
2678
+ );
2679
+ return /* @__PURE__ */ jsx5(LocalizationContext.Provider, { value, children });
2604
2680
  };
2605
- var useAI = () => {
2606
- const context = useContext4(AIContext);
2681
+ var useLocalization = () => {
2682
+ const context = useContext5(LocalizationContext);
2607
2683
  if (!context) {
2608
- throw new Error("useAI \u0434\u043E\u043B\u0436\u0435\u043D \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C\u0441\u044F \u0432\u043D\u0443\u0442\u0440\u0438 <AIProvider>");
2684
+ throw new Error("useLocalization must be used within a LocalizationProvider");
2609
2685
  }
2610
2686
  return context;
2611
2687
  };
2612
2688
 
2613
- // src/context/TranslateProvider.tsx
2614
- import { createContext as createContext5, useContext as useContext5 } from "react";
2615
- import { jsx as jsx5 } from "react/jsx-runtime";
2616
- var TranslationNamespaceContext = createContext5({
2617
- namespace: "common"
2618
- // значение по умолчанию
2619
- });
2620
- var TranslateProvider = ({ namespace, children }) => {
2621
- return /* @__PURE__ */ jsx5(TranslationNamespaceContext.Provider, { value: { namespace }, children });
2689
+ // src/context/ThemeProvider.tsx
2690
+ import { createContext as createContext6, useContext as useContext6, useMemo as useMemo3, useState as useState4, useEffect as useEffect4 } from "react";
2691
+ import { createTheme, ThemeProvider as MuiThemeProvider, CssBaseline, useMediaQuery } from "@mui/material";
2692
+ import { jsx as jsx6, jsxs } from "react/jsx-runtime";
2693
+ var DEFAULT_LIGHT_COLORS = {
2694
+ primary: "#1976d2",
2695
+ secondary: "#9c27b0",
2696
+ backgroundDefault: "#f5f5f5",
2697
+ backgroundPaper: "#ffffff",
2698
+ textPrimary: "#0f172a",
2699
+ textSecondary: "#475569",
2700
+ success: "#2e7d32",
2701
+ error: "#d32f2f",
2702
+ warning: "#ed6c02",
2703
+ info: "#0288d1"
2622
2704
  };
2623
- var useTranslationNamespace = (overrideNamespace) => {
2624
- const context = useContext5(TranslationNamespaceContext);
2625
- return overrideNamespace || context.namespace;
2705
+ var DEFAULT_DARK_COLORS = {
2706
+ primary: "#90caf9",
2707
+ secondary: "#ce93d8",
2708
+ backgroundDefault: "#0f172a",
2709
+ backgroundPaper: "#1e293b",
2710
+ textPrimary: "#f8fafc",
2711
+ textSecondary: "#94a3b8",
2712
+ success: "#66bb6a",
2713
+ error: "#f44336",
2714
+ warning: "#ffa726",
2715
+ info: "#29b6f6"
2626
2716
  };
2627
-
2628
- // src/i18n/TranslateText.tsx
2629
- import { useState as useState4, useRef as useRef2, useEffect as useEffect4 } from "react";
2630
- import { useTranslation } from "react-i18next";
2631
- import { Box as Box2, Popper, Paper as Paper2, MenuList, MenuItem, ListItemIcon, ListItemText } from "@mui/material";
2632
- import TranslateIcon2 from "@mui/icons-material/Translate";
2633
- import LaunchIcon from "@mui/icons-material/Launch";
2634
-
2635
- // src/i18n/TranslationDialog.tsx
2636
- import { useEffect as useEffect3, useState as useState3 } from "react";
2637
- import {
2638
- Dialog,
2639
- DialogTitle,
2640
- DialogContent,
2641
- DialogActions,
2642
- Button,
2643
- TextField,
2644
- Box,
2645
- Typography,
2646
- CircularProgress,
2647
- IconButton,
2648
- Tooltip,
2649
- Paper,
2650
- Alert
2651
- } from "@mui/material";
2652
- import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
2653
- import TranslateIcon from "@mui/icons-material/Translate";
2654
- import LightbulbIcon from "@mui/icons-material/Lightbulb";
2655
-
2656
- // src/services/translation.service.ts
2657
- var translationService = {
2658
- // 1. Принимаем client первым аргументом
2659
- incrementVersion: async (client, namespace, langCodes) => {
2660
- try {
2661
- const { data } = await client.from("translation_versions").select("version, lang_code").eq("namespace", namespace);
2662
- const currentVersion = data && data.length > 0 ? Math.max(...data.map((d) => d.version || 0)) : 0;
2663
- const nextVersion = currentVersion + 1;
2664
- const languagesToUpdate = langCodes && langCodes.length > 0 ? langCodes : ["uk"];
2665
- const updates = languagesToUpdate.map((lang_code) => ({
2666
- namespace,
2667
- lang_code,
2668
- version: nextVersion,
2669
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
2670
- }));
2671
- const { error } = await client.from("translation_versions").upsert(updates, { onConflict: "namespace,lang_code" });
2672
- if (error) console.error("Error updating translation version:", error);
2673
- return nextVersion;
2674
- } catch (err) {
2675
- console.error("Failed to increment version in Supabase:", err);
2676
- return null;
2677
- }
2717
+ var INITIAL_THEME_CONFIG = {
2718
+ name: "Base MUI Theme",
2719
+ mode: "light",
2720
+ borderRadius: 12,
2721
+ colors: {
2722
+ light: DEFAULT_LIGHT_COLORS,
2723
+ dark: DEFAULT_DARK_COLORS
2678
2724
  },
2679
- translateSmart: async (client, aiGenerateFn, params) => {
2680
- const languagesToTranslate = params.targetLangs || (params.targetLang ? [params.targetLang] : []);
2681
- try {
2682
- const { data, error } = await client.functions.invoke("translate-smart", { body: params });
2683
- if (!error && data) return data;
2684
- } catch {
2685
- console.warn("Edge Function translate-smart \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C AIProvider...");
2686
- }
2687
- if (aiGenerateFn) {
2688
- const prompt = `Translate the source text into language codes: ${JSON.stringify(languagesToTranslate)}.
2689
- Source Text: "${params.text}"
2690
- ${params.contextHint ? `Context Hint: "${params.contextHint}"` : ""}`;
2691
- const translations = await aiGenerateFn({
2692
- prompt,
2693
- systemInstruction: 'Respond STRICTLY with a valid JSON mapping lang code to text. Example: {"en": "Hello"}',
2694
- jsonMode: true
2695
- });
2696
- if (translations) {
2697
- if (params.targetLang) {
2698
- return { translatedText: translations[params.targetLang] || params.text };
2725
+ typography: {
2726
+ fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
2727
+ fontSize: 14,
2728
+ fontWeightLight: 300,
2729
+ fontWeightRegular: 400,
2730
+ fontWeightMedium: 600,
2731
+ fontWeightBold: 700
2732
+ }
2733
+ };
2734
+ var buildMuiTheme = (config) => {
2735
+ const currentMode = config.mode;
2736
+ const currentColors = config.colors?.[currentMode] || (currentMode === "light" ? DEFAULT_LIGHT_COLORS : DEFAULT_DARK_COLORS);
2737
+ return createTheme({
2738
+ palette: {
2739
+ mode: currentMode,
2740
+ primary: {
2741
+ main: currentColors.primary,
2742
+ light: currentMode === "light" ? "#63a4ff" : "#a1d2fa",
2743
+ dark: currentMode === "light" ? "#004ba0" : "#42a5f5"
2744
+ },
2745
+ secondary: {
2746
+ main: currentColors.secondary
2747
+ },
2748
+ background: {
2749
+ default: currentColors.backgroundDefault,
2750
+ paper: currentColors.backgroundPaper
2751
+ },
2752
+ text: {
2753
+ primary: currentColors.textPrimary,
2754
+ secondary: currentColors.textSecondary || (currentMode === "light" ? "#475569" : "#94a3b8")
2755
+ },
2756
+ success: { main: currentColors.success },
2757
+ error: { main: currentColors.error },
2758
+ warning: { main: currentColors.warning },
2759
+ info: { main: currentColors.info }
2760
+ },
2761
+ shape: {
2762
+ borderRadius: config.borderRadius
2763
+ },
2764
+ typography: {
2765
+ fontFamily: config.typography?.fontFamily || INITIAL_THEME_CONFIG.typography.fontFamily,
2766
+ fontSize: config.typography?.fontSize || INITIAL_THEME_CONFIG.typography.fontSize,
2767
+ fontWeightLight: config.typography?.fontWeightLight || INITIAL_THEME_CONFIG.typography.fontWeightLight,
2768
+ fontWeightRegular: config.typography?.fontWeightRegular || INITIAL_THEME_CONFIG.typography.fontWeightRegular,
2769
+ fontWeightMedium: config.typography?.fontWeightMedium || INITIAL_THEME_CONFIG.typography.fontWeightMedium,
2770
+ fontWeightBold: config.typography?.fontWeightBold || INITIAL_THEME_CONFIG.typography.fontWeightBold,
2771
+ h5: { fontWeight: 700, fontSize: "1.25rem" },
2772
+ subtitle1: { fontWeight: 600, fontSize: "0.95rem" },
2773
+ body2: { fontSize: "0.85rem" }
2774
+ },
2775
+ components: {
2776
+ MuiAutocomplete: {
2777
+ defaultProps: {
2778
+ noOptionsText: "\u041D\u0456\u0447\u043E\u0433\u043E \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E"
2779
+ }
2780
+ },
2781
+ MuiListItem: {
2782
+ styleOverrides: {
2783
+ root: {
2784
+ transition: "all 0.2s ease-in-out",
2785
+ "&:hover": {
2786
+ backgroundColor: currentMode === "light" ? "#f1f5f9" : "#334155"
2787
+ }
2788
+ }
2789
+ }
2790
+ },
2791
+ MuiListItemButton: {
2792
+ styleOverrides: {
2793
+ root: {
2794
+ transition: "all 0.2s ease-in-out",
2795
+ "&:hover": {
2796
+ backgroundColor: currentMode === "light" ? "#f1f5f9" : "#334155"
2797
+ }
2798
+ }
2799
+ }
2800
+ },
2801
+ MuiButton: {
2802
+ styleOverrides: {
2803
+ root: {
2804
+ textTransform: "none",
2805
+ fontWeight: 600,
2806
+ borderRadius: `${config.borderRadius}px`,
2807
+ padding: "10px 24px",
2808
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
2809
+ }
2810
+ }
2811
+ },
2812
+ MuiPaper: {
2813
+ styleOverrides: {
2814
+ rounded: {
2815
+ borderRadius: `${config.borderRadius * 1.25}px`
2816
+ }
2699
2817
  }
2700
- return { translations };
2701
- }
2702
- }
2703
- if (params.targetLang) {
2704
- const fallbackUrl = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${params.targetLang}&dt=t&q=${encodeURIComponent(params.text)}`;
2705
- const res = await fetch(fallbackUrl);
2706
- if (res.ok) {
2707
- const data = await res.json();
2708
- return { translatedText: data[0].map((x) => x[0]).join("") };
2709
2818
  }
2710
2819
  }
2711
- throw new Error("All translation strategies failed.");
2712
- }
2820
+ });
2713
2821
  };
2714
-
2715
- // src/i18n/TranslationDialog.tsx
2716
- import { jsx as jsx6, jsxs } from "react/jsx-runtime";
2717
- var TranslationDialog = ({
2718
- open,
2719
- onClose,
2720
- translationKey,
2721
- namespace,
2722
- onSave
2723
- }) => {
2822
+ var ThemeContext = createContext6(void 0);
2823
+ var THEME_STORAGE_KEY = "docsua_custom_theme_config";
2824
+ var THEME_MODE_STORAGE_KEY = "docsua_custom_theme_mode";
2825
+ var ThemeProvider = ({ children }) => {
2724
2826
  const supabase = useSupabase();
2725
- const { generate } = useAI();
2726
- const [languages, setLanguages] = useState3([]);
2727
- const [values, setValues] = useState3({});
2728
- const [contextHint, setContextHint] = useState3("");
2729
- const [loading, setLoading] = useState3(false);
2730
- const [saving, setSaving] = useState3(false);
2731
- const [translatingCode, setTranslatingCode] = useState3(null);
2732
- const [isTranslatingAll, setIsTranslatingAll] = useState3(false);
2733
- const [error, setError] = useState3(null);
2734
- useEffect3(() => {
2735
- if (!open) return;
2736
- const fetchData = async () => {
2737
- setLoading(true);
2738
- setError(null);
2827
+ const { user, isAuthenticated, loading: isAuthLoading } = useAuth();
2828
+ const userId = user?.id;
2829
+ const [themeConfig, setThemeConfig] = useState4(() => {
2830
+ const saved = localStorage.getItem(THEME_STORAGE_KEY);
2831
+ if (saved) {
2739
2832
  try {
2740
- const { data: langs, error: langErr } = await supabase.from("languages").select("code, name, svg_flag").eq("enabled", true);
2741
- if (langErr) throw langErr;
2742
- setLanguages(langs || []);
2743
- const { data: trans, error: transErr } = await supabase.from("translations").select("lang_code, value, context_hint").eq("namespace", namespace).eq("key", translationKey);
2744
- if (transErr) throw transErr;
2745
- const initialValues = {};
2746
- let existingHint = "";
2747
- langs?.forEach((l) => {
2748
- const found = trans?.find((t2) => t2.lang_code === l.code);
2749
- initialValues[l.code] = found ? found.value : "";
2750
- if (found?.context_hint) {
2751
- existingHint = found.context_hint;
2752
- }
2753
- });
2754
- setValues(initialValues);
2755
- setContextHint(existingHint);
2756
- } catch (err) {
2757
- console.error("Failed to load translations:", err);
2758
- setError(err?.message || "Failed to load initial translation data.");
2759
- } finally {
2760
- setLoading(false);
2761
- }
2762
- };
2763
- fetchData();
2764
- }, [open, translationKey, namespace]);
2765
- const handleAutoTranslate = async (e, targetLang) => {
2766
- e.preventDefault();
2767
- e.stopPropagation();
2768
- setError(null);
2769
- setTranslatingCode(targetLang);
2770
- try {
2771
- const res = await translationService.translateSmart(supabase, generate, {
2772
- text: translationKey,
2773
- targetLang,
2774
- contextHint
2775
- });
2776
- if (res?.translatedText) {
2777
- setValues((prev) => ({ ...prev, [targetLang]: res.translatedText }));
2833
+ return JSON.parse(saved);
2834
+ } catch (e) {
2778
2835
  }
2779
- } catch (err) {
2780
- console.log(err);
2781
- setError(`Failed to translate into ${targetLang}. ${err?.message || ""}`);
2782
- } finally {
2783
- setTranslatingCode(null);
2784
2836
  }
2785
- };
2786
- const handleTranslateAll = async (e) => {
2787
- e.preventDefault();
2788
- e.stopPropagation();
2789
- if (!translationKey || languages.length === 0) return;
2790
- setIsTranslatingAll(true);
2791
- setError(null);
2792
- try {
2793
- const targetLangs = languages.map((l) => l.code);
2794
- const res = await translationService.translateSmart(supabase, generate, {
2795
- text: translationKey,
2796
- targetLangs,
2797
- contextHint
2798
- });
2799
- if (res?.translations) {
2800
- setValues((prev) => ({ ...prev, ...res.translations }));
2801
- }
2802
- } catch (err) {
2803
- console.error("Batch translation error:", err);
2804
- setError(err?.message || "Failed to translate all languages.");
2805
- } finally {
2806
- setIsTranslatingAll(false);
2837
+ return INITIAL_THEME_CONFIG;
2838
+ });
2839
+ useEffect4(() => {
2840
+ if (isAuthLoading) return;
2841
+ if (isAuthenticated && userId) {
2842
+ const fetchActiveTheme = async () => {
2843
+ try {
2844
+ const { data, error } = await supabase.from("user_themes").select("*").eq("user_id", userId).eq("is_active", true).maybeSingle();
2845
+ if (error) {
2846
+ console.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0435 \u0442\u0435\u043C\u044B \u0438\u0437 Supabase:", error.message);
2847
+ return;
2848
+ }
2849
+ if (data) {
2850
+ const activeTheme = data;
2851
+ const savedMode = localStorage.getItem(THEME_MODE_STORAGE_KEY);
2852
+ setThemeConfig({
2853
+ id: activeTheme.id,
2854
+ name: activeTheme.name || INITIAL_THEME_CONFIG.name,
2855
+ mode: savedMode || activeTheme.config?.mode || "light",
2856
+ borderRadius: activeTheme.config?.borderRadius ?? INITIAL_THEME_CONFIG.borderRadius,
2857
+ colors: activeTheme.config?.colors || INITIAL_THEME_CONFIG.colors,
2858
+ typography: activeTheme.config?.typography || INITIAL_THEME_CONFIG.typography,
2859
+ updatedAt: activeTheme.updated_at || void 0
2860
+ });
2861
+ }
2862
+ } catch (err) {
2863
+ console.error("\u041D\u0435\u043F\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043D\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0437\u0430\u043F\u0440\u043E\u0441\u0435 \u0442\u0435\u043C\u044B:", err);
2864
+ }
2865
+ };
2866
+ fetchActiveTheme();
2867
+ } else {
2868
+ setThemeConfig(INITIAL_THEME_CONFIG);
2869
+ localStorage.removeItem(THEME_STORAGE_KEY);
2870
+ localStorage.removeItem(THEME_MODE_STORAGE_KEY);
2807
2871
  }
2808
- };
2809
- const handleSaveAll = async (e) => {
2810
- e.preventDefault();
2811
- e.stopPropagation();
2812
- setSaving(true);
2813
- setError(null);
2814
- try {
2815
- await onSave(values, contextHint);
2816
- onClose();
2817
- } catch (err) {
2818
- console.error("Failed to save translations:", err);
2819
- setError(err?.message || "Failed to save translations. Please try again.");
2820
- } finally {
2821
- setSaving(false);
2872
+ }, [isAuthenticated, isAuthLoading, userId]);
2873
+ useEffect4(() => {
2874
+ if (isAuthenticated) {
2875
+ localStorage.setItem(THEME_STORAGE_KEY, JSON.stringify(themeConfig));
2876
+ localStorage.setItem(THEME_MODE_STORAGE_KEY, themeConfig.mode);
2822
2877
  }
2878
+ }, [themeConfig, isAuthenticated]);
2879
+ const mode = themeConfig.mode;
2880
+ const setMode = (newMode) => {
2881
+ setThemeConfig((prev) => ({ ...prev, mode: newMode }));
2823
2882
  };
2824
- const handleClose = (_, reason) => {
2825
- if (reason === "backdropClick" && isBusy) return;
2826
- setError(null);
2827
- onClose();
2883
+ const toggleTheme = () => {
2884
+ setMode(mode === "light" ? "dark" : "light");
2828
2885
  };
2829
- const isBusy = loading || saving || isTranslatingAll || translatingCode !== null;
2830
- return /* @__PURE__ */ jsxs(
2831
- Dialog,
2832
- {
2833
- open,
2834
- onClose: handleClose,
2835
- fullWidth: true,
2836
- maxWidth: "sm",
2837
- onClick: (e) => e.stopPropagation(),
2838
- onMouseDown: (e) => e.stopPropagation(),
2839
- children: [
2840
- /* @__PURE__ */ jsxs(DialogTitle, { sx: { pb: 1, display: "flex", justifyContent: "space-between", alignItems: "flex-start" }, children: [
2841
- /* @__PURE__ */ jsxs(Box, { children: [
2842
- "Context Translation",
2843
- /* @__PURE__ */ jsxs(Typography, { component: "div", variant: "subtitle2", color: "text.secondary", sx: { wordBreak: "break-all", mt: 0.5 }, children: [
2844
- "Original: ",
2845
- /* @__PURE__ */ jsxs("strong", { children: [
2846
- '"',
2847
- translationKey,
2848
- '"'
2849
- ] })
2850
- ] })
2851
- ] }),
2852
- !loading && /* @__PURE__ */ jsx6(
2853
- Button,
2854
- {
2855
- type: "button",
2856
- size: "small",
2857
- variant: "outlined",
2858
- color: "secondary",
2859
- startIcon: isTranslatingAll ? /* @__PURE__ */ jsx6(CircularProgress, { size: 16, color: "inherit" }) : /* @__PURE__ */ jsx6(TranslateIcon, { fontSize: "small" }),
2860
- onClick: handleTranslateAll,
2861
- disabled: isBusy,
2862
- sx: { flexShrink: 0, ml: 2, whiteSpace: "nowrap" },
2863
- children: isTranslatingAll ? "Translating..." : "Translate All"
2864
- }
2865
- )
2866
- ] }),
2867
- /* @__PURE__ */ jsxs(DialogContent, { dividers: true, sx: { pt: 2 }, children: [
2868
- error && /* @__PURE__ */ jsx6(Alert, { severity: "error", onClose: () => setError(null), sx: { mb: 2 }, children: error }),
2869
- loading ? /* @__PURE__ */ jsx6(Box, { sx: { display: "flex", justifyContent: "center", py: 4 }, children: /* @__PURE__ */ jsx6(CircularProgress, {}) }) : /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 2.5 }, children: [
2870
- /* @__PURE__ */ jsxs(Paper, { variant: "outlined", sx: { p: 2, bgcolor: "action.hover", borderRadius: 2 }, children: [
2871
- /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1 }, children: [
2872
- /* @__PURE__ */ jsx6(LightbulbIcon, { color: "warning", fontSize: "small" }),
2873
- /* @__PURE__ */ jsx6(Typography, { variant: "subtitle2", sx: { fontWeight: 600 }, children: "Context Hint / Clarification" })
2874
- ] }),
2875
- /* @__PURE__ */ jsx6(
2876
- TextField,
2877
- {
2878
- fullWidth: true,
2879
- multiline: true,
2880
- rows: 2,
2881
- size: "small",
2882
- value: contextHint,
2883
- onChange: (e) => setContextHint(e.target.value),
2884
- placeholder: "e.g. Website navigation menu link to the home page",
2885
- helperText: "This hint explains the exact meaning to AI translation models.",
2886
- disabled: isBusy
2887
- }
2888
- )
2889
- ] }),
2890
- languages.map((lang) => /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2 }, children: [
2891
- /* @__PURE__ */ jsx6(
2892
- Box,
2893
- {
2894
- sx: {
2895
- width: 32,
2896
- height: 22,
2897
- flexShrink: 0,
2898
- borderRadius: "4px",
2899
- overflow: "hidden",
2900
- boxShadow: 1,
2901
- "& svg": { width: "100%", height: "100%", display: "block" }
2902
- },
2903
- dangerouslySetInnerHTML: { __html: lang.svg_flag }
2904
- }
2905
- ),
2906
- /* @__PURE__ */ jsx6(
2907
- TextField,
2908
- {
2909
- fullWidth: true,
2910
- label: lang.name,
2911
- size: "small",
2912
- value: values[lang.code] || "",
2913
- onChange: (e) => setValues((prev) => ({ ...prev, [lang.code]: e.target.value })),
2914
- placeholder: "Enter manual translation...",
2915
- disabled: isBusy
2916
- }
2917
- ),
2918
- /* @__PURE__ */ jsx6(Tooltip, { title: "Translate with context hint", children: /* @__PURE__ */ jsx6("span", { children: /* @__PURE__ */ jsx6(
2919
- IconButton,
2920
- {
2921
- type: "button",
2922
- color: "primary",
2923
- disabled: isBusy,
2924
- onClick: (e) => handleAutoTranslate(e, lang.code),
2925
- children: translatingCode === lang.code ? /* @__PURE__ */ jsx6(CircularProgress, { size: 20, color: "inherit" }) : /* @__PURE__ */ jsx6(AutoFixHighIcon, { fontSize: "small" })
2926
- }
2927
- ) }) })
2928
- ] }, lang.code))
2929
- ] })
2930
- ] }),
2931
- /* @__PURE__ */ jsxs(DialogActions, { sx: { px: 3, py: 2 }, children: [
2932
- /* @__PURE__ */ jsx6(Button, { onClick: onClose, disabled: isBusy, color: "inherit", type: "button", children: "Cancel" }),
2933
- /* @__PURE__ */ jsx6(
2934
- Button,
2935
- {
2936
- onClick: handleSaveAll,
2937
- variant: "contained",
2938
- disabled: isBusy,
2939
- type: "button",
2940
- children: saving ? "Saving..." : "Save All"
2941
- }
2942
- )
2943
- ] })
2944
- ]
2945
- }
2946
- );
2886
+ const theme = useMemo3(() => buildMuiTheme(themeConfig), [themeConfig]);
2887
+ const displayState = {
2888
+ isMobile: useMediaQuery(theme.breakpoints.down("sm")),
2889
+ isTablet: useMediaQuery(theme.breakpoints.between("sm", "md")),
2890
+ isDesktop: useMediaQuery(theme.breakpoints.between("md", "lg")),
2891
+ isWideDesktop: useMediaQuery(theme.breakpoints.between("lg", "xl")),
2892
+ isSuperWideDesktop: useMediaQuery(theme.breakpoints.up("xl"))
2893
+ };
2894
+ return /* @__PURE__ */ jsx6(ThemeContext.Provider, { value: { mode, toggleTheme, setMode, theme, themeConfig, setThemeConfig, displayState }, children: /* @__PURE__ */ jsxs(MuiThemeProvider, { theme, children: [
2895
+ /* @__PURE__ */ jsx6(CssBaseline, {}),
2896
+ children
2897
+ ] }) });
2898
+ };
2899
+ var useAppTheme = () => {
2900
+ const context = useContext6(ThemeContext);
2901
+ if (!context) {
2902
+ throw new Error("useAppTheme should be used within the ThemeProvider");
2903
+ }
2904
+ return context;
2947
2905
  };
2948
2906
 
2949
- // src/i18n/TranslateText.tsx
2950
- import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
2951
- var TranslateText = ({
2952
- overrideNamespace,
2953
- children,
2954
- values
2955
- }) => {
2956
- const namespace = useTranslationNamespace(overrideNamespace);
2957
- const { t: t2, i18n } = useTranslation(namespace);
2958
- const { isTranslationMode, canEdit } = useTranslationEdit();
2959
- const supabase = useSupabase();
2960
- const [anchorEl, setAnchorEl] = useState4(null);
2961
- const [isDialogOpen, setIsDialogOpen] = useState4(false);
2962
- const timeoutRef = useRef2(null);
2963
- const translatedValue = t2(children, values);
2964
- useEffect4(() => {
2965
- return () => {
2966
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
2967
- };
2968
- }, []);
2969
- if (!canEdit || !isTranslationMode) {
2970
- return /* @__PURE__ */ jsx7(Fragment, { children: translatedValue });
2907
+ // src/context/BreadcrumbProvider.tsx
2908
+ import { createContext as createContext7, useContext as useContext7, useState as useState5 } from "react";
2909
+ import { jsx as jsx7 } from "react/jsx-runtime";
2910
+ var BreadcrumbContext = createContext7({
2911
+ breadcrumbTitles: {},
2912
+ setBreadcrumbTitle: () => {
2971
2913
  }
2972
- const handleSaveTranslations = async (newValues, contextHint) => {
2973
- const upsertData = Object.entries(newValues).map(([langCode, text]) => ({
2974
- lang_code: langCode,
2975
- namespace,
2976
- key: children,
2977
- value: text,
2978
- context_hint: contextHint
2979
- }));
2980
- const { error } = await supabase.from("translations").upsert(upsertData, { onConflict: "lang_code,namespace,key" });
2981
- if (error) {
2982
- alert("\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0438 \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043D\u043D\u044B\u0445");
2983
- throw error;
2984
- }
2985
- await translationService.incrementVersion(supabase, namespace, Object.keys(newValues));
2986
- Object.entries(newValues).forEach(([langCode, text]) => {
2987
- i18n.addResource(langCode, namespace, children, text);
2914
+ });
2915
+ var BreadcrumbProvider = ({ children }) => {
2916
+ const [breadcrumbTitles, setBreadcrumbTitles] = useState5({});
2917
+ const setBreadcrumbTitle = (path, title) => {
2918
+ setBreadcrumbTitles((prev) => {
2919
+ if (prev[path] === title) return prev;
2920
+ return { ...prev, [path]: title };
2988
2921
  });
2989
2922
  };
2990
- const handleMouseEnter = (event) => {
2991
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
2992
- setAnchorEl(event.currentTarget);
2993
- };
2994
- const handleMouseLeave = () => {
2995
- timeoutRef.current = setTimeout(() => {
2996
- setAnchorEl(null);
2997
- }, 200);
2998
- };
2999
- const handleOpenDialog = (e) => {
3000
- e.preventDefault();
3001
- e.stopPropagation();
3002
- setAnchorEl(null);
3003
- if (document.activeElement instanceof HTMLElement) {
3004
- document.activeElement.blur();
3005
- }
3006
- setIsDialogOpen(true);
3007
- };
3008
- const handleLaunchAction = (_) => {
3009
- setAnchorEl(null);
3010
- };
3011
- const open = Boolean(anchorEl);
3012
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
3013
- /* @__PURE__ */ jsx7(
3014
- Box2,
3015
- {
3016
- component: "span",
3017
- onMouseEnter: handleMouseEnter,
3018
- onMouseLeave: handleMouseLeave,
3019
- onClick: (e) => {
3020
- e.preventDefault();
3021
- e.stopPropagation();
3022
- },
3023
- sx: {
3024
- display: "inline-block",
3025
- position: "relative",
3026
- borderRadius: "4px",
3027
- padding: "0 3px",
3028
- margin: "0 1px",
3029
- border: "1px dashed #f59e0b",
3030
- backgroundColor: "rgba(254, 243, 199, 0.35)",
3031
- cursor: "pointer",
3032
- transition: "all 0.2s ease-in-out",
3033
- userSelect: "none",
3034
- "&:hover": {
3035
- backgroundColor: "#fef08a",
3036
- borderColor: "#d97706",
3037
- boxShadow: "0 0 0 2px rgba(245, 158, 11, 0.35)",
3038
- transform: "scale(1.02)"
3039
- }
3040
- },
3041
- children: translatedValue
3042
- }
3043
- ),
3044
- /* @__PURE__ */ jsx7(
3045
- Popper,
2923
+ return /* @__PURE__ */ jsx7(BreadcrumbContext.Provider, { value: { breadcrumbTitles, setBreadcrumbTitle }, children });
2924
+ };
2925
+ var useBreadcrumb = () => useContext7(BreadcrumbContext);
2926
+
2927
+ // src/context/ToastProvider.tsx
2928
+ import { createContext as createContext8, useContext as useContext8, useState as useState6, useCallback as useCallback3, useMemo as useMemo4 } from "react";
2929
+ import { Snackbar, Alert } from "@mui/material";
2930
+ import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
2931
+ var ToastContext = createContext8(void 0);
2932
+ var ToastProvider = ({ children }) => {
2933
+ const [toast, setToast] = useState6(null);
2934
+ const showToast = useCallback3((text, type = "info") => {
2935
+ setToast({ text, type });
2936
+ }, []);
2937
+ const hideToast = useCallback3(() => {
2938
+ setToast(null);
2939
+ }, []);
2940
+ const contextValue = useMemo4(() => ({
2941
+ showToast,
2942
+ hideToast
2943
+ }), [showToast, hideToast]);
2944
+ return /* @__PURE__ */ jsxs2(ToastContext.Provider, { value: contextValue, children: [
2945
+ children,
2946
+ /* @__PURE__ */ jsx8(
2947
+ Snackbar,
3046
2948
  {
3047
- open,
3048
- anchorEl,
3049
- placement: "bottom-start",
3050
- style: { zIndex: 1300 },
3051
- children: /* @__PURE__ */ jsx7(
3052
- Paper2,
2949
+ open: Boolean(toast),
2950
+ autoHideDuration: 3500,
2951
+ onClose: hideToast,
2952
+ anchorOrigin: { vertical: "bottom", horizontal: "center" },
2953
+ children: /* @__PURE__ */ jsx8(
2954
+ Alert,
3053
2955
  {
3054
- elevation: 4,
3055
- onMouseEnter: () => {
3056
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
3057
- },
3058
- onMouseLeave: handleMouseLeave,
3059
- sx: {
3060
- mt: 0.5,
3061
- borderRadius: 2,
3062
- minWidth: 140,
3063
- boxShadow: "0px 10px 15px -3px rgba(0,0,0,0.1)"
3064
- },
3065
- children: /* @__PURE__ */ jsxs2(MenuList, { dense: true, sx: { py: 0.5 }, children: [
3066
- /* @__PURE__ */ jsxs2(MenuItem, { onClick: handleOpenDialog, sx: { py: 0.8, px: 1.5 }, children: [
3067
- /* @__PURE__ */ jsx7(ListItemIcon, { sx: { minWidth: "28px !important" }, children: /* @__PURE__ */ jsx7(TranslateIcon2, { fontSize: "small", sx: { color: "#d97706" } }) }),
3068
- /* @__PURE__ */ jsx7(
3069
- ListItemText,
3070
- {
3071
- primary: "Localization",
3072
- slotProps: {
3073
- primary: {
3074
- sx: {
3075
- fontSize: "0.825rem",
3076
- fontWeight: 600,
3077
- color: "text.primary"
3078
- }
3079
- }
3080
- }
3081
- }
3082
- )
3083
- ] }),
3084
- /* @__PURE__ */ jsxs2(MenuItem, { onClick: handleLaunchAction, sx: { py: 0.8, px: 1.5 }, children: [
3085
- /* @__PURE__ */ jsx7(ListItemIcon, { sx: { minWidth: "28px !important" }, children: /* @__PURE__ */ jsx7(LaunchIcon, { fontSize: "small", color: "secondary" }) }),
3086
- /* @__PURE__ */ jsx7(
3087
- ListItemText,
3088
- {
3089
- primary: "Launch action",
3090
- slotProps: {
3091
- primary: {
3092
- sx: {
3093
- fontSize: "0.825rem",
3094
- fontWeight: 600,
3095
- color: "text.primary"
3096
- }
3097
- }
3098
- }
3099
- }
3100
- )
3101
- ] })
3102
- ] })
2956
+ severity: toast?.type || "info",
2957
+ variant: "filled",
2958
+ onClose: hideToast,
2959
+ children: toast?.text
3103
2960
  }
3104
2961
  )
3105
2962
  }
3106
- ),
3107
- /* @__PURE__ */ jsx7("span", { style: { display: "inline" }, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx7(
3108
- TranslationDialog,
3109
- {
3110
- open: isDialogOpen,
3111
- onClose: () => setIsDialogOpen(false),
3112
- translationKey: children,
3113
- namespace,
3114
- onSave: handleSaveTranslations
3115
- }
3116
- ) })
2963
+ )
3117
2964
  ] });
3118
2965
  };
2966
+ var useToast = () => {
2967
+ const context = useContext8(ToastContext);
2968
+ if (!context) {
2969
+ throw new Error("useToast must be used within a ToastProvider");
2970
+ }
2971
+ return context;
2972
+ };
3119
2973
 
3120
- // src/UI/components/data.display/TranslatableTooltipComponent.tsx
3121
- import { Tooltip as Tooltip2 } from "@mui/material";
3122
- import { jsx as jsx8 } from "react/jsx-runtime";
3123
- var TranslatableTooltipComponent = ({
3124
- overrideNamespace,
3125
- title,
3126
- disableSpan,
3127
- children,
3128
- ...props
3129
- }) => {
3130
- const { isTranslationMode } = useTranslationEdit();
3131
- return /* @__PURE__ */ jsx8(
3132
- Tooltip2,
3133
- {
3134
- title: typeof title === "string" ? /* @__PURE__ */ jsx8(TranslateText, { overrideNamespace, children: title }) : title,
3135
- slotProps: {
3136
- popper: {
3137
- sx: isTranslationMode ? {
3138
- pointerEvents: "auto",
3139
- // Разрешаем клики внутри тултипа
3140
- "& .MuiTooltip-tooltip": {
3141
- pointerEvents: "auto"
3142
- }
3143
- } : void 0
2974
+ // src/context/ConfirmationProvider.tsx
2975
+ import { createContext as createContext10, useContext as useContext10, useState as useState9, useCallback as useCallback4 } from "react";
2976
+ import {
2977
+ Button as Button2,
2978
+ Dialog as Dialog2,
2979
+ DialogActions as DialogActions2,
2980
+ DialogContent as DialogContent2,
2981
+ DialogContentText,
2982
+ DialogTitle as DialogTitle2,
2983
+ Box as Box3
2984
+ } from "@mui/material";
2985
+ import {
2986
+ Error as ErrorIcon,
2987
+ WarningAmberOutlined as WarningIcon,
2988
+ InfoOutlined as InfoIcon,
2989
+ CheckCircleOutlined as SuccessIcon
2990
+ } from "@mui/icons-material";
2991
+
2992
+ // src/i18n/TranslateText.tsx
2993
+ import { useState as useState8, useRef as useRef2, useEffect as useEffect6 } from "react";
2994
+ import { useTranslation as useTranslation2 } from "react-i18next";
2995
+ import { Box as Box2, Popper, Paper as Paper2, MenuList, MenuItem, ListItemIcon, ListItemText } from "@mui/material";
2996
+ import TranslateIcon2 from "@mui/icons-material/Translate";
2997
+ import LaunchIcon from "@mui/icons-material/Launch";
2998
+
2999
+ // src/i18n/TranslationDialog.tsx
3000
+ import { useEffect as useEffect5, useState as useState7 } from "react";
3001
+ import {
3002
+ Dialog,
3003
+ DialogTitle,
3004
+ DialogContent,
3005
+ DialogActions,
3006
+ Button,
3007
+ TextField,
3008
+ Box,
3009
+ Typography,
3010
+ CircularProgress,
3011
+ IconButton,
3012
+ Tooltip,
3013
+ Paper,
3014
+ Alert as Alert2
3015
+ } from "@mui/material";
3016
+ import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
3017
+ import TranslateIcon from "@mui/icons-material/Translate";
3018
+ import LightbulbIcon from "@mui/icons-material/Lightbulb";
3019
+
3020
+ // src/services/translation.service.ts
3021
+ var translationService = {
3022
+ // 1. Принимаем client первым аргументом
3023
+ incrementVersion: async (client, namespace, langCodes) => {
3024
+ try {
3025
+ const { data } = await client.from("translation_versions").select("version, lang_code").eq("namespace", namespace);
3026
+ const currentVersion = data && data.length > 0 ? Math.max(...data.map((d) => d.version || 0)) : 0;
3027
+ const nextVersion = currentVersion + 1;
3028
+ const languagesToUpdate = langCodes && langCodes.length > 0 ? langCodes : ["uk"];
3029
+ const updates = languagesToUpdate.map((lang_code) => ({
3030
+ namespace,
3031
+ lang_code,
3032
+ version: nextVersion,
3033
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3034
+ }));
3035
+ const { error } = await client.from("translation_versions").upsert(updates, { onConflict: "namespace,lang_code" });
3036
+ if (error) console.error("Error updating translation version:", error);
3037
+ return nextVersion;
3038
+ } catch (err) {
3039
+ console.error("Failed to increment version in Supabase:", err);
3040
+ return null;
3041
+ }
3042
+ },
3043
+ translateSmart: async (client, aiGenerateFn, params) => {
3044
+ const languagesToTranslate = params.targetLangs || (params.targetLang ? [params.targetLang] : []);
3045
+ try {
3046
+ const { data, error } = await client.functions.invoke("translate-smart", { body: params });
3047
+ if (!error && data) return data;
3048
+ } catch {
3049
+ console.warn("Edge Function translate-smart \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C AIProvider...");
3050
+ }
3051
+ if (aiGenerateFn) {
3052
+ const prompt = `Translate the source text into language codes: ${JSON.stringify(languagesToTranslate)}.
3053
+ Source Text: "${params.text}"
3054
+ ${params.contextHint ? `Context Hint: "${params.contextHint}"` : ""}`;
3055
+ const translations = await aiGenerateFn({
3056
+ prompt,
3057
+ systemInstruction: 'Respond STRICTLY with a valid JSON mapping lang code to text. Example: {"en": "Hello"}',
3058
+ jsonMode: true
3059
+ });
3060
+ if (translations) {
3061
+ if (params.targetLang) {
3062
+ return { translatedText: translations[params.targetLang] || params.text };
3144
3063
  }
3145
- },
3146
- leaveDelay: isTranslationMode ? 1e3 : 100,
3147
- ...props,
3148
- children: disableSpan ? children : /* @__PURE__ */ jsx8("span", { children })
3064
+ return { translations };
3065
+ }
3149
3066
  }
3150
- );
3067
+ if (params.targetLang) {
3068
+ const fallbackUrl = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${params.targetLang}&dt=t&q=${encodeURIComponent(params.text)}`;
3069
+ const res = await fetch(fallbackUrl);
3070
+ if (res.ok) {
3071
+ const data = await res.json();
3072
+ return { translatedText: data[0].map((x) => x[0]).join("") };
3073
+ }
3074
+ }
3075
+ throw new Error("All translation strategies failed.");
3076
+ }
3151
3077
  };
3152
3078
 
3153
- // src/context/LocalizationProvider.tsx
3154
- import { createContext as createContext6, useContext as useContext6, useMemo as useMemo3, useEffect as useEffect5, useState as useState5 } from "react";
3155
- import { useTranslation as useTranslation2 } from "react-i18next";
3156
- import moment from "moment/min/moment-with-locales";
3079
+ // src/context/AIProvider.tsx
3080
+ import { createContext as createContext9, useContext as useContext9, useMemo as useMemo5 } from "react";
3081
+ import { GoogleGenAI } from "@google/genai";
3157
3082
  import { jsx as jsx9 } from "react/jsx-runtime";
3158
- var DEFAULT_DATETIME_FORMATS = {
3159
- uk: "DD.MM.YYYY, HH:mm",
3160
- de: "DD.MM.YYYY, HH:mm",
3161
- en: "MM/DD/YYYY, hh:mm A",
3162
- es: "DD/MM/YYYY, HH:mm",
3163
- fr: "DD/MM/YYYY, HH:mm",
3164
- it: "DD/MM/YYYY, HH:mm"
3165
- };
3166
- var DEFAULT_DATE_FORMATS = {
3167
- uk: "DD.MM.YYYY",
3168
- de: "DD.MM.YYYY",
3169
- en: "MM/DD/YYYY",
3170
- es: "DD/MM/YYYY",
3171
- fr: "DD/MM/YYYY",
3172
- it: "DD/MM/YYYY"
3083
+ var AIContext = createContext9(null);
3084
+ var AIProvider = ({
3085
+ children,
3086
+ config,
3087
+ apiKey: directApiKey
3088
+ }) => {
3089
+ const apiKey = directApiKey || config?.apiKey;
3090
+ const provider = config?.provider || "gemini";
3091
+ const modelName = config?.modelName || (provider === "gemini" ? "gemini-2.5-flash" : "gpt-4o-mini");
3092
+ const contextValue = useMemo5(() => {
3093
+ return {
3094
+ apiKey,
3095
+ provider,
3096
+ modelName,
3097
+ generate: async ({
3098
+ prompt,
3099
+ systemInstruction,
3100
+ jsonMode = false,
3101
+ temperature
3102
+ }) => {
3103
+ if (!apiKey) {
3104
+ console.warn("[AIProvider]: API Key \u043D\u0435 \u0437\u0430\u0434\u0430\u043D.");
3105
+ return null;
3106
+ }
3107
+ if (provider === "gemini") {
3108
+ try {
3109
+ const ai = new GoogleGenAI({ apiKey });
3110
+ const response = await ai.models.generateContent({
3111
+ model: modelName,
3112
+ contents: prompt,
3113
+ config: {
3114
+ systemInstruction,
3115
+ temperature,
3116
+ responseMimeType: jsonMode ? "application/json" : "text/plain"
3117
+ }
3118
+ });
3119
+ const rawText = response.text?.trim() || "";
3120
+ if (jsonMode) {
3121
+ const cleanedText = rawText.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
3122
+ return JSON.parse(cleanedText);
3123
+ }
3124
+ return rawText;
3125
+ } catch (err) {
3126
+ console.error("[AIProvider Gemini Error]:", err);
3127
+ return null;
3128
+ }
3129
+ }
3130
+ if (provider === "openai") {
3131
+ try {
3132
+ const messages = [];
3133
+ if (systemInstruction) {
3134
+ messages.push({ role: "system", content: systemInstruction });
3135
+ }
3136
+ messages.push({ role: "user", content: prompt });
3137
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
3138
+ method: "POST",
3139
+ headers: {
3140
+ "Content-Type": "application/json",
3141
+ Authorization: `Bearer ${apiKey}`
3142
+ },
3143
+ body: JSON.stringify({
3144
+ model: modelName,
3145
+ messages,
3146
+ temperature,
3147
+ ...jsonMode ? { response_format: { type: "json_object" } } : {}
3148
+ })
3149
+ });
3150
+ const data = await res.json();
3151
+ const content = data.choices?.[0]?.message?.content?.trim() || "";
3152
+ if (jsonMode) {
3153
+ return JSON.parse(content);
3154
+ }
3155
+ return content;
3156
+ } catch (err) {
3157
+ console.error("[AIProvider OpenAI Error]:", err);
3158
+ return null;
3159
+ }
3160
+ }
3161
+ return null;
3162
+ }
3163
+ };
3164
+ }, [apiKey, provider, modelName]);
3165
+ return /* @__PURE__ */ jsx9(AIContext.Provider, { value: contextValue, children });
3173
3166
  };
3174
- var DEFAULT_TIME_FORMATS = {
3175
- uk: "HH:mm:ss",
3176
- de: "HH:mm:ss",
3177
- en: "hh:mm:ss A",
3178
- es: "HH:mm:ss",
3179
- fr: "HH:mm:ss",
3180
- it: "HH:mm:ss"
3167
+ var useAI = () => {
3168
+ const context = useContext9(AIContext);
3169
+ if (!context) {
3170
+ throw new Error("useAI \u0434\u043E\u043B\u0436\u0435\u043D \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C\u0441\u044F \u0432\u043D\u0443\u0442\u0440\u0438 <AIProvider>");
3171
+ }
3172
+ return context;
3181
3173
  };
3182
- var LocalizationContext = createContext6(void 0);
3183
- var LocalizationProvider = ({ children }) => {
3174
+
3175
+ // src/i18n/TranslationDialog.tsx
3176
+ import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
3177
+ var TranslationDialog = ({
3178
+ open,
3179
+ onClose,
3180
+ translationKey,
3181
+ namespace,
3182
+ onSave
3183
+ }) => {
3184
3184
  const supabase = useSupabase();
3185
- const { i18n } = useTranslation2();
3186
- const [languages, setLanguages] = useState5([]);
3187
- const [isLoadingLangs, setIsLoadingLangs] = useState5(true);
3188
- const [languagesError, setLanguagesError] = useState5(null);
3185
+ const { generate } = useAI();
3186
+ const [languages, setLanguages] = useState7([]);
3187
+ const [values, setValues] = useState7({});
3188
+ const [contextHint, setContextHint] = useState7("");
3189
+ const [loading, setLoading] = useState7(false);
3190
+ const [saving, setSaving] = useState7(false);
3191
+ const [translatingCode, setTranslatingCode] = useState7(null);
3192
+ const [isTranslatingAll, setIsTranslatingAll] = useState7(false);
3193
+ const [error, setError] = useState7(null);
3189
3194
  useEffect5(() => {
3190
- let isMounted = true;
3191
- const fetchLanguages = async () => {
3195
+ if (!open) return;
3196
+ const fetchData = async () => {
3197
+ setLoading(true);
3198
+ setError(null);
3192
3199
  try {
3193
- setIsLoadingLangs(true);
3194
- setLanguagesError(null);
3195
- const { data: langs, error: langErr } = await supabase.from("languages").select("code, name, svg_flag, enabled, created_at").eq("enabled", true).order("code", { ascending: true });
3196
- if (langErr) {
3197
- throw langErr;
3198
- }
3199
- if (isMounted && langs) {
3200
- setLanguages(langs);
3201
- }
3200
+ const { data: langs, error: langErr } = await supabase.from("languages").select("code, name, svg_flag").eq("enabled", true);
3201
+ if (langErr) throw langErr;
3202
+ setLanguages(langs || []);
3203
+ const { data: trans, error: transErr } = await supabase.from("translations").select("lang_code, value, context_hint").eq("namespace", namespace).eq("key", translationKey);
3204
+ if (transErr) throw transErr;
3205
+ const initialValues = {};
3206
+ let existingHint = "";
3207
+ langs?.forEach((l) => {
3208
+ const found = trans?.find((t2) => t2.lang_code === l.code);
3209
+ initialValues[l.code] = found ? found.value : "";
3210
+ if (found?.context_hint) {
3211
+ existingHint = found.context_hint;
3212
+ }
3213
+ });
3214
+ setValues(initialValues);
3215
+ setContextHint(existingHint);
3202
3216
  } catch (err) {
3203
- console.error("Failed to load languages from Supabase:", err);
3204
- if (isMounted) {
3205
- setLanguagesError(err.message || "Error loading languages");
3206
- }
3217
+ console.error("Failed to load translations:", err);
3218
+ setError(err?.message || "Failed to load initial translation data.");
3207
3219
  } finally {
3208
- if (isMounted) {
3209
- setIsLoadingLangs(false);
3210
- }
3220
+ setLoading(false);
3211
3221
  }
3212
3222
  };
3213
- fetchLanguages();
3214
- return () => {
3215
- isMounted = false;
3216
- };
3217
- }, [supabase]);
3218
- const rawLang = i18n.language || "uk";
3219
- const currentLang = rawLang.split("-")[0].toLowerCase();
3220
- const getDateFormat = () => {
3221
- return DEFAULT_DATE_FORMATS[currentLang] || "DD.MM.YYYY";
3222
- };
3223
- const getTimeFormat = () => {
3224
- return DEFAULT_TIME_FORMATS[currentLang] || "HH:mm:ss";
3225
- };
3226
- const getDateTimeFormat = () => {
3227
- return DEFAULT_DATETIME_FORMATS[currentLang] || "DD.MM.YYYY, HH:mm";
3228
- };
3229
- const formatDateTime = (date, customPattern) => {
3230
- if (!date) return "\u2014";
3231
- const pattern = customPattern || DEFAULT_DATETIME_FORMATS[currentLang] || "DD.MM.YYYY, HH:mm";
3232
- return moment(date).locale(currentLang).format(pattern);
3233
- };
3234
- const formatDate = (date, customPattern) => {
3235
- if (!date) return "\u2014";
3236
- const pattern = customPattern || DEFAULT_DATE_FORMATS[currentLang] || "DD.MM.YYYY";
3237
- return moment(date).locale(currentLang).format(pattern);
3223
+ fetchData();
3224
+ }, [open, translationKey, namespace]);
3225
+ const handleAutoTranslate = async (e, targetLang) => {
3226
+ e.preventDefault();
3227
+ e.stopPropagation();
3228
+ setError(null);
3229
+ setTranslatingCode(targetLang);
3230
+ try {
3231
+ const res = await translationService.translateSmart(supabase, generate, {
3232
+ text: translationKey,
3233
+ targetLang,
3234
+ contextHint
3235
+ });
3236
+ if (res?.translatedText) {
3237
+ setValues((prev) => ({ ...prev, [targetLang]: res.translatedText }));
3238
+ }
3239
+ } catch (err) {
3240
+ console.log(err);
3241
+ setError(`Failed to translate into ${targetLang}. ${err?.message || ""}`);
3242
+ } finally {
3243
+ setTranslatingCode(null);
3244
+ }
3238
3245
  };
3239
- const formatTime = (date, customPattern) => {
3240
- if (!date) return "\u2014";
3241
- const pattern = customPattern || DEFAULT_TIME_FORMATS[currentLang] || "HH:mm:ss";
3242
- return moment(date).locale(currentLang).format(pattern);
3246
+ const handleTranslateAll = async (e) => {
3247
+ e.preventDefault();
3248
+ e.stopPropagation();
3249
+ if (!translationKey || languages.length === 0) return;
3250
+ setIsTranslatingAll(true);
3251
+ setError(null);
3252
+ try {
3253
+ const targetLangs = languages.map((l) => l.code);
3254
+ const res = await translationService.translateSmart(supabase, generate, {
3255
+ text: translationKey,
3256
+ targetLangs,
3257
+ contextHint
3258
+ });
3259
+ if (res?.translations) {
3260
+ setValues((prev) => ({ ...prev, ...res.translations }));
3261
+ }
3262
+ } catch (err) {
3263
+ console.error("Batch translation error:", err);
3264
+ setError(err?.message || "Failed to translate all languages.");
3265
+ } finally {
3266
+ setIsTranslatingAll(false);
3267
+ }
3243
3268
  };
3244
- const formatRelativeTime = (date, withoutSuffix = false) => {
3245
- if (!date) return "\u2014";
3246
- return moment(date).locale(currentLang).fromNow(withoutSuffix);
3269
+ const handleSaveAll = async (e) => {
3270
+ e.preventDefault();
3271
+ e.stopPropagation();
3272
+ setSaving(true);
3273
+ setError(null);
3274
+ try {
3275
+ await onSave(values, contextHint);
3276
+ onClose();
3277
+ } catch (err) {
3278
+ console.error("Failed to save translations:", err);
3279
+ setError(err?.message || "Failed to save translations. Please try again.");
3280
+ } finally {
3281
+ setSaving(false);
3282
+ }
3247
3283
  };
3248
- const changeLanguage2 = async (lang) => {
3249
- await i18n.changeLanguage(lang);
3284
+ const handleClose = (_, reason) => {
3285
+ if (reason === "backdropClick" && isBusy) return;
3286
+ setError(null);
3287
+ onClose();
3250
3288
  };
3251
- const value = useMemo3(
3252
- () => ({
3253
- currentLang,
3254
- languages,
3255
- isLoadingLangs,
3256
- languagesError,
3257
- changeLanguage: changeLanguage2,
3258
- getDateFormat,
3259
- getTimeFormat,
3260
- getDateTimeFormat,
3261
- formatDateTime,
3262
- formatDate,
3263
- formatTime,
3264
- formatRelativeTime
3265
- }),
3266
- [currentLang, languages, isLoadingLangs, languagesError]
3267
- );
3268
- return /* @__PURE__ */ jsx9(LocalizationContext.Provider, { value, children });
3269
- };
3270
- var useLocalization = () => {
3271
- const context = useContext6(LocalizationContext);
3272
- if (!context) {
3273
- throw new Error("useLocalization must be used within a LocalizationProvider");
3274
- }
3275
- return context;
3276
- };
3277
-
3278
- // src/context/ThemeProvider.tsx
3279
- import { createContext as createContext7, useContext as useContext7, useMemo as useMemo4, useState as useState6, useEffect as useEffect6 } from "react";
3280
- import { createTheme, ThemeProvider as MuiThemeProvider, CssBaseline, useMediaQuery } from "@mui/material";
3281
- import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
3282
- var DEFAULT_LIGHT_COLORS = {
3283
- primary: "#1976d2",
3284
- secondary: "#9c27b0",
3285
- backgroundDefault: "#f5f5f5",
3286
- backgroundPaper: "#ffffff",
3287
- textPrimary: "#0f172a",
3288
- textSecondary: "#475569",
3289
- success: "#2e7d32",
3290
- error: "#d32f2f",
3291
- warning: "#ed6c02",
3292
- info: "#0288d1"
3293
- };
3294
- var DEFAULT_DARK_COLORS = {
3295
- primary: "#90caf9",
3296
- secondary: "#ce93d8",
3297
- backgroundDefault: "#0f172a",
3298
- backgroundPaper: "#1e293b",
3299
- textPrimary: "#f8fafc",
3300
- textSecondary: "#94a3b8",
3301
- success: "#66bb6a",
3302
- error: "#f44336",
3303
- warning: "#ffa726",
3304
- info: "#29b6f6"
3305
- };
3306
- var INITIAL_THEME_CONFIG = {
3307
- name: "Base MUI Theme",
3308
- mode: "light",
3309
- borderRadius: 12,
3310
- colors: {
3311
- light: DEFAULT_LIGHT_COLORS,
3312
- dark: DEFAULT_DARK_COLORS
3313
- },
3314
- typography: {
3315
- fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
3316
- fontSize: 14,
3317
- fontWeightLight: 300,
3318
- fontWeightRegular: 400,
3319
- fontWeightMedium: 600,
3320
- fontWeightBold: 700
3321
- }
3322
- };
3323
- var buildMuiTheme = (config) => {
3324
- const currentMode = config.mode;
3325
- const currentColors = config.colors?.[currentMode] || (currentMode === "light" ? DEFAULT_LIGHT_COLORS : DEFAULT_DARK_COLORS);
3326
- return createTheme({
3327
- palette: {
3328
- mode: currentMode,
3329
- primary: {
3330
- main: currentColors.primary,
3331
- light: currentMode === "light" ? "#63a4ff" : "#a1d2fa",
3332
- dark: currentMode === "light" ? "#004ba0" : "#42a5f5"
3333
- },
3334
- secondary: {
3335
- main: currentColors.secondary
3336
- },
3337
- background: {
3338
- default: currentColors.backgroundDefault,
3339
- paper: currentColors.backgroundPaper
3340
- },
3341
- text: {
3342
- primary: currentColors.textPrimary,
3343
- secondary: currentColors.textSecondary || (currentMode === "light" ? "#475569" : "#94a3b8")
3344
- },
3345
- success: { main: currentColors.success },
3346
- error: { main: currentColors.error },
3347
- warning: { main: currentColors.warning },
3348
- info: { main: currentColors.info }
3349
- },
3350
- shape: {
3351
- borderRadius: config.borderRadius
3352
- },
3353
- typography: {
3354
- fontFamily: config.typography?.fontFamily || INITIAL_THEME_CONFIG.typography.fontFamily,
3355
- fontSize: config.typography?.fontSize || INITIAL_THEME_CONFIG.typography.fontSize,
3356
- fontWeightLight: config.typography?.fontWeightLight || INITIAL_THEME_CONFIG.typography.fontWeightLight,
3357
- fontWeightRegular: config.typography?.fontWeightRegular || INITIAL_THEME_CONFIG.typography.fontWeightRegular,
3358
- fontWeightMedium: config.typography?.fontWeightMedium || INITIAL_THEME_CONFIG.typography.fontWeightMedium,
3359
- fontWeightBold: config.typography?.fontWeightBold || INITIAL_THEME_CONFIG.typography.fontWeightBold,
3360
- h5: { fontWeight: 700, fontSize: "1.25rem" },
3361
- subtitle1: { fontWeight: 600, fontSize: "0.95rem" },
3362
- body2: { fontSize: "0.85rem" }
3363
- },
3364
- components: {
3365
- MuiAutocomplete: {
3366
- defaultProps: {
3367
- noOptionsText: "\u041D\u0456\u0447\u043E\u0433\u043E \u043D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E"
3368
- }
3369
- },
3370
- MuiListItem: {
3371
- styleOverrides: {
3372
- root: {
3373
- transition: "all 0.2s ease-in-out",
3374
- "&:hover": {
3375
- backgroundColor: currentMode === "light" ? "#f1f5f9" : "#334155"
3289
+ const isBusy = loading || saving || isTranslatingAll || translatingCode !== null;
3290
+ return /* @__PURE__ */ jsxs3(
3291
+ Dialog,
3292
+ {
3293
+ open,
3294
+ onClose: handleClose,
3295
+ fullWidth: true,
3296
+ maxWidth: "sm",
3297
+ onClick: (e) => e.stopPropagation(),
3298
+ onMouseDown: (e) => e.stopPropagation(),
3299
+ children: [
3300
+ /* @__PURE__ */ jsxs3(DialogTitle, { sx: { pb: 1, display: "flex", justifyContent: "space-between", alignItems: "flex-start" }, children: [
3301
+ /* @__PURE__ */ jsxs3(Box, { children: [
3302
+ "Context Translation",
3303
+ /* @__PURE__ */ jsxs3(Typography, { component: "div", variant: "subtitle2", color: "text.secondary", sx: { wordBreak: "break-all", mt: 0.5 }, children: [
3304
+ "Original: ",
3305
+ /* @__PURE__ */ jsxs3("strong", { children: [
3306
+ '"',
3307
+ translationKey,
3308
+ '"'
3309
+ ] })
3310
+ ] })
3311
+ ] }),
3312
+ !loading && /* @__PURE__ */ jsx10(
3313
+ Button,
3314
+ {
3315
+ type: "button",
3316
+ size: "small",
3317
+ variant: "outlined",
3318
+ color: "secondary",
3319
+ startIcon: isTranslatingAll ? /* @__PURE__ */ jsx10(CircularProgress, { size: 16, color: "inherit" }) : /* @__PURE__ */ jsx10(TranslateIcon, { fontSize: "small" }),
3320
+ onClick: handleTranslateAll,
3321
+ disabled: isBusy,
3322
+ sx: { flexShrink: 0, ml: 2, whiteSpace: "nowrap" },
3323
+ children: isTranslatingAll ? "Translating..." : "Translate All"
3376
3324
  }
3377
- }
3378
- }
3379
- },
3380
- MuiListItemButton: {
3381
- styleOverrides: {
3382
- root: {
3383
- transition: "all 0.2s ease-in-out",
3384
- "&:hover": {
3385
- backgroundColor: currentMode === "light" ? "#f1f5f9" : "#334155"
3325
+ )
3326
+ ] }),
3327
+ /* @__PURE__ */ jsxs3(DialogContent, { dividers: true, sx: { pt: 2 }, children: [
3328
+ error && /* @__PURE__ */ jsx10(Alert2, { severity: "error", onClose: () => setError(null), sx: { mb: 2 }, children: error }),
3329
+ loading ? /* @__PURE__ */ jsx10(Box, { sx: { display: "flex", justifyContent: "center", py: 4 }, children: /* @__PURE__ */ jsx10(CircularProgress, {}) }) : /* @__PURE__ */ jsxs3(Box, { sx: { display: "flex", flexDirection: "column", gap: 2.5 }, children: [
3330
+ /* @__PURE__ */ jsxs3(Paper, { variant: "outlined", sx: { p: 2, bgcolor: "action.hover", borderRadius: 2 }, children: [
3331
+ /* @__PURE__ */ jsxs3(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1 }, children: [
3332
+ /* @__PURE__ */ jsx10(LightbulbIcon, { color: "warning", fontSize: "small" }),
3333
+ /* @__PURE__ */ jsx10(Typography, { variant: "subtitle2", sx: { fontWeight: 600 }, children: "Context Hint / Clarification" })
3334
+ ] }),
3335
+ /* @__PURE__ */ jsx10(
3336
+ TextField,
3337
+ {
3338
+ fullWidth: true,
3339
+ multiline: true,
3340
+ rows: 2,
3341
+ size: "small",
3342
+ value: contextHint,
3343
+ onChange: (e) => setContextHint(e.target.value),
3344
+ placeholder: "e.g. Website navigation menu link to the home page",
3345
+ helperText: "This hint explains the exact meaning to AI translation models.",
3346
+ disabled: isBusy
3347
+ }
3348
+ )
3349
+ ] }),
3350
+ languages.map((lang) => /* @__PURE__ */ jsxs3(Box, { sx: { display: "flex", alignItems: "center", gap: 2 }, children: [
3351
+ /* @__PURE__ */ jsx10(
3352
+ Box,
3353
+ {
3354
+ sx: {
3355
+ width: 32,
3356
+ height: 22,
3357
+ flexShrink: 0,
3358
+ borderRadius: "4px",
3359
+ overflow: "hidden",
3360
+ boxShadow: 1,
3361
+ "& svg": { width: "100%", height: "100%", display: "block" }
3362
+ },
3363
+ dangerouslySetInnerHTML: { __html: lang.svg_flag }
3364
+ }
3365
+ ),
3366
+ /* @__PURE__ */ jsx10(
3367
+ TextField,
3368
+ {
3369
+ fullWidth: true,
3370
+ label: lang.name,
3371
+ size: "small",
3372
+ value: values[lang.code] || "",
3373
+ onChange: (e) => setValues((prev) => ({ ...prev, [lang.code]: e.target.value })),
3374
+ placeholder: "Enter manual translation...",
3375
+ disabled: isBusy
3376
+ }
3377
+ ),
3378
+ /* @__PURE__ */ jsx10(Tooltip, { title: "Translate with context hint", children: /* @__PURE__ */ jsx10("span", { children: /* @__PURE__ */ jsx10(
3379
+ IconButton,
3380
+ {
3381
+ type: "button",
3382
+ color: "primary",
3383
+ disabled: isBusy,
3384
+ onClick: (e) => handleAutoTranslate(e, lang.code),
3385
+ children: translatingCode === lang.code ? /* @__PURE__ */ jsx10(CircularProgress, { size: 20, color: "inherit" }) : /* @__PURE__ */ jsx10(AutoFixHighIcon, { fontSize: "small" })
3386
+ }
3387
+ ) }) })
3388
+ ] }, lang.code))
3389
+ ] })
3390
+ ] }),
3391
+ /* @__PURE__ */ jsxs3(DialogActions, { sx: { px: 3, py: 2 }, children: [
3392
+ /* @__PURE__ */ jsx10(Button, { onClick: onClose, disabled: isBusy, color: "inherit", type: "button", children: "Cancel" }),
3393
+ /* @__PURE__ */ jsx10(
3394
+ Button,
3395
+ {
3396
+ onClick: handleSaveAll,
3397
+ variant: "contained",
3398
+ disabled: isBusy,
3399
+ type: "button",
3400
+ children: saving ? "Saving..." : "Save All"
3386
3401
  }
3387
- }
3388
- }
3389
- },
3390
- MuiButton: {
3391
- styleOverrides: {
3392
- root: {
3393
- textTransform: "none",
3394
- fontWeight: 600,
3395
- borderRadius: `${config.borderRadius}px`,
3396
- padding: "10px 24px",
3397
- transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
3398
- }
3399
- }
3400
- },
3401
- MuiPaper: {
3402
- styleOverrides: {
3403
- rounded: {
3404
- borderRadius: `${config.borderRadius * 1.25}px`
3405
- }
3406
- }
3407
- }
3402
+ )
3403
+ ] })
3404
+ ]
3408
3405
  }
3409
- });
3406
+ );
3410
3407
  };
3411
- var ThemeContext = createContext7(void 0);
3412
- var THEME_STORAGE_KEY = "docsua_custom_theme_config";
3413
- var THEME_MODE_STORAGE_KEY = "docsua_custom_theme_mode";
3414
- var ThemeProvider = ({ children }) => {
3408
+
3409
+ // src/i18n/TranslateText.tsx
3410
+ import { Fragment, jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
3411
+ var TranslateText = ({
3412
+ overrideNamespace,
3413
+ children,
3414
+ values
3415
+ }) => {
3416
+ const namespace = useTranslationNamespace(overrideNamespace);
3417
+ const { t: t2, i18n } = useTranslation2(namespace);
3418
+ const { isTranslationMode, canEdit } = useTranslationEdit();
3415
3419
  const supabase = useSupabase();
3416
- const { user, isAuthenticated, loading: isAuthLoading } = useAuth();
3417
- const userId = user?.id;
3418
- const [themeConfig, setThemeConfig] = useState6(() => {
3419
- const saved = localStorage.getItem(THEME_STORAGE_KEY);
3420
- if (saved) {
3421
- try {
3422
- return JSON.parse(saved);
3423
- } catch (e) {
3424
- }
3425
- }
3426
- return INITIAL_THEME_CONFIG;
3427
- });
3428
- useEffect6(() => {
3429
- if (isAuthLoading) return;
3430
- if (isAuthenticated && userId) {
3431
- const fetchActiveTheme = async () => {
3432
- try {
3433
- const { data, error } = await supabase.from("user_themes").select("*").eq("user_id", userId).eq("is_active", true).maybeSingle();
3434
- if (error) {
3435
- console.error("\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0435 \u0442\u0435\u043C\u044B \u0438\u0437 Supabase:", error.message);
3436
- return;
3437
- }
3438
- if (data) {
3439
- const activeTheme = data;
3440
- const savedMode = localStorage.getItem(THEME_MODE_STORAGE_KEY);
3441
- setThemeConfig({
3442
- id: activeTheme.id,
3443
- name: activeTheme.name || INITIAL_THEME_CONFIG.name,
3444
- mode: savedMode || activeTheme.config?.mode || "light",
3445
- borderRadius: activeTheme.config?.borderRadius ?? INITIAL_THEME_CONFIG.borderRadius,
3446
- colors: activeTheme.config?.colors || INITIAL_THEME_CONFIG.colors,
3447
- typography: activeTheme.config?.typography || INITIAL_THEME_CONFIG.typography,
3448
- updatedAt: activeTheme.updated_at || void 0
3449
- });
3450
- }
3451
- } catch (err) {
3452
- console.error("\u041D\u0435\u043F\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043D\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0437\u0430\u043F\u0440\u043E\u0441\u0435 \u0442\u0435\u043C\u044B:", err);
3453
- }
3454
- };
3455
- fetchActiveTheme();
3456
- } else {
3457
- setThemeConfig(INITIAL_THEME_CONFIG);
3458
- localStorage.removeItem(THEME_STORAGE_KEY);
3459
- localStorage.removeItem(THEME_MODE_STORAGE_KEY);
3460
- }
3461
- }, [isAuthenticated, isAuthLoading, userId]);
3420
+ const [anchorEl, setAnchorEl] = useState8(null);
3421
+ const [isDialogOpen, setIsDialogOpen] = useState8(false);
3422
+ const timeoutRef = useRef2(null);
3423
+ const translatedValue = t2(children, values);
3462
3424
  useEffect6(() => {
3463
- if (isAuthenticated) {
3464
- localStorage.setItem(THEME_STORAGE_KEY, JSON.stringify(themeConfig));
3465
- localStorage.setItem(THEME_MODE_STORAGE_KEY, themeConfig.mode);
3425
+ return () => {
3426
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
3427
+ };
3428
+ }, []);
3429
+ if (!canEdit || !isTranslationMode) {
3430
+ return /* @__PURE__ */ jsx11(Fragment, { children: translatedValue });
3431
+ }
3432
+ const handleSaveTranslations = async (newValues, contextHint) => {
3433
+ const upsertData = Object.entries(newValues).map(([langCode, text]) => ({
3434
+ lang_code: langCode,
3435
+ namespace,
3436
+ key: children,
3437
+ value: text,
3438
+ context_hint: contextHint
3439
+ }));
3440
+ const { error } = await supabase.from("translations").upsert(upsertData, { onConflict: "lang_code,namespace,key" });
3441
+ if (error) {
3442
+ alert("\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0438 \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043D\u043D\u044B\u0445");
3443
+ throw error;
3466
3444
  }
3467
- }, [themeConfig, isAuthenticated]);
3468
- const mode = themeConfig.mode;
3469
- const setMode = (newMode) => {
3470
- setThemeConfig((prev) => ({ ...prev, mode: newMode }));
3445
+ await translationService.incrementVersion(supabase, namespace, Object.keys(newValues));
3446
+ Object.entries(newValues).forEach(([langCode, text]) => {
3447
+ i18n.addResource(langCode, namespace, children, text);
3448
+ });
3471
3449
  };
3472
- const toggleTheme = () => {
3473
- setMode(mode === "light" ? "dark" : "light");
3450
+ const handleMouseEnter = (event) => {
3451
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
3452
+ setAnchorEl(event.currentTarget);
3474
3453
  };
3475
- const theme = useMemo4(() => buildMuiTheme(themeConfig), [themeConfig]);
3476
- const displayState = {
3477
- isMobile: useMediaQuery(theme.breakpoints.down("sm")),
3478
- isTablet: useMediaQuery(theme.breakpoints.between("sm", "md")),
3479
- isDesktop: useMediaQuery(theme.breakpoints.between("md", "lg")),
3480
- isWideDesktop: useMediaQuery(theme.breakpoints.between("lg", "xl")),
3481
- isSuperWideDesktop: useMediaQuery(theme.breakpoints.up("xl"))
3454
+ const handleMouseLeave = () => {
3455
+ timeoutRef.current = setTimeout(() => {
3456
+ setAnchorEl(null);
3457
+ }, 200);
3482
3458
  };
3483
- return /* @__PURE__ */ jsx10(ThemeContext.Provider, { value: { mode, toggleTheme, setMode, theme, themeConfig, setThemeConfig, displayState }, children: /* @__PURE__ */ jsxs3(MuiThemeProvider, { theme, children: [
3484
- /* @__PURE__ */ jsx10(CssBaseline, {}),
3485
- children
3486
- ] }) });
3487
- };
3488
- var useAppTheme = () => {
3489
- const context = useContext7(ThemeContext);
3490
- if (!context) {
3491
- throw new Error("useAppTheme should be used within the ThemeProvider");
3492
- }
3493
- return context;
3494
- };
3495
-
3496
- // src/context/BreadcrumbProvider.tsx
3497
- import { createContext as createContext8, useContext as useContext8, useState as useState7 } from "react";
3498
- import { jsx as jsx11 } from "react/jsx-runtime";
3499
- var BreadcrumbContext = createContext8({
3500
- breadcrumbTitles: {},
3501
- setBreadcrumbTitle: () => {
3502
- }
3503
- });
3504
- var BreadcrumbProvider = ({ children }) => {
3505
- const [breadcrumbTitles, setBreadcrumbTitles] = useState7({});
3506
- const setBreadcrumbTitle = (path, title) => {
3507
- setBreadcrumbTitles((prev) => {
3508
- if (prev[path] === title) return prev;
3509
- return { ...prev, [path]: title };
3510
- });
3459
+ const handleOpenDialog = (e) => {
3460
+ e.preventDefault();
3461
+ e.stopPropagation();
3462
+ setAnchorEl(null);
3463
+ if (document.activeElement instanceof HTMLElement) {
3464
+ document.activeElement.blur();
3465
+ }
3466
+ setIsDialogOpen(true);
3511
3467
  };
3512
- return /* @__PURE__ */ jsx11(BreadcrumbContext.Provider, { value: { breadcrumbTitles, setBreadcrumbTitle }, children });
3513
- };
3514
- var useBreadcrumb = () => useContext8(BreadcrumbContext);
3515
-
3516
- // src/context/ToastProvider.tsx
3517
- import { createContext as createContext9, useContext as useContext9, useState as useState8, useCallback as useCallback3, useMemo as useMemo5 } from "react";
3518
- import { Snackbar, Alert as Alert2 } from "@mui/material";
3519
- import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
3520
- var ToastContext = createContext9(void 0);
3521
- var ToastProvider = ({ children }) => {
3522
- const [toast, setToast] = useState8(null);
3523
- const showToast = useCallback3((text, type = "info") => {
3524
- setToast({ text, type });
3525
- }, []);
3526
- const hideToast = useCallback3(() => {
3527
- setToast(null);
3528
- }, []);
3529
- const contextValue = useMemo5(() => ({
3530
- showToast,
3531
- hideToast
3532
- }), [showToast, hideToast]);
3533
- return /* @__PURE__ */ jsxs4(ToastContext.Provider, { value: contextValue, children: [
3534
- children,
3535
- /* @__PURE__ */ jsx12(
3536
- Snackbar,
3468
+ const handleLaunchAction = (_) => {
3469
+ setAnchorEl(null);
3470
+ };
3471
+ const open = Boolean(anchorEl);
3472
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
3473
+ /* @__PURE__ */ jsx11(
3474
+ Box2,
3537
3475
  {
3538
- open: Boolean(toast),
3539
- autoHideDuration: 3500,
3540
- onClose: hideToast,
3541
- anchorOrigin: { vertical: "bottom", horizontal: "center" },
3542
- children: /* @__PURE__ */ jsx12(
3543
- Alert2,
3476
+ component: "span",
3477
+ onMouseEnter: handleMouseEnter,
3478
+ onMouseLeave: handleMouseLeave,
3479
+ onClick: (e) => {
3480
+ e.preventDefault();
3481
+ e.stopPropagation();
3482
+ },
3483
+ sx: {
3484
+ display: "inline-block",
3485
+ position: "relative",
3486
+ borderRadius: "4px",
3487
+ padding: "0 3px",
3488
+ margin: "0 1px",
3489
+ border: "1px dashed #f59e0b",
3490
+ backgroundColor: "rgba(254, 243, 199, 0.35)",
3491
+ cursor: "pointer",
3492
+ transition: "all 0.2s ease-in-out",
3493
+ userSelect: "none",
3494
+ "&:hover": {
3495
+ backgroundColor: "#fef08a",
3496
+ borderColor: "#d97706",
3497
+ boxShadow: "0 0 0 2px rgba(245, 158, 11, 0.35)",
3498
+ transform: "scale(1.02)"
3499
+ }
3500
+ },
3501
+ children: translatedValue
3502
+ }
3503
+ ),
3504
+ /* @__PURE__ */ jsx11(
3505
+ Popper,
3506
+ {
3507
+ open,
3508
+ anchorEl,
3509
+ placement: "bottom-start",
3510
+ style: { zIndex: 1300 },
3511
+ children: /* @__PURE__ */ jsx11(
3512
+ Paper2,
3544
3513
  {
3545
- severity: toast?.type || "info",
3546
- variant: "filled",
3547
- onClose: hideToast,
3548
- children: toast?.text
3514
+ elevation: 4,
3515
+ onMouseEnter: () => {
3516
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
3517
+ },
3518
+ onMouseLeave: handleMouseLeave,
3519
+ sx: {
3520
+ mt: 0.5,
3521
+ borderRadius: 2,
3522
+ minWidth: 140,
3523
+ boxShadow: "0px 10px 15px -3px rgba(0,0,0,0.1)"
3524
+ },
3525
+ children: /* @__PURE__ */ jsxs4(MenuList, { dense: true, sx: { py: 0.5 }, children: [
3526
+ /* @__PURE__ */ jsxs4(MenuItem, { onClick: handleOpenDialog, sx: { py: 0.8, px: 1.5 }, children: [
3527
+ /* @__PURE__ */ jsx11(ListItemIcon, { sx: { minWidth: "28px !important" }, children: /* @__PURE__ */ jsx11(TranslateIcon2, { fontSize: "small", sx: { color: "#d97706" } }) }),
3528
+ /* @__PURE__ */ jsx11(
3529
+ ListItemText,
3530
+ {
3531
+ primary: "Localization",
3532
+ slotProps: {
3533
+ primary: {
3534
+ sx: {
3535
+ fontSize: "0.825rem",
3536
+ fontWeight: 600,
3537
+ color: "text.primary"
3538
+ }
3539
+ }
3540
+ }
3541
+ }
3542
+ )
3543
+ ] }),
3544
+ /* @__PURE__ */ jsxs4(MenuItem, { onClick: handleLaunchAction, sx: { py: 0.8, px: 1.5 }, children: [
3545
+ /* @__PURE__ */ jsx11(ListItemIcon, { sx: { minWidth: "28px !important" }, children: /* @__PURE__ */ jsx11(LaunchIcon, { fontSize: "small", color: "secondary" }) }),
3546
+ /* @__PURE__ */ jsx11(
3547
+ ListItemText,
3548
+ {
3549
+ primary: "Launch action",
3550
+ slotProps: {
3551
+ primary: {
3552
+ sx: {
3553
+ fontSize: "0.825rem",
3554
+ fontWeight: 600,
3555
+ color: "text.primary"
3556
+ }
3557
+ }
3558
+ }
3559
+ }
3560
+ )
3561
+ ] })
3562
+ ] })
3549
3563
  }
3550
3564
  )
3551
3565
  }
3552
- )
3566
+ ),
3567
+ /* @__PURE__ */ jsx11("span", { style: { display: "inline" }, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx11(
3568
+ TranslationDialog,
3569
+ {
3570
+ open: isDialogOpen,
3571
+ onClose: () => setIsDialogOpen(false),
3572
+ translationKey: children,
3573
+ namespace,
3574
+ onSave: handleSaveTranslations
3575
+ }
3576
+ ) })
3553
3577
  ] });
3554
3578
  };
3555
- var useToast = () => {
3556
- const context = useContext9(ToastContext);
3557
- if (!context) {
3558
- throw new Error("useToast must be used within a ToastProvider");
3559
- }
3560
- return context;
3561
- };
3562
3579
 
3563
3580
  // src/context/ConfirmationProvider.tsx
3564
- import { createContext as createContext10, useContext as useContext10, useState as useState9, useCallback as useCallback4 } from "react";
3565
- import {
3566
- Button as Button2,
3567
- Dialog as Dialog2,
3568
- DialogActions as DialogActions2,
3569
- DialogContent as DialogContent2,
3570
- DialogContentText,
3571
- DialogTitle as DialogTitle2,
3572
- Box as Box3
3573
- } from "@mui/material";
3574
- import {
3575
- Error as ErrorIcon,
3576
- WarningAmberOutlined as WarningIcon,
3577
- InfoOutlined as InfoIcon,
3578
- CheckCircleOutlined as SuccessIcon
3579
- } from "@mui/icons-material";
3580
- import { jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
3581
+ import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
3581
3582
  var ConfirmContext = createContext10(null);
3582
3583
  var SEVERITY_ICONS = {
3583
- error: /* @__PURE__ */ jsx13(ErrorIcon, { color: "error", sx: { fontSize: 40 } }),
3584
- warning: /* @__PURE__ */ jsx13(WarningIcon, { color: "warning", sx: { fontSize: 40 } }),
3585
- info: /* @__PURE__ */ jsx13(InfoIcon, { color: "info", sx: { fontSize: 40 } }),
3586
- success: /* @__PURE__ */ jsx13(SuccessIcon, { color: "success", sx: { fontSize: 40 } })
3584
+ error: /* @__PURE__ */ jsx12(ErrorIcon, { color: "error", sx: { fontSize: 40 } }),
3585
+ warning: /* @__PURE__ */ jsx12(WarningIcon, { color: "warning", sx: { fontSize: 40 } }),
3586
+ info: /* @__PURE__ */ jsx12(InfoIcon, { color: "info", sx: { fontSize: 40 } }),
3587
+ success: /* @__PURE__ */ jsx12(SuccessIcon, { color: "success", sx: { fontSize: 40 } })
3587
3588
  };
3588
3589
  var ConfirmationProvider = ({ children }) => {
3589
3590
  const [open, setOpen] = useState9(false);
@@ -3617,36 +3618,36 @@ var ConfirmationProvider = ({ children }) => {
3617
3618
  children: [
3618
3619
  /* @__PURE__ */ jsxs5(DialogTitle2, { sx: { display: "flex", alignItems: "center", gap: 1.5, pb: 1 }, children: [
3619
3620
  SEVERITY_ICONS[severityType],
3620
- /* @__PURE__ */ jsx13(Box3, { component: "span", sx: { fontWeight: 600 }, children: options?.title || /* @__PURE__ */ jsx13(TranslateText, { children: "Confirmation" }) })
3621
+ /* @__PURE__ */ jsx12(Box3, { component: "span", sx: { fontWeight: 600 }, children: options?.title || /* @__PURE__ */ jsx12(TranslateText, { children: "Confirmation" }) })
3621
3622
  ] }),
3622
- /* @__PURE__ */ jsx13(DialogContent2, { sx: { py: 2 }, dividers: true, children: /* @__PURE__ */ jsx13(DialogContentText, { color: "text.secondary", children: options?.message }) }),
3623
+ /* @__PURE__ */ jsx12(DialogContent2, { sx: { py: 2 }, dividers: true, children: /* @__PURE__ */ jsx12(DialogContentText, { color: "text.secondary", children: options?.message }) }),
3623
3624
  /* @__PURE__ */ jsxs5(DialogActions2, { sx: { pt: 2, px: 2, justifyContent: "flex-end", gap: 1 }, children: [
3624
- (options?.showCancelButton ?? true) && /* @__PURE__ */ jsx13(
3625
+ (options?.showCancelButton ?? true) && /* @__PURE__ */ jsx12(
3625
3626
  Button2,
3626
3627
  {
3627
3628
  onClick: () => handleClose("cancel"),
3628
3629
  color: "inherit",
3629
3630
  variant: "text",
3630
- children: options?.cancelText || /* @__PURE__ */ jsx13(TranslateText, { children: "Cancel" })
3631
+ children: options?.cancelText || /* @__PURE__ */ jsx12(TranslateText, { children: "Cancel" })
3631
3632
  }
3632
3633
  ),
3633
- options?.showNoButton && /* @__PURE__ */ jsx13(
3634
+ options?.showNoButton && /* @__PURE__ */ jsx12(
3634
3635
  Button2,
3635
3636
  {
3636
3637
  onClick: () => handleClose("no"),
3637
3638
  color: "inherit",
3638
3639
  variant: "outlined",
3639
- children: options?.noText || /* @__PURE__ */ jsx13(TranslateText, { children: "No" })
3640
+ children: options?.noText || /* @__PURE__ */ jsx12(TranslateText, { children: "No" })
3640
3641
  }
3641
3642
  ),
3642
- /* @__PURE__ */ jsx13(
3643
+ /* @__PURE__ */ jsx12(
3643
3644
  Button2,
3644
3645
  {
3645
3646
  onClick: () => handleClose("yes"),
3646
3647
  color: options?.confirmColor || (severityType === "error" ? "error" : "primary"),
3647
3648
  variant: "contained",
3648
3649
  autoFocus: true,
3649
- children: options?.confirmText || /* @__PURE__ */ jsx13(TranslateText, { children: "Yes" })
3650
+ children: options?.confirmText || /* @__PURE__ */ jsx12(TranslateText, { children: "Yes" })
3650
3651
  }
3651
3652
  )
3652
3653
  ] })
@@ -3677,28 +3678,26 @@ var Providers = {
3677
3678
  };
3678
3679
 
3679
3680
  // src/UI/components/data.display/UserStatusChipComponent.tsx
3680
- import { Chip, IconButton as IconButton2 } from "@mui/material";
3681
- import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
3682
- import { jsx as jsx14 } from "react/jsx-runtime";
3681
+ import { jsx as jsx13 } from "react/jsx-runtime";
3683
3682
  var UserStatusChipComponent = ({ overrideNamespace, state }) => {
3684
3683
  const confirm = useConfirmation();
3685
3684
  const handleShowReason = async (e) => {
3686
3685
  e.stopPropagation();
3687
3686
  if (!state.locked_reason) return;
3688
3687
  await confirm({
3689
- title: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Lock Reason" }),
3688
+ title: /* @__PURE__ */ jsx13(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Lock Reason" }),
3690
3689
  message: state.locked_reason,
3691
3690
  type: "info",
3692
- confirmText: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "OK" }),
3691
+ confirmText: /* @__PURE__ */ jsx13(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "OK" }),
3693
3692
  showNoButton: false,
3694
3693
  showCancelButton: false
3695
3694
  });
3696
3695
  };
3697
3696
  if (state.profile_archived) {
3698
- return /* @__PURE__ */ jsx14(
3697
+ return /* @__PURE__ */ jsx13(
3699
3698
  Chip,
3700
3699
  {
3701
- label: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Archived" }),
3700
+ label: /* @__PURE__ */ jsx13(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Archived" }),
3702
3701
  color: "default",
3703
3702
  size: "small",
3704
3703
  variant: "filled"
@@ -3706,10 +3705,10 @@ var UserStatusChipComponent = ({ overrideNamespace, state }) => {
3706
3705
  );
3707
3706
  }
3708
3707
  if (state.profile_locked) {
3709
- return /* @__PURE__ */ jsx14(
3708
+ return /* @__PURE__ */ jsx13(
3710
3709
  Chip,
3711
3710
  {
3712
- label: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "System Locked" }),
3711
+ label: /* @__PURE__ */ jsx13(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "System Locked" }),
3713
3712
  color: "error",
3714
3713
  size: "small",
3715
3714
  variant: "filled"
@@ -3717,30 +3716,30 @@ var UserStatusChipComponent = ({ overrideNamespace, state }) => {
3717
3716
  );
3718
3717
  }
3719
3718
  if (state.excluded) {
3720
- return /* @__PURE__ */ jsx14(
3719
+ return /* @__PURE__ */ jsx13(
3721
3720
  Chip,
3722
3721
  {
3723
- label: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Excluded" }),
3722
+ label: /* @__PURE__ */ jsx13(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Excluded" }),
3724
3723
  size: "small"
3725
3724
  }
3726
3725
  );
3727
3726
  }
3728
3727
  if (state.locked) {
3729
- return /* @__PURE__ */ jsx14(
3728
+ return /* @__PURE__ */ jsx13(
3730
3729
  Chip,
3731
3730
  {
3732
- label: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Locked" }),
3731
+ label: /* @__PURE__ */ jsx13(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Locked" }),
3733
3732
  color: "error",
3734
3733
  size: "small",
3735
3734
  onDelete: state.locked_reason ? handleShowReason : void 0,
3736
- deleteIcon: state.locked_reason ? /* @__PURE__ */ jsx14(IconButton2, { size: "small", component: "span", sx: { p: 0.2 }, children: /* @__PURE__ */ jsx14(InfoOutlinedIcon, { fontSize: "small" }) }) : void 0
3735
+ deleteIcon: state.locked_reason ? /* @__PURE__ */ jsx13(IconButton2, { size: "small", component: "span", sx: { p: 0.2 }, children: /* @__PURE__ */ jsx13(InfoOutlinedIcon, { fontSize: "small" }) }) : void 0
3737
3736
  }
3738
3737
  );
3739
3738
  }
3740
- return /* @__PURE__ */ jsx14(
3739
+ return /* @__PURE__ */ jsx13(
3741
3740
  Chip,
3742
3741
  {
3743
- label: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Active" }),
3742
+ label: /* @__PURE__ */ jsx13(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Active" }),
3744
3743
  color: "success",
3745
3744
  size: "small",
3746
3745
  variant: "outlined"
@@ -3750,32 +3749,32 @@ var UserStatusChipComponent = ({ overrideNamespace, state }) => {
3750
3749
 
3751
3750
  // src/UI/components/data.display/UserRoleChipComponent.tsx
3752
3751
  import { Chip as Chip2 } from "@mui/material";
3753
- import { jsx as jsx15 } from "react/jsx-runtime";
3752
+ import { jsx as jsx14 } from "react/jsx-runtime";
3754
3753
  var UserRoleChipComponent = ({ overrideNamespace, role }) => {
3755
3754
  switch (role) {
3756
3755
  case "superadmin":
3757
- return /* @__PURE__ */ jsx15(
3756
+ return /* @__PURE__ */ jsx14(
3758
3757
  Chip2,
3759
3758
  {
3760
- label: /* @__PURE__ */ jsx15(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Superadministrator" }),
3759
+ label: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Superadministrator" }),
3761
3760
  color: "error",
3762
3761
  size: "small"
3763
3762
  }
3764
3763
  );
3765
3764
  case "admin":
3766
- return /* @__PURE__ */ jsx15(
3765
+ return /* @__PURE__ */ jsx14(
3767
3766
  Chip2,
3768
3767
  {
3769
- label: /* @__PURE__ */ jsx15(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Administrator" }),
3768
+ label: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "Administrator" }),
3770
3769
  color: "primary",
3771
3770
  size: "small"
3772
3771
  }
3773
3772
  );
3774
3773
  default:
3775
- return /* @__PURE__ */ jsx15(
3774
+ return /* @__PURE__ */ jsx14(
3776
3775
  Chip2,
3777
3776
  {
3778
- label: /* @__PURE__ */ jsx15(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "User" }),
3777
+ label: /* @__PURE__ */ jsx14(TranslateText, { overrideNamespace: overrideNamespace || "system_widgets", children: "User" }),
3779
3778
  variant: "outlined",
3780
3779
  size: "small"
3781
3780
  }
@@ -3788,6 +3787,41 @@ import { useState as useState10 } from "react";
3788
3787
  import { IconButton as IconButton3 } from "@mui/material";
3789
3788
  import CheckIcon from "@mui/icons-material/Check";
3790
3789
  import CopyIcon from "@mui/icons-material/ContentCopy";
3790
+
3791
+ // src/UI/components/data.display/TranslatableTooltipComponent.tsx
3792
+ import { Tooltip as Tooltip2 } from "@mui/material";
3793
+ import { jsx as jsx15 } from "react/jsx-runtime";
3794
+ var TranslatableTooltipComponent = ({
3795
+ overrideNamespace,
3796
+ title,
3797
+ disableSpan,
3798
+ children,
3799
+ ...props
3800
+ }) => {
3801
+ const { isTranslationMode } = useTranslationEdit();
3802
+ return /* @__PURE__ */ jsx15(
3803
+ Tooltip2,
3804
+ {
3805
+ title: typeof title === "string" ? /* @__PURE__ */ jsx15(TranslateText, { overrideNamespace, children: title }) : title,
3806
+ slotProps: {
3807
+ popper: {
3808
+ sx: isTranslationMode ? {
3809
+ pointerEvents: "auto",
3810
+ // Разрешаем клики внутри тултипа
3811
+ "& .MuiTooltip-tooltip": {
3812
+ pointerEvents: "auto"
3813
+ }
3814
+ } : void 0
3815
+ }
3816
+ },
3817
+ leaveDelay: isTranslationMode ? 1e3 : 100,
3818
+ ...props,
3819
+ children: disableSpan ? children : /* @__PURE__ */ jsx15("span", { children })
3820
+ }
3821
+ );
3822
+ };
3823
+
3824
+ // src/UI/components/buttons/CopyClipboardButtonComponent.tsx
3791
3825
  import { jsx as jsx16 } from "react/jsx-runtime";
3792
3826
  var CopyClipboardButtonComponent = ({
3793
3827
  overrideNamespace,
@@ -3831,11 +3865,367 @@ var CopyClipboardButtonComponent = ({
3831
3865
  ) }) });
3832
3866
  };
3833
3867
 
3868
+ // src/context/UserSettingsProvider.tsx
3869
+ import { createContext as createContext11, useContext as useContext11, useEffect as useEffect7, useState as useState11, useCallback as useCallback5 } from "react";
3870
+ import { jsx as jsx17 } from "react/jsx-runtime";
3871
+ var UserSettingsContext = createContext11(void 0);
3872
+ var useUserSettings = () => {
3873
+ const context = useContext11(UserSettingsContext);
3874
+ if (!context) {
3875
+ throw new Error("useUserSettings should be used within the UserSettingsProvider");
3876
+ }
3877
+ return context;
3878
+ };
3879
+
3880
+ // src/UI/components/data.display/UserPopoverComponent.tsx
3881
+ import { Fragment as Fragment2, jsx as jsx18, jsxs as jsxs6 } from "react/jsx-runtime";
3882
+ var useUserStatus = (supabase, userId, open) => {
3883
+ const [lastSeenAt, setLastSeenAt] = useState12(null);
3884
+ const [loading, setLoading] = useState12(true);
3885
+ useEffect8(() => {
3886
+ if (!userId || !open) {
3887
+ setLastSeenAt(null);
3888
+ setLoading(false);
3889
+ return;
3890
+ }
3891
+ let isMounted = true;
3892
+ const loadStatus = async () => {
3893
+ setLoading(true);
3894
+ const date = await getUserLastSeenAt(supabase, userId);
3895
+ if (isMounted) {
3896
+ setLastSeenAt(date);
3897
+ setLoading(false);
3898
+ }
3899
+ };
3900
+ loadStatus();
3901
+ const channel = supabase.channel(`user_status_popover_${userId}`).on(
3902
+ "postgres_changes",
3903
+ {
3904
+ event: "*",
3905
+ schema: "public",
3906
+ table: "user_sessions",
3907
+ filter: `user_id=eq.${userId}`
3908
+ },
3909
+ () => {
3910
+ loadStatus();
3911
+ }
3912
+ ).subscribe();
3913
+ return () => {
3914
+ isMounted = false;
3915
+ supabase.removeChannel(channel);
3916
+ };
3917
+ }, [userId, open]);
3918
+ const isOnline = Boolean(
3919
+ lastSeenAt && (/* @__PURE__ */ new Date()).getTime() - new Date(lastSeenAt).getTime() < 3 * 60 * 1e3
3920
+ );
3921
+ return { lastSeenAt, isOnline, loading };
3922
+ };
3923
+ var UserPopoverComponent = ({
3924
+ overrideNamespace,
3925
+ userId,
3926
+ anchorEl,
3927
+ open,
3928
+ onClose
3929
+ }) => {
3930
+ const supabase = useSupabase();
3931
+ const { activeCompanyId, membership: currentMembership } = useAuth();
3932
+ const { openChatWithUser } = useUserSettings();
3933
+ const { formatDate, formatRelativeTime } = useLocalization();
3934
+ const [profileData, setProfileData] = useState12(null);
3935
+ const [membershipData, setMembershipData] = useState12(null);
3936
+ const [avatarUrl, setAvatarUrl] = useState12(null);
3937
+ const [coverUrl, setCoverUrl] = useState12(null);
3938
+ const [loading, setLoading] = useState12(true);
3939
+ const { lastSeenAt, isOnline } = useUserStatus(supabase, userId, open);
3940
+ useEffect8(() => {
3941
+ if (!open || !userId || !activeCompanyId) return;
3942
+ let isMounted = true;
3943
+ setLoading(true);
3944
+ const fetchUserData = async () => {
3945
+ try {
3946
+ const { data, error } = await supabase.rpc("get_user_avatar_and_profile", {
3947
+ p_company_id: activeCompanyId,
3948
+ //currentMembership?.id || null,
3949
+ p_user_id: userId
3950
+ }).maybeSingle();
3951
+ if (error) throw error;
3952
+ if (isMounted && data) {
3953
+ const rpcData = data;
3954
+ const profile = rpcData.profile;
3955
+ const membership = rpcData.membership;
3956
+ setProfileData(profile);
3957
+ setMembershipData(membership);
3958
+ const avatarPath = profile?.avatar_path || profile?.avatar_url;
3959
+ if (avatarPath && !avatarPath.startsWith("http")) {
3960
+ const { data: avatarSigned } = await supabase.storage.from("user-media").createSignedUrl(avatarPath, 3600);
3961
+ if (isMounted) setAvatarUrl(avatarSigned?.signedUrl || null);
3962
+ } else {
3963
+ if (isMounted) setAvatarUrl(avatarPath || null);
3964
+ }
3965
+ const coverPath = profile?.cover_image_path || profile?.cover_image_url;
3966
+ if (coverPath && !coverPath.startsWith("http")) {
3967
+ const { data: coverSigned } = await supabase.storage.from("user-media").createSignedUrl(coverPath, 3600);
3968
+ if (isMounted) setCoverUrl(coverSigned?.signedUrl || null);
3969
+ } else {
3970
+ if (isMounted) setCoverUrl(coverPath || null);
3971
+ }
3972
+ }
3973
+ } catch (err) {
3974
+ console.error("Error loading user popover data:", err);
3975
+ } finally {
3976
+ if (isMounted) setLoading(false);
3977
+ }
3978
+ };
3979
+ fetchUserData();
3980
+ return () => {
3981
+ isMounted = false;
3982
+ };
3983
+ }, [open, userId, currentMembership?.id, activeCompanyId]);
3984
+ const getStatusChip = () => {
3985
+ if (!profileData) return null;
3986
+ return /* @__PURE__ */ jsx18(UserStatusChipComponent, { state: { profile_archived: profileData.archived, profile_locked: profileData.locked, excluded: membershipData?.excluded || false, locked: membershipData?.locked || false, locked_reason: membershipData?.locked_reason || "" } });
3987
+ };
3988
+ const getStatusTypographyColor = (isOnline2, lastSeenAt2) => {
3989
+ if (isOnline2) return "success";
3990
+ if (!lastSeenAt2) return "textSecondary";
3991
+ const diffInMinutes = (Date.now() - new Date(lastSeenAt2).getTime()) / (1e3 * 60);
3992
+ if (diffInMinutes <= 15) {
3993
+ return "warning";
3994
+ }
3995
+ return "textSecondary";
3996
+ };
3997
+ const getStatusIconColor = (isOnline2, lastSeenAt2) => {
3998
+ if (isOnline2) return "success";
3999
+ if (!lastSeenAt2) return "inherit";
4000
+ const diffInMinutes = (Date.now() - new Date(lastSeenAt2).getTime()) / (1e3 * 60);
4001
+ if (diffInMinutes <= 15) {
4002
+ return "warning";
4003
+ }
4004
+ return "inherit";
4005
+ };
4006
+ const canChat = currentMembership && membershipData && currentMembership.id !== membershipData.id;
4007
+ return /* @__PURE__ */ jsx18(
4008
+ Popover,
4009
+ {
4010
+ open,
4011
+ anchorEl,
4012
+ onClose,
4013
+ anchorOrigin: { vertical: "bottom", horizontal: "left" },
4014
+ transformOrigin: { vertical: "top", horizontal: "left" },
4015
+ slotProps: {
4016
+ paper: {
4017
+ sx: {
4018
+ width: 350,
4019
+ p: 0,
4020
+ overflow: "hidden",
4021
+ borderRadius: 0,
4022
+ boxShadow: 4
4023
+ }
4024
+ }
4025
+ },
4026
+ children: loading ? /* @__PURE__ */ jsx18(Box4, { sx: { display: "flex", justifyContent: "center", py: 4 }, children: /* @__PURE__ */ jsx18(CircularProgress2, { size: 28 }) }) : profileData ? /* @__PURE__ */ jsxs6(Box4, { sx: { display: "flex", flexDirection: "column" }, children: [
4027
+ /* @__PURE__ */ jsxs6(
4028
+ Box4,
4029
+ {
4030
+ sx: {
4031
+ position: "relative",
4032
+ width: "100%",
4033
+ height: 96,
4034
+ bgcolor: "action.selected"
4035
+ },
4036
+ children: [
4037
+ coverUrl && /* @__PURE__ */ jsx18(
4038
+ Box4,
4039
+ {
4040
+ component: "img",
4041
+ src: coverUrl,
4042
+ alt: "Profile background",
4043
+ sx: {
4044
+ width: "100%",
4045
+ height: "100%",
4046
+ objectFit: "cover",
4047
+ display: "block",
4048
+ m: 0,
4049
+ p: 0
4050
+ }
4051
+ }
4052
+ ),
4053
+ /* @__PURE__ */ jsx18(
4054
+ IconButton4,
4055
+ {
4056
+ size: "small",
4057
+ onClick: onClose,
4058
+ sx: {
4059
+ position: "absolute",
4060
+ top: 8,
4061
+ right: 8,
4062
+ color: "common.white",
4063
+ bgcolor: "rgba(0, 0, 0, 0.4)",
4064
+ backdropFilter: "blur(4px)",
4065
+ transition: "all 0.2s ease",
4066
+ "&:hover": {
4067
+ bgcolor: "rgba(0, 0, 0, 0.8)",
4068
+ transform: "scale(1.1)"
4069
+ }
4070
+ },
4071
+ children: /* @__PURE__ */ jsx18(CloseIcon, { fontSize: "small" })
4072
+ }
4073
+ )
4074
+ ]
4075
+ }
4076
+ ),
4077
+ /* @__PURE__ */ jsxs6(Box4, { sx: { p: 2, pt: 0 }, children: [
4078
+ /* @__PURE__ */ jsxs6(Stack, { direction: "row", spacing: 1.5, sx: { alignItems: "flex-end", mt: "-18px", mb: 1 }, children: [
4079
+ /* @__PURE__ */ jsx18(
4080
+ Badge,
4081
+ {
4082
+ overlap: "circular",
4083
+ anchorOrigin: { vertical: "bottom", horizontal: "right" },
4084
+ variant: "dot",
4085
+ sx: {
4086
+ "& .MuiBadge-badge": {
4087
+ backgroundColor: isOnline ? "#44b700" : "#bdbdbd",
4088
+ color: isOnline ? "#44b700" : "#bdbdbd",
4089
+ boxShadow: `0 0 0 2px #fff`,
4090
+ width: 12,
4091
+ height: 12,
4092
+ borderRadius: "50%",
4093
+ "&::after": isOnline ? {
4094
+ position: "absolute",
4095
+ top: 0,
4096
+ left: 0,
4097
+ width: "100%",
4098
+ height: "100%",
4099
+ borderRadius: "50%",
4100
+ animation: "ripple 1.2s infinite ease-in-out",
4101
+ border: "1px solid currentColor",
4102
+ content: '""'
4103
+ } : {}
4104
+ },
4105
+ "@keyframes ripple": {
4106
+ "0%": {
4107
+ transform: "scale(.8)",
4108
+ opacity: 1
4109
+ },
4110
+ "100%": {
4111
+ transform: "scale(2.4)",
4112
+ opacity: 0
4113
+ }
4114
+ }
4115
+ },
4116
+ children: /* @__PURE__ */ jsx18(
4117
+ Avatar,
4118
+ {
4119
+ src: avatarUrl || coverUrl || void 0,
4120
+ sx: {
4121
+ width: 65,
4122
+ height: 65,
4123
+ border: "3px solid",
4124
+ borderColor: "background.paper",
4125
+ boxShadow: 2,
4126
+ bgcolor: "primary.main",
4127
+ fontSize: 22,
4128
+ fontWeight: 600
4129
+ },
4130
+ children: profileData.display_name?.[0]?.toUpperCase() || "?"
4131
+ }
4132
+ )
4133
+ }
4134
+ ),
4135
+ /* @__PURE__ */ jsxs6(Box4, { sx: { overflow: "hidden", pb: 0.5 }, children: [
4136
+ /* @__PURE__ */ jsx18(Typography2, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.2 }, noWrap: true, children: profileData.display_name || `${profileData.first_name || ""} ${profileData.last_name || ""}` }),
4137
+ membershipData?.employee_post && /* @__PURE__ */ jsx18(Typography2, { variant: "body2", color: "text.secondary", noWrap: true, children: membershipData.employee_post })
4138
+ ] })
4139
+ ] }),
4140
+ /* @__PURE__ */ jsxs6(Stack, { direction: "row", spacing: 1, sx: { my: 1, flexWrap: "wrap", gap: 0.5 }, children: [
4141
+ getStatusChip(),
4142
+ /* @__PURE__ */ jsx18(UserRoleChipComponent, { role: membershipData?.role || "user" })
4143
+ ] }),
4144
+ /* @__PURE__ */ jsx18(Divider, { sx: { my: 1.5 } }),
4145
+ /* @__PURE__ */ jsxs6(Stack, { spacing: 1, sx: { fontSize: "0.875rem", mb: 1.5 }, children: [
4146
+ /* @__PURE__ */ jsxs6(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, color: "text.secondary", children: [
4147
+ /* @__PURE__ */ jsx18(AccessTimeIcon, { fontSize: "small", color: getStatusIconColor(isOnline, lastSeenAt) }),
4148
+ /* @__PURE__ */ jsx18(Typography2, { variant: "body2", color: getStatusTypographyColor(isOnline, lastSeenAt), children: isOnline ? /* @__PURE__ */ jsx18(TranslateText, { overrideNamespace: overrideNamespace || "form_components", children: "Online" }) : lastSeenAt ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
4149
+ /* @__PURE__ */ jsx18(TranslateText, { overrideNamespace: overrideNamespace || "form_components", children: "Last seen" }),
4150
+ ":",
4151
+ " ",
4152
+ /* @__PURE__ */ jsx18("strong", { children: formatRelativeTime(lastSeenAt) })
4153
+ ] }) : /* @__PURE__ */ jsx18(TranslateText, { overrideNamespace: overrideNamespace || "form_components", children: "Offline" }) })
4154
+ ] }),
4155
+ profileData.email && /* @__PURE__ */ jsxs6(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, color: "text.secondary", children: [
4156
+ /* @__PURE__ */ jsx18(EmailIcon, { fontSize: "small" }),
4157
+ /* @__PURE__ */ jsx18(Typography2, { variant: "body2", color: "text.primary", noWrap: true, sx: { flexGrow: 1 }, children: profileData.email }),
4158
+ /* @__PURE__ */ jsx18(CopyClipboardButtonComponent, { textToCopy: profileData.email })
4159
+ ] }),
4160
+ membershipData?.employee_post && /* @__PURE__ */ jsxs6(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, color: "text.secondary", children: [
4161
+ /* @__PURE__ */ jsx18(WorkIcon, { fontSize: "small" }),
4162
+ /* @__PURE__ */ jsx18(Typography2, { variant: "body2", color: "text.primary", noWrap: true, sx: { flexGrow: 1 }, children: membershipData.employee_post }),
4163
+ /* @__PURE__ */ jsx18(CopyClipboardButtonComponent, { textToCopy: membershipData.employee_post })
4164
+ ] }),
4165
+ profileData.phone && /* @__PURE__ */ jsxs6(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, color: "text.secondary", children: [
4166
+ /* @__PURE__ */ jsx18(PhoneIcon, { fontSize: "small" }),
4167
+ /* @__PURE__ */ jsx18(Typography2, { variant: "body2", color: "text.primary", children: profileData.phone })
4168
+ ] }),
4169
+ profileData.birthday && /* @__PURE__ */ jsxs6(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, color: "text.secondary", children: [
4170
+ /* @__PURE__ */ jsx18(CakeIcon, { fontSize: "small" }),
4171
+ /* @__PURE__ */ jsx18(Typography2, { variant: "body2", color: "text.primary", children: formatDate(profileData.birthday) })
4172
+ ] }),
4173
+ membershipData?.created_at && /* @__PURE__ */ jsxs6(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, color: "text.secondary", children: [
4174
+ /* @__PURE__ */ jsx18(CalendarIcon, { fontSize: "small" }),
4175
+ /* @__PURE__ */ jsxs6(Typography2, { variant: "body2", color: "text.primary", children: [
4176
+ /* @__PURE__ */ jsx18(TranslateText, { overrideNamespace: overrideNamespace || "form_components", children: "In company from" }),
4177
+ ": ",
4178
+ formatDate(membershipData.created_at)
4179
+ ] })
4180
+ ] }),
4181
+ profileData.description && /* @__PURE__ */ jsx18(
4182
+ Typography2,
4183
+ {
4184
+ variant: "caption",
4185
+ color: "text.secondary",
4186
+ sx: {
4187
+ display: "-webkit-box",
4188
+ WebkitLineClamp: 2,
4189
+ WebkitBoxOrient: "vertical",
4190
+ overflow: "hidden",
4191
+ mt: 0.5
4192
+ },
4193
+ children: profileData.description
4194
+ }
4195
+ )
4196
+ ] }),
4197
+ /* @__PURE__ */ jsx18(Divider, { sx: { my: 1 } }),
4198
+ /* @__PURE__ */ jsxs6(Stack, { direction: "row", spacing: 1, sx: { justifyContent: "space-around", pt: 0.5 }, children: [
4199
+ /* @__PURE__ */ jsx18(TranslatableTooltipComponent, { title: /* @__PURE__ */ jsx18(TranslateText, { overrideNamespace: overrideNamespace || "form_components", children: "Send a notification" }), children: /* @__PURE__ */ jsx18(IconButton4, { size: "small", color: "primary", children: /* @__PURE__ */ jsx18(NotificationIcon, { fontSize: "small" }) }) }),
4200
+ /* @__PURE__ */ jsx18(TranslatableTooltipComponent, { title: /* @__PURE__ */ jsx18(TranslateText, { overrideNamespace: overrideNamespace || "form_components", children: "Set a task" }), children: /* @__PURE__ */ jsx18(IconButton4, { size: "small", color: "primary", children: /* @__PURE__ */ jsx18(TaskIcon, { fontSize: "small" }) }) }),
4201
+ /* @__PURE__ */ jsx18(TranslatableTooltipComponent, { title: /* @__PURE__ */ jsx18(TranslateText, { overrideNamespace: overrideNamespace || "form_components", children: "Start a chat" }), children: /* @__PURE__ */ jsx18(
4202
+ IconButton4,
4203
+ {
4204
+ size: "small",
4205
+ color: "primary",
4206
+ disabled: !canChat,
4207
+ onClick: () => membershipData && openChatWithUser(membershipData.id),
4208
+ children: /* @__PURE__ */ jsx18(ChatIcon, { fontSize: "small" })
4209
+ }
4210
+ ) })
4211
+ ] })
4212
+ ] })
4213
+ ] }) : /* @__PURE__ */ jsx18(Box4, { sx: { p: 2 }, children: /* @__PURE__ */ jsx18(
4214
+ Typography2,
4215
+ {
4216
+ color: "error",
4217
+ variant: "body2",
4218
+ children: /* @__PURE__ */ jsx18(TranslateText, { overrideNamespace: overrideNamespace || "form_components", children: "Failed to load user data" })
4219
+ }
4220
+ ) })
4221
+ }
4222
+ );
4223
+ };
4224
+ var UserPopoverComponent_default = UserPopoverComponent;
4225
+
3834
4226
  export {
3835
- getUserLastSeenAt,
3836
4227
  SupabaseProvider,
3837
4228
  useSupabase,
3838
- useAuth,
3839
4229
  TranslationEditProvider,
3840
4230
  useTranslationEdit,
3841
4231
  translationService,
@@ -3859,5 +4249,7 @@ export {
3859
4249
  Providers,
3860
4250
  UserStatusChipComponent,
3861
4251
  UserRoleChipComponent,
3862
- CopyClipboardButtonComponent
4252
+ CopyClipboardButtonComponent,
4253
+ UserPopoverComponent,
4254
+ UserPopoverComponent_default
3863
4255
  };