mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-13 03:16:58 -05:00
feat: Charts list page with demo create/edit and real delete/duplicate (#7353)
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: TheodorTomas <theodortomas@gmail.com>
This commit is contained in:
committed by
GitHub
parent
fbbf917093
commit
3a802810e3
@@ -1,9 +1,8 @@
|
||||
const ChartsPage = () => {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12 text-sm text-slate-500">
|
||||
Charts will appear here.
|
||||
</div>
|
||||
);
|
||||
import { ChartsListPage } from "@/modules/ee/analysis/charts/components/charts-list-page";
|
||||
|
||||
const ChartsPage = async (props: Readonly<{ params: Promise<{ environmentId: string }> }>) => {
|
||||
const { environmentId } = await props.params;
|
||||
return <ChartsListPage environmentId={environmentId} />;
|
||||
};
|
||||
|
||||
export default ChartsPage;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const DashboardDetailPage = async (props: { params: Promise<{ dashboardId: string }> }) => {
|
||||
const DashboardDetailPage = async (props: Readonly<{ params: Promise<{ dashboardId: string }> }>) => {
|
||||
const { dashboardId } = await props.params;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
export { DashboardsListPage as default } from "@/modules/ee/analysis/dashboards/pages/dashboards-list-page";
|
||||
import { DashboardsListPage } from "@/modules/ee/analysis/dashboards/pages/dashboards-list-page";
|
||||
|
||||
const DashboardsPage = async (props: Readonly<{ params: Promise<{ environmentId: string }> }>) => {
|
||||
const { environmentId } = await props.params;
|
||||
return <DashboardsListPage environmentId={environmentId} />;
|
||||
};
|
||||
|
||||
export default DashboardsPage;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
const AnalysisPage = async (props: { params: Promise<{ environmentId: string }> }) => {
|
||||
const AnalysisPage = async (props: Readonly<{ params: Promise<{ environmentId: string }> }>) => {
|
||||
const { environmentId } = await props.params;
|
||||
return redirect(`/environments/${environmentId}/analysis/dashboards`);
|
||||
};
|
||||
|
||||
@@ -123,6 +123,7 @@ checksums:
|
||||
common/bottom_right: aaef9a70ef795affc806c6d1853d8373
|
||||
common/cancel: 2e2a849c2223911717de8caa2c71bade
|
||||
common/centered_modal: 982ff411cb7e91e30300c2ed56b7e507
|
||||
common/chart: 6f4d9c56e45ceb8fc22d2f74454cd813
|
||||
common/charts: 1da4564d89264c89de4ed28d7451b43e
|
||||
common/choices: 8a7a77a71ec6eebc363c5dc0f8490a4d
|
||||
common/choose_environment: 5762cd499529815fc3e6a7feea39f90b
|
||||
@@ -203,6 +204,7 @@ checksums:
|
||||
common/failed_to_copy_to_clipboard: de836a7d628d36c832809252f188f784
|
||||
common/failed_to_load_organizations: 512808a2b674c7c28bca73f8f91fd87e
|
||||
common/failed_to_load_workspaces: 6ee3448097394517dc605074cd4e6ea4
|
||||
common/filter: 626325a05e4c8800f7ede7012b0cadaf
|
||||
common/finish: ffa7a10f71182b48fefed7135bee24fa
|
||||
common/first_name: cf040a5d6a9fd696be400380cc99f54b
|
||||
common/follow_these: 3a730b242bb17a3f95e01bf0dae86885
|
||||
@@ -585,6 +587,20 @@ checksums:
|
||||
environments/actions/you_can_track_code_action_anywhere_in_your_app_using: 3c0bbf160b8ddbeef142403103b70554
|
||||
environments/actions/your_survey_would_be_shown_on_this_url: 766fdeeb52d170c156af5d035a1f8c37
|
||||
environments/actions/your_survey_would_not_be_shown: af44fe160f449ff9557ebe5d3686832d
|
||||
environments/analysis/charts/action_coming_soon: ee2b0671e00972773210c5be5a9ccb89
|
||||
environments/analysis/charts/chart_deleted_successfully: 79148f471cd9acc2c8d0d033fb85437e
|
||||
environments/analysis/charts/chart_deletion_error: 267eb65c168e726075d7cea678dd32e0
|
||||
environments/analysis/charts/chart_duplicated_successfully: 755c4ce5bf533764d549a53c33e32165
|
||||
environments/analysis/charts/chart_duplication_error: 90d7166c85188b52f821c9d9f53ff8c4
|
||||
environments/analysis/charts/chart_type_area: 535754c6425f045f17e1dcb551840c93
|
||||
environments/analysis/charts/chart_type_bar: c11d460595d3ddfe8efd67ac068574c5
|
||||
environments/analysis/charts/chart_type_big_number: 9d17fb96241507c955dca25e143ae67a
|
||||
environments/analysis/charts/chart_type_line: f42dd53238ed4d44def306a61d47d5c4
|
||||
environments/analysis/charts/chart_type_pie: 068a797404233ccf68d07ad63af7b50c
|
||||
environments/analysis/charts/create_chart: ca7fdcc964e01f42ea9709924221edba
|
||||
environments/analysis/charts/delete_chart_confirmation: f7fd7b0a08e81c9b392b08c9c1ad2147
|
||||
environments/analysis/charts/no_charts_found: d4a27d5b56e49ebdd38bf28791dbcc42
|
||||
environments/analysis/charts/open_options: 2c6a35fec9b9d008e41728594bcd07d7
|
||||
environments/analysis/dashboards/create_dashboard: 9396aec1ea4a9b05ada94483655d1373
|
||||
environments/analysis/dashboards/create_dashboard_description: d29f60615f6d8c96cc4265541e75ec26
|
||||
environments/analysis/dashboards/create_failed: 7b58f15568047a35220b3a47cc3b0f71
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "Unten rechts",
|
||||
"cancel": "Abbrechen",
|
||||
"centered_modal": "Zentriertes Modalfenster",
|
||||
"chart": "Diagramm",
|
||||
"charts": "Diagramme",
|
||||
"choices": "Entscheidungen",
|
||||
"choose_environment": "Umgebung auswählen",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "Ihre Umfrage wäre nicht angezeigt."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "Kommt bald",
|
||||
"chart_deleted_successfully": "Diagramm erfolgreich gelöscht",
|
||||
"chart_deletion_error": "Diagramm konnte nicht gelöscht werden",
|
||||
"chart_duplicated_successfully": "Diagramm erfolgreich dupliziert",
|
||||
"chart_duplication_error": "Diagramm konnte nicht dupliziert werden",
|
||||
"chart_type_area": "Flächendiagramm",
|
||||
"chart_type_bar": "Balkendiagramm",
|
||||
"chart_type_big_number": "Große Zahl",
|
||||
"chart_type_line": "Liniendiagramm",
|
||||
"chart_type_pie": "Kreisdiagramm",
|
||||
"create_chart": "Diagramm erstellen",
|
||||
"delete_chart_confirmation": "Bist du sicher, dass du dieses Diagramm löschen möchtest?",
|
||||
"no_charts_found": "Keine Diagramme gefunden.",
|
||||
"open_options": "Diagrammoptionen öffnen"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Dashboard erstellen",
|
||||
"create_dashboard_description": "Gib einen Namen für dein neues Dashboard ein.",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "Bottom Right",
|
||||
"cancel": "Cancel",
|
||||
"centered_modal": "Centered Modal",
|
||||
"chart": "Chart",
|
||||
"charts": "Charts",
|
||||
"choices": "Choices",
|
||||
"choose_environment": "Choose environment",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "Your survey would not be shown."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "Coming soon",
|
||||
"chart_deleted_successfully": "Chart deleted successfully",
|
||||
"chart_deletion_error": "Failed to delete chart",
|
||||
"chart_duplicated_successfully": "Chart duplicated successfully",
|
||||
"chart_duplication_error": "Failed to duplicate chart",
|
||||
"chart_type_area": "Area Chart",
|
||||
"chart_type_bar": "Bar Chart",
|
||||
"chart_type_big_number": "Big Number",
|
||||
"chart_type_line": "Line Chart",
|
||||
"chart_type_pie": "Pie Chart",
|
||||
"create_chart": "Create Chart",
|
||||
"delete_chart_confirmation": "Are you sure you want to delete this chart?",
|
||||
"no_charts_found": "No charts found.",
|
||||
"open_options": "Open chart options"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Create Dashboard",
|
||||
"create_dashboard_description": "Enter a name for your new dashboard.",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "Inferior derecha",
|
||||
"cancel": "Cancelar",
|
||||
"centered_modal": "Modal centrado",
|
||||
"chart": "Gráfico",
|
||||
"charts": "Gráficos",
|
||||
"choices": "Opciones",
|
||||
"choose_environment": "Elegir entorno",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "Tu encuesta no se mostraría."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "Próximamente",
|
||||
"chart_deleted_successfully": "Gráfico eliminado correctamente",
|
||||
"chart_deletion_error": "Error al eliminar el gráfico",
|
||||
"chart_duplicated_successfully": "Gráfico duplicado correctamente",
|
||||
"chart_duplication_error": "Error al duplicar el gráfico",
|
||||
"chart_type_area": "Gráfico de área",
|
||||
"chart_type_bar": "Gráfico de barras",
|
||||
"chart_type_big_number": "Número grande",
|
||||
"chart_type_line": "Gráfico de líneas",
|
||||
"chart_type_pie": "Gráfico circular",
|
||||
"create_chart": "Crear gráfico",
|
||||
"delete_chart_confirmation": "¿Estás seguro de que quieres eliminar este gráfico?",
|
||||
"no_charts_found": "No se encontraron gráficos.",
|
||||
"open_options": "Abrir opciones del gráfico"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Crear panel de control",
|
||||
"create_dashboard_description": "Introduce un nombre para tu panel de control nuevo.",
|
||||
|
||||
@@ -112,7 +112,6 @@
|
||||
"link_expired_description": "Le lien que vous avez utilisé n'est plus valide."
|
||||
},
|
||||
"common": {
|
||||
"Filter": "Filtrer",
|
||||
"accepted": "Accepté",
|
||||
"account": "Compte",
|
||||
"account_settings": "Paramètres du compte",
|
||||
@@ -151,6 +150,7 @@
|
||||
"bottom_right": "En bas à droite",
|
||||
"cancel": "Annuler",
|
||||
"centered_modal": "Au centre",
|
||||
"chart": "Graphique",
|
||||
"charts": "Graphiques",
|
||||
"choices": "Choix",
|
||||
"choose_environment": "Choisir l'environnement",
|
||||
@@ -621,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "Votre enquête ne serait pas affichée."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "À venir bientôt",
|
||||
"chart_deleted_successfully": "Graphique supprimé avec succès",
|
||||
"chart_deletion_error": "Échec de la suppression du graphique",
|
||||
"chart_duplicated_successfully": "Graphique dupliqué avec succès",
|
||||
"chart_duplication_error": "Échec de la duplication du graphique",
|
||||
"chart_type_area": "Graphique en aires",
|
||||
"chart_type_bar": "Graphique à barres",
|
||||
"chart_type_big_number": "Grand nombre",
|
||||
"chart_type_line": "Graphique linéaire",
|
||||
"chart_type_pie": "Graphique circulaire",
|
||||
"create_chart": "Créer un graphique",
|
||||
"delete_chart_confirmation": "Êtes-vous sûr de vouloir supprimer ce graphique ?",
|
||||
"no_charts_found": "Aucun graphique trouvé.",
|
||||
"open_options": "Ouvrir les options du graphique"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Créer un tableau de bord",
|
||||
"create_dashboard_description": "Saisissez un nom pour votre nouveau tableau de bord.",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "Jobbra lent",
|
||||
"cancel": "Mégse",
|
||||
"centered_modal": "Középre helyezett kizárólagos",
|
||||
"chart": "Diagram",
|
||||
"charts": "Diagramok",
|
||||
"choices": "Választási lehetőségek",
|
||||
"choose_environment": "Környezet kiválasztása",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "A kérdőív nem jelenne meg."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "Hamarosan",
|
||||
"chart_deleted_successfully": "A diagram sikeresen törölve",
|
||||
"chart_deletion_error": "A diagram törlése sikertelen",
|
||||
"chart_duplicated_successfully": "A diagram sikeresen duplikálva",
|
||||
"chart_duplication_error": "A diagram duplikálása sikertelen",
|
||||
"chart_type_area": "Területdiagram",
|
||||
"chart_type_bar": "Oszlopdiagram",
|
||||
"chart_type_big_number": "Nagy szám",
|
||||
"chart_type_line": "Vonaldiagram",
|
||||
"chart_type_pie": "Kördiagram",
|
||||
"create_chart": "Diagram létrehozása",
|
||||
"delete_chart_confirmation": "Biztosan törölni szeretnéd ezt a diagramot?",
|
||||
"no_charts_found": "Nem található diagram.",
|
||||
"open_options": "Diagram beállításainak megnyitása"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Vezérlőpult létrehozása",
|
||||
"create_dashboard_description": "Adjon nevet az új vezérlőpultnak.",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "右下",
|
||||
"cancel": "キャンセル",
|
||||
"centered_modal": "中央モーダル",
|
||||
"chart": "チャート",
|
||||
"charts": "チャート",
|
||||
"choices": "選択肢",
|
||||
"choose_environment": "環境を選択",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "あなたのフォームは表示されません。"
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "近日公開",
|
||||
"chart_deleted_successfully": "チャートを削除しました",
|
||||
"chart_deletion_error": "チャートの削除に失敗しました",
|
||||
"chart_duplicated_successfully": "チャートを複製しました",
|
||||
"chart_duplication_error": "チャートの複製に失敗しました",
|
||||
"chart_type_area": "エリアチャート",
|
||||
"chart_type_bar": "棒グラフ",
|
||||
"chart_type_big_number": "大きな数値",
|
||||
"chart_type_line": "折れ線グラフ",
|
||||
"chart_type_pie": "円グラフ",
|
||||
"create_chart": "チャートを作成",
|
||||
"delete_chart_confirmation": "このチャートを削除してもよろしいですか?",
|
||||
"no_charts_found": "チャートが見つかりません。",
|
||||
"open_options": "チャートオプションを開く"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "ダッシュボードを作成",
|
||||
"create_dashboard_description": "新しいダッシュボードの名前を入力してください。",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "Rechtsonder",
|
||||
"cancel": "Annuleren",
|
||||
"centered_modal": "Gecentreerd modaal",
|
||||
"chart": "Grafiek",
|
||||
"charts": "Grafieken",
|
||||
"choices": "Keuzes",
|
||||
"choose_environment": "Kies omgeving",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "Uw enquête wordt niet getoond."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "Binnenkort beschikbaar",
|
||||
"chart_deleted_successfully": "Grafiek succesvol verwijderd",
|
||||
"chart_deletion_error": "Verwijderen van grafiek mislukt",
|
||||
"chart_duplicated_successfully": "Grafiek succesvol gedupliceerd",
|
||||
"chart_duplication_error": "Dupliceren van grafiek mislukt",
|
||||
"chart_type_area": "Vlakdiagram",
|
||||
"chart_type_bar": "Staafdiagram",
|
||||
"chart_type_big_number": "Groot getal",
|
||||
"chart_type_line": "Lijndiagram",
|
||||
"chart_type_pie": "Cirkeldiagram",
|
||||
"create_chart": "Diagram maken",
|
||||
"delete_chart_confirmation": "Weet je zeker dat je deze grafiek wilt verwijderen?",
|
||||
"no_charts_found": "Geen diagrammen gevonden.",
|
||||
"open_options": "Open diagramopties"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Dashboard creëren",
|
||||
"create_dashboard_description": "Voer een naam in voor je nieuwe dashboard.",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "Canto Inferior Direito",
|
||||
"cancel": "Cancelar",
|
||||
"centered_modal": "Modal Centralizado",
|
||||
"chart": "Gráfico",
|
||||
"charts": "Gráficos",
|
||||
"choices": "Escolhas",
|
||||
"choose_environment": "Escolher ambiente",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "Sua pesquisa não seria exibida."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "Em breve",
|
||||
"chart_deleted_successfully": "Gráfico excluído com sucesso",
|
||||
"chart_deletion_error": "Falha ao excluir gráfico",
|
||||
"chart_duplicated_successfully": "Gráfico duplicado com sucesso",
|
||||
"chart_duplication_error": "Falha ao duplicar gráfico",
|
||||
"chart_type_area": "Gráfico de área",
|
||||
"chart_type_bar": "Gráfico de barras",
|
||||
"chart_type_big_number": "Número grande",
|
||||
"chart_type_line": "Gráfico de linhas",
|
||||
"chart_type_pie": "Gráfico de pizza",
|
||||
"create_chart": "Criar gráfico",
|
||||
"delete_chart_confirmation": "Tem certeza de que deseja excluir este gráfico?",
|
||||
"no_charts_found": "Nenhum gráfico encontrado.",
|
||||
"open_options": "Abrir opções do gráfico"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Criar painel",
|
||||
"create_dashboard_description": "Digite um nome para o seu novo painel.",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "Inferior Direito",
|
||||
"cancel": "Cancelar",
|
||||
"centered_modal": "Modal Centralizado",
|
||||
"chart": "Gráfico",
|
||||
"charts": "Gráficos",
|
||||
"choices": "Escolhas",
|
||||
"choose_environment": "Escolha o ambiente",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "O seu inquérito não seria mostrado."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "Em breve",
|
||||
"chart_deleted_successfully": "Gráfico eliminado com sucesso",
|
||||
"chart_deletion_error": "Falha ao eliminar gráfico",
|
||||
"chart_duplicated_successfully": "Gráfico duplicado com sucesso",
|
||||
"chart_duplication_error": "Falha ao duplicar gráfico",
|
||||
"chart_type_area": "Gráfico de área",
|
||||
"chart_type_bar": "Gráfico de barras",
|
||||
"chart_type_big_number": "Número grande",
|
||||
"chart_type_line": "Gráfico de linhas",
|
||||
"chart_type_pie": "Gráfico circular",
|
||||
"create_chart": "Criar gráfico",
|
||||
"delete_chart_confirmation": "Tens a certeza de que queres eliminar este gráfico?",
|
||||
"no_charts_found": "Nenhum gráfico encontrado.",
|
||||
"open_options": "Abrir opções do gráfico"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Criar painel",
|
||||
"create_dashboard_description": "Introduza um nome para o seu novo painel.",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "Dreapta Jos",
|
||||
"cancel": "Anulare",
|
||||
"centered_modal": "Modală centralizată",
|
||||
"chart": "Grafic",
|
||||
"charts": "Grafice",
|
||||
"choices": "Alegeri",
|
||||
"choose_environment": "Alege mediul",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "Sondajul dumneavoastră nu va fi afișat."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "În curând",
|
||||
"chart_deleted_successfully": "Graficul a fost șters cu succes",
|
||||
"chart_deletion_error": "Nu s-a putut șterge graficul",
|
||||
"chart_duplicated_successfully": "Graficul a fost duplicat cu succes",
|
||||
"chart_duplication_error": "Nu s-a putut duplica graficul",
|
||||
"chart_type_area": "Grafic de tip arie",
|
||||
"chart_type_bar": "Grafic de tip bară",
|
||||
"chart_type_big_number": "Număr mare",
|
||||
"chart_type_line": "Grafic de tip linie",
|
||||
"chart_type_pie": "Grafic de tip plăcintă",
|
||||
"create_chart": "Creează grafic",
|
||||
"delete_chart_confirmation": "Ești sigur că vrei să ștergi acest grafic?",
|
||||
"no_charts_found": "Nu s-au găsit grafice.",
|
||||
"open_options": "Deschide opțiunile graficului"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Creează tablou de bord",
|
||||
"create_dashboard_description": "Introdu un nume pentru noul tău tablou de bord.",
|
||||
|
||||
@@ -112,7 +112,6 @@
|
||||
"link_expired_description": "Ссылка, которой вы воспользовались, больше не действительна."
|
||||
},
|
||||
"common": {
|
||||
"Filter": "Фильтр",
|
||||
"accepted": "Принято",
|
||||
"account": "Аккаунт",
|
||||
"account_settings": "Настройки аккаунта",
|
||||
@@ -151,6 +150,7 @@
|
||||
"bottom_right": "Внизу справа",
|
||||
"cancel": "Отмена",
|
||||
"centered_modal": "Центрированное модальное окно",
|
||||
"chart": "График",
|
||||
"charts": "Графики",
|
||||
"choices": "Варианты",
|
||||
"choose_environment": "Выберите среду",
|
||||
@@ -621,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "Ваш опрос не будет отображаться."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "Скоро будет",
|
||||
"chart_deleted_successfully": "График успешно удалён",
|
||||
"chart_deletion_error": "Не удалось удалить график",
|
||||
"chart_duplicated_successfully": "График успешно дублирован",
|
||||
"chart_duplication_error": "Не удалось дублировать график",
|
||||
"chart_type_area": "График областью",
|
||||
"chart_type_bar": "Столбчатая диаграмма",
|
||||
"chart_type_big_number": "Большое число",
|
||||
"chart_type_line": "Линейный график",
|
||||
"chart_type_pie": "Круговая диаграмма",
|
||||
"create_chart": "Создать график",
|
||||
"delete_chart_confirmation": "Ты уверен, что хочешь удалить этот график?",
|
||||
"no_charts_found": "Графики не найдены.",
|
||||
"open_options": "Открыть настройки графика"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Создать панель управления",
|
||||
"create_dashboard_description": "Введите название для новой панели управления.",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "Nedre höger",
|
||||
"cancel": "Avbryt",
|
||||
"centered_modal": "Centrerad modal",
|
||||
"chart": "Diagram",
|
||||
"charts": "Diagram",
|
||||
"choices": "Val",
|
||||
"choose_environment": "Välj miljö",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "Din enkät skulle inte visas."
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "Kommer snart",
|
||||
"chart_deleted_successfully": "Diagrammet har tagits bort",
|
||||
"chart_deletion_error": "Det gick inte att ta bort diagrammet",
|
||||
"chart_duplicated_successfully": "Diagrammet har duplicerats",
|
||||
"chart_duplication_error": "Det gick inte att duplicera diagrammet",
|
||||
"chart_type_area": "Ytdiagram",
|
||||
"chart_type_bar": "Stapeldiagram",
|
||||
"chart_type_big_number": "Stort tal",
|
||||
"chart_type_line": "Linjediagram",
|
||||
"chart_type_pie": "Cirkeldiagram",
|
||||
"create_chart": "Skapa diagram",
|
||||
"delete_chart_confirmation": "Är du säker på att du vill ta bort det här diagrammet?",
|
||||
"no_charts_found": "Inga diagram hittades.",
|
||||
"open_options": "Öppna diagramalternativ"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "Skapa instrumentpanel",
|
||||
"create_dashboard_description": "Ange ett namn för din nya instrumentpanel.",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "右下",
|
||||
"cancel": "取消",
|
||||
"centered_modal": "居中 模态",
|
||||
"chart": "图表",
|
||||
"charts": "图表",
|
||||
"choices": "选项",
|
||||
"choose_environment": "选择 环境",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "您的 调查 不会 显示。"
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "即将推出",
|
||||
"chart_deleted_successfully": "图表删除成功",
|
||||
"chart_deletion_error": "图表删除失败",
|
||||
"chart_duplicated_successfully": "图表复制成功",
|
||||
"chart_duplication_error": "图表复制失败",
|
||||
"chart_type_area": "面积图",
|
||||
"chart_type_bar": "柱状图",
|
||||
"chart_type_big_number": "大数字",
|
||||
"chart_type_line": "折线图",
|
||||
"chart_type_pie": "饼图",
|
||||
"create_chart": "创建图表",
|
||||
"delete_chart_confirmation": "你确定要删除这个图表吗?",
|
||||
"no_charts_found": "未找到图表。",
|
||||
"open_options": "打开图表选项"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "创建 Dashboard",
|
||||
"create_dashboard_description": "请输入新 Dashboard 的名称。",
|
||||
|
||||
@@ -150,6 +150,7 @@
|
||||
"bottom_right": "右下",
|
||||
"cancel": "取消",
|
||||
"centered_modal": "置中彈窗",
|
||||
"chart": "圖表",
|
||||
"charts": "圖表",
|
||||
"choices": "選項",
|
||||
"choose_environment": "選擇環境",
|
||||
@@ -620,6 +621,22 @@
|
||||
"your_survey_would_not_be_shown": "您的問卷將不會顯示。"
|
||||
},
|
||||
"analysis": {
|
||||
"charts": {
|
||||
"action_coming_soon": "即將推出",
|
||||
"chart_deleted_successfully": "圖表已成功刪除",
|
||||
"chart_deletion_error": "刪除圖表失敗",
|
||||
"chart_duplicated_successfully": "圖表已成功複製",
|
||||
"chart_duplication_error": "圖表複製失敗",
|
||||
"chart_type_area": "區域圖",
|
||||
"chart_type_bar": "長條圖",
|
||||
"chart_type_big_number": "大數字",
|
||||
"chart_type_line": "折線圖",
|
||||
"chart_type_pie": "圓餅圖",
|
||||
"create_chart": "建立圖表",
|
||||
"delete_chart_confirmation": "你確定要刪除此圖表嗎?",
|
||||
"no_charts_found": "找不到圖表。",
|
||||
"open_options": "開啟圖表選項"
|
||||
},
|
||||
"dashboards": {
|
||||
"create_dashboard": "建立儀表板",
|
||||
"create_dashboard_description": "請輸入新儀表板的名稱。",
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
"use client";
|
||||
|
||||
import { CopyIcon, MoreVertical, SquarePenIcon, TrashIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { deleteChartAction, duplicateChartAction } from "@/modules/ee/analysis/charts/actions";
|
||||
import type { TChartWithCreator } from "@/modules/ee/analysis/types/analysis";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
|
||||
interface ChartDropdownMenuProps {
|
||||
environmentId: string;
|
||||
chart: TChartWithCreator;
|
||||
}
|
||||
|
||||
export function ChartDropdownMenu({ environmentId, chart }: Readonly<ChartDropdownMenuProps>) {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isDuplicating, setIsDuplicating] = useState(false);
|
||||
const [isDropDownOpen, setIsDropDownOpen] = useState(false);
|
||||
|
||||
const handleDuplicate = async () => {
|
||||
setIsDuplicating(true);
|
||||
try {
|
||||
const result = await duplicateChartAction({ environmentId, chartId: chart.id });
|
||||
if (result?.data) {
|
||||
toast.success(t("environments.analysis.charts.chart_duplicated_successfully"));
|
||||
router.refresh();
|
||||
} else {
|
||||
toast.error(
|
||||
getFormattedErrorMessage(result) || t("environments.analysis.charts.chart_duplication_error")
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
toast.error(t("environments.analysis.charts.chart_duplication_error"));
|
||||
} finally {
|
||||
setIsDuplicating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
const result = await deleteChartAction({ environmentId, chartId: chart.id });
|
||||
if (result?.data) {
|
||||
toast.success(t("environments.analysis.charts.chart_deleted_successfully"));
|
||||
setDeleteDialogOpen(false);
|
||||
router.refresh();
|
||||
} else {
|
||||
const msg =
|
||||
getFormattedErrorMessage(result) || t("environments.analysis.charts.chart_deletion_error");
|
||||
toast.error(msg);
|
||||
}
|
||||
} catch {
|
||||
toast.error(t("common.something_went_wrong_please_try_again"));
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = () => {
|
||||
toast(t("environments.analysis.charts.action_coming_soon"));
|
||||
};
|
||||
|
||||
return (
|
||||
<div id={`chart-${chart.id}-actions`} data-testid="chart-dropdown-menu">
|
||||
<DropdownMenu open={isDropDownOpen} onOpenChange={setIsDropDownOpen}>
|
||||
<DropdownMenuTrigger className="z-10" asChild>
|
||||
<Button variant="outline" className="px-2" onClick={(e) => e.stopPropagation()}>
|
||||
<span className="sr-only">{t("environments.analysis.charts.open_options")}</span>
|
||||
<MoreVertical className="size-4" aria-hidden="true" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="inline-block w-auto min-w-max" align="end">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem icon={<SquarePenIcon className="size-4" />} onClick={handleEdit}>
|
||||
{t("common.edit")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
icon={<CopyIcon className="size-4" />}
|
||||
onClick={() => {
|
||||
setIsDropDownOpen(false);
|
||||
handleDuplicate();
|
||||
}}
|
||||
disabled={isDuplicating}>
|
||||
{t("common.duplicate")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
icon={<TrashIcon className="size-4" />}
|
||||
onClick={() => {
|
||||
setIsDropDownOpen(false);
|
||||
setDeleteDialogOpen(true);
|
||||
}}
|
||||
disabled={isDeleting}>
|
||||
{t("common.delete")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DeleteDialog
|
||||
deleteWhat={t("common.chart")}
|
||||
open={isDeleteDialogOpen}
|
||||
setOpen={setDeleteDialogOpen}
|
||||
onDelete={handleDelete}
|
||||
text={t("environments.analysis.charts.delete_chart_confirmation")}
|
||||
isDeleting={isDeleting}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import { BarChart3Icon } from "lucide-react";
|
||||
import { convertDateString, timeSinceDate } from "@/lib/time";
|
||||
import { ChartDropdownMenu } from "@/modules/ee/analysis/charts/components/chart-dropdown-menu";
|
||||
import { CHART_TYPE_ICONS } from "@/modules/ee/analysis/charts/lib/chart-types";
|
||||
import type { TChartWithCreator } from "@/modules/ee/analysis/types/analysis";
|
||||
|
||||
interface ChartRowProps {
|
||||
chart: TChartWithCreator;
|
||||
environmentId: string;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export function ChartRow({ chart, environmentId, isReadOnly }: Readonly<ChartRowProps>) {
|
||||
const IconComponent = CHART_TYPE_ICONS[chart.type as keyof typeof CHART_TYPE_ICONS] ?? BarChart3Icon;
|
||||
|
||||
return (
|
||||
<div className="grid h-12 w-full grid-cols-7 content-center text-left transition-colors ease-in-out hover:bg-slate-100">
|
||||
<div className="col-span-6 grid grid-cols-6 content-center p-2">
|
||||
<div className="col-span-3 flex items-center pl-6 text-sm">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="ph-no-capture w-8 flex-shrink-0 text-slate-500">
|
||||
<IconComponent className="h-5 w-5" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="ph-no-capture font-medium text-slate-900">{chart.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 my-auto hidden whitespace-nowrap text-center text-sm text-slate-500 sm:block">
|
||||
<div className="ph-no-capture text-slate-900">{chart.creator?.name ?? "-"}</div>
|
||||
</div>
|
||||
<div className="col-span-1 my-auto hidden whitespace-normal text-center text-sm text-slate-500 sm:block">
|
||||
<div className="ph-no-capture text-slate-900">
|
||||
{convertDateString(chart.createdAt.toISOString())}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 my-auto hidden text-center text-sm text-slate-500 sm:block">
|
||||
<div className="ph-no-capture text-slate-900">{timeSinceDate(new Date(chart.updatedAt))}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 my-auto flex items-center justify-end pr-6">
|
||||
{!isReadOnly && <ChartDropdownMenu environmentId={environmentId} chart={chart} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Delay } from "@suspensive/react";
|
||||
import { Suspense, use } from "react";
|
||||
import { getTranslate } from "@/lingodotdev/server";
|
||||
import { ChartsList } from "@/modules/ee/analysis/charts/components/charts-list";
|
||||
import { ChartsListSkeleton } from "@/modules/ee/analysis/charts/components/charts-list-skeleton";
|
||||
import { CreateChartButton } from "@/modules/ee/analysis/charts/components/create-chart-button";
|
||||
import { getChartsWithCreator } from "@/modules/ee/analysis/charts/lib/charts";
|
||||
import { AnalysisPageLayout } from "@/modules/ee/analysis/components/analysis-page-layout";
|
||||
import type { TChartWithCreator } from "@/modules/ee/analysis/types/analysis";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
|
||||
interface ChartsListContentProps {
|
||||
chartsPromise: Promise<TChartWithCreator[]>;
|
||||
environmentId: string;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
const ChartsListContent = ({
|
||||
chartsPromise,
|
||||
environmentId,
|
||||
isReadOnly,
|
||||
}: Readonly<ChartsListContentProps>) => {
|
||||
const charts = use(chartsPromise);
|
||||
|
||||
return <ChartsList charts={charts} environmentId={environmentId} isReadOnly={isReadOnly} />;
|
||||
};
|
||||
|
||||
interface ChartsListPageProps {
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
export async function ChartsListPage({ environmentId }: Readonly<ChartsListPageProps>) {
|
||||
const t = await getTranslate();
|
||||
const { project, isReadOnly } = await getEnvironmentAuth(environmentId);
|
||||
const chartsPromise = getChartsWithCreator(project.id);
|
||||
|
||||
return (
|
||||
<AnalysisPageLayout
|
||||
pageTitle={t("common.analysis")}
|
||||
environmentId={environmentId}
|
||||
cta={isReadOnly ? undefined : <CreateChartButton />}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<Delay ms={200}>
|
||||
<ChartsListSkeleton
|
||||
columnHeaders={[
|
||||
t("common.title"),
|
||||
t("common.created_by"),
|
||||
t("common.created_at"),
|
||||
t("common.updated_at"),
|
||||
]}
|
||||
/>
|
||||
</Delay>
|
||||
}>
|
||||
<ChartsListContent
|
||||
chartsPromise={chartsPromise}
|
||||
environmentId={environmentId}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</Suspense>
|
||||
</AnalysisPageLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
const SKELETON_ROWS = 3;
|
||||
|
||||
const SkeletonRow = () => {
|
||||
return (
|
||||
<div className="grid h-12 w-full animate-pulse grid-cols-7 content-center p-2">
|
||||
<div className="col-span-3 flex items-center gap-4 pl-6">
|
||||
<div className="h-5 w-5 rounded bg-gray-200" />
|
||||
<div className="h-4 w-36 rounded bg-gray-200" />
|
||||
</div>
|
||||
<div className="col-span-1 my-auto hidden sm:flex sm:justify-center">
|
||||
<div className="h-4 w-20 rounded bg-gray-200" />
|
||||
</div>
|
||||
<div className="col-span-1 my-auto hidden sm:flex sm:justify-center">
|
||||
<div className="h-4 w-24 rounded bg-gray-200" />
|
||||
</div>
|
||||
<div className="col-span-1 my-auto hidden sm:flex sm:justify-center">
|
||||
<div className="h-4 w-20 rounded bg-gray-200" />
|
||||
</div>
|
||||
<div className="col-span-1" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface ChartsListSkeletonProps {
|
||||
columnHeaders: [string, string, string, string];
|
||||
}
|
||||
|
||||
export const ChartsListSkeleton = ({ columnHeaders }: Readonly<ChartsListSkeletonProps>) => {
|
||||
return (
|
||||
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
|
||||
<div className="grid h-12 grid-cols-7 content-center border-b text-left text-sm font-semibold text-slate-900">
|
||||
<div className="col-span-3 pl-6">{columnHeaders[0]}</div>
|
||||
<div className="col-span-1 hidden text-center sm:block">{columnHeaders[1]}</div>
|
||||
<div className="col-span-1 hidden text-center sm:block">{columnHeaders[2]}</div>
|
||||
<div className="col-span-1 hidden text-center sm:block">{columnHeaders[3]}</div>
|
||||
<div className="col-span-1" />
|
||||
</div>
|
||||
{Array.from({ length: SKELETON_ROWS }).map((_, i) => (
|
||||
<SkeletonRow key={`skeleton-row-${String(i)}`} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import { getTranslate } from "@/lingodotdev/server";
|
||||
import { ChartRow } from "@/modules/ee/analysis/charts/components/chart-row";
|
||||
import type { TChartWithCreator } from "@/modules/ee/analysis/types/analysis";
|
||||
|
||||
interface ChartsListProps {
|
||||
charts: TChartWithCreator[];
|
||||
environmentId: string;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export const ChartsList = async ({ charts, environmentId, isReadOnly }: Readonly<ChartsListProps>) => {
|
||||
const t = await getTranslate();
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
|
||||
<div className="grid h-12 grid-cols-7 content-center border-b text-left text-sm font-semibold text-slate-900">
|
||||
<div className="col-span-3 pl-6">{t("common.title")}</div>
|
||||
<div className="col-span-1 hidden text-center sm:block">{t("common.created_by")}</div>
|
||||
<div className="col-span-1 hidden text-center sm:block">{t("common.created_at")}</div>
|
||||
<div className="col-span-1 hidden text-center sm:block">{t("common.updated_at")}</div>
|
||||
<div className="col-span-1" />
|
||||
</div>
|
||||
{charts.length === 0 ? (
|
||||
<p className="py-6 text-center text-sm text-slate-400">
|
||||
{t("environments.analysis.charts.no_charts_found")}
|
||||
</p>
|
||||
) : (
|
||||
charts.map((chart) => (
|
||||
<ChartRow key={chart.id} chart={chart} environmentId={environmentId} isReadOnly={isReadOnly} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
|
||||
export function CreateChartButton() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClick = () => {
|
||||
toast(t("environments.analysis.charts.action_coming_soon"));
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleClick}>
|
||||
<PlusIcon className="mr-2 h-4 w-4" />
|
||||
{t("environments.analysis.charts.create_chart")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { CHART_TYPE_ICONS, getChartTypes } from "./chart-types";
|
||||
|
||||
describe("chart-types", () => {
|
||||
test("CHART_TYPE_ICONS has all chart types", () => {
|
||||
expect(Object.keys(CHART_TYPE_ICONS)).toEqual(["area", "bar", "line", "pie", "big_number"]);
|
||||
});
|
||||
|
||||
test("getChartTypes returns chart types with translated labels", () => {
|
||||
const t = vi.fn((key: string) => key);
|
||||
const result = getChartTypes(t);
|
||||
|
||||
expect(result).toHaveLength(5);
|
||||
expect(result.map((r) => r.id)).toEqual(["area", "bar", "line", "pie", "big_number"]);
|
||||
expect(t).toHaveBeenCalledWith("environments.analysis.charts.chart_type_area");
|
||||
expect(result[0].label).toBe("environments.analysis.charts.chart_type_area");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { TFunction } from "i18next";
|
||||
import { ActivityIcon, AreaChartIcon, BarChart3Icon, LineChartIcon, PieChartIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
import type { TChartType } from "@/modules/ee/analysis/types/analysis";
|
||||
|
||||
export const CHART_TYPE_ICONS: Record<
|
||||
TChartType,
|
||||
React.ComponentType<{ className?: string; strokeWidth?: number }>
|
||||
> = {
|
||||
area: AreaChartIcon,
|
||||
bar: BarChart3Icon,
|
||||
line: LineChartIcon,
|
||||
pie: PieChartIcon,
|
||||
big_number: ActivityIcon,
|
||||
};
|
||||
|
||||
export function getChartTypes(
|
||||
t: TFunction
|
||||
): readonly {
|
||||
id: TChartType;
|
||||
icon: React.ComponentType<{ className?: string; strokeWidth?: number }>;
|
||||
label: string;
|
||||
}[] {
|
||||
return [
|
||||
{ id: "area", icon: CHART_TYPE_ICONS.area, label: t("environments.analysis.charts.chart_type_area") },
|
||||
{ id: "bar", icon: CHART_TYPE_ICONS.bar, label: t("environments.analysis.charts.chart_type_bar") },
|
||||
{ id: "line", icon: CHART_TYPE_ICONS.line, label: t("environments.analysis.charts.chart_type_line") },
|
||||
{ id: "pie", icon: CHART_TYPE_ICONS.pie, label: t("environments.analysis.charts.chart_type_pie") },
|
||||
{
|
||||
id: "big_number",
|
||||
icon: CHART_TYPE_ICONS.big_number,
|
||||
label: t("environments.analysis.charts.chart_type_big_number"),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -380,4 +380,35 @@ describe("Chart Service", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getChartsWithCreator", () => {
|
||||
test("returns charts with creator info", async () => {
|
||||
const chartsWithCreator = [
|
||||
{ ...mockChart, creator: { name: "Alice" } },
|
||||
{ ...mockChart, id: "chart-2", name: "Chart 2", creator: null },
|
||||
];
|
||||
vi.mocked(prisma.chart.findMany).mockResolvedValue(chartsWithCreator as any);
|
||||
const { getChartsWithCreator } = await import("./charts");
|
||||
|
||||
const result = await getChartsWithCreator(mockProjectId);
|
||||
|
||||
expect(result).toEqual(chartsWithCreator);
|
||||
expect(prisma.chart.findMany).toHaveBeenCalledWith({
|
||||
where: { projectId: mockProjectId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
select: expect.objectContaining({
|
||||
creator: { select: { name: true } },
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
test("throws DatabaseError on Prisma errors", async () => {
|
||||
vi.mocked(prisma.chart.findMany).mockRejectedValue(makePrismaError("P9999"));
|
||||
const { getChartsWithCreator } = await import("./charts");
|
||||
|
||||
await expect(getChartsWithCreator(mockProjectId)).rejects.toMatchObject({
|
||||
name: "DatabaseError",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TChart,
|
||||
TChartCreateInput,
|
||||
TChartUpdateInput,
|
||||
TChartWithCreator,
|
||||
TChartWithWidgets,
|
||||
ZChartCreateInput,
|
||||
ZChartType,
|
||||
@@ -244,3 +245,25 @@ export const getCharts = async (projectId: string): Promise<TChartWithWidgets[]>
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getChartsWithCreator = async (projectId: string): Promise<TChartWithCreator[]> => {
|
||||
validateInputs([projectId, ZId]);
|
||||
|
||||
try {
|
||||
return await prisma.chart.findMany({
|
||||
where: { projectId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
select: {
|
||||
...selectChart,
|
||||
creator: {
|
||||
select: { name: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,8 +25,11 @@ const DashboardsListContent = ({
|
||||
return <DashboardsTable dashboards={dashboards} environmentId={environmentId} isReadOnly={isReadOnly} />;
|
||||
};
|
||||
|
||||
export const DashboardsListPage = async (props: { params: Promise<{ environmentId: string }> }) => {
|
||||
const { environmentId } = await props.params;
|
||||
interface DashboardsListPageProps {
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
export const DashboardsListPage = async ({ environmentId }: Readonly<DashboardsListPageProps>) => {
|
||||
const t = await getTranslate();
|
||||
const { project, isReadOnly } = await getEnvironmentAuth(environmentId);
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ export type TChartWithWidgets = TChart & {
|
||||
widgets: { dashboardId: string }[];
|
||||
};
|
||||
|
||||
export type TChartWithCreator = TChart & {
|
||||
creator: { name: string } | null;
|
||||
};
|
||||
|
||||
// ── Dashboard input schemas ─────────────────────────────────────────────────
|
||||
|
||||
export const ZDashboardCreateInput = z.object({
|
||||
|
||||
Reference in New Issue
Block a user