Compare commits

...

1 Commits

Author SHA1 Message Date
Theodór Tómas
f35e54f21d feat: (dashboards) adding analysis tab to sidebar along with placeholder pages (#7311) 2026-02-20 09:58:26 +05:30
25 changed files with 174 additions and 1 deletions

View File

@@ -32,6 +32,7 @@ The `@formbricks/surveys` package is pre-compiled (Vite → UMD + ESM) and the b
TypeScript, React, and Prisma are the primary languages. Use the shared ESLint presets (`@formbricks/eslint-config`) and Prettier preset (110-char width, semicolons, double quotes, sorted import groups). Two-space indentation is standard; prefer `PascalCase` for React components and folders under `modules/`, `camelCase` for functions/variables, and `SCREAMING_SNAKE_CASE` only for constants. When adding mocks, place them inside `__mocks__` so import ordering stays stable.
We are using SonarQube to identify code smells and security hotspots.
Always mark React component props as `Readonly<>` (e.g., `({ children }: Readonly<MyProps>)`).
## Architecture & Patterns

View File

@@ -0,0 +1,9 @@
const ChartsPage = () => {
return (
<div className="flex items-center justify-center py-12 text-sm text-slate-500">
Charts will appear here.
</div>
);
};
export default ChartsPage;

View File

@@ -0,0 +1,8 @@
import type { ReactNode } from "react";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
const DashboardDetailLayout = ({ children }: Readonly<{ children: ReactNode }>) => {
return <PageContentWrapper>{children}</PageContentWrapper>;
};
export default DashboardDetailLayout;

View File

@@ -0,0 +1,11 @@
const DashboardDetailPage = async (props: { params: Promise<{ dashboardId: string }> }) => {
const { dashboardId } = await props.params;
return (
<div className="flex items-center justify-center py-12 text-sm text-slate-500">
Dashboard detail for {dashboardId} will appear here.
</div>
);
};
export default DashboardDetailPage;

View File

@@ -0,0 +1,9 @@
const DashboardsPage = () => {
return (
<div className="flex items-center justify-center py-12 text-sm text-slate-500">
Dashboards will appear here.
</div>
);
};
export default DashboardsPage;

View File

@@ -0,0 +1,16 @@
import type { ReactNode } from "react";
import { getTranslate } from "@/lingodotdev/server";
import { AnalysisPageLayout } from "@/modules/ee/analysis/components/analysis-page-layout";
const AnalysisLayout = async (props: { children: ReactNode; params: Promise<{ environmentId: string }> }) => {
const { environmentId } = await props.params;
const t = await getTranslate();
return (
<AnalysisPageLayout pageTitle={t("common.analysis")} environmentId={environmentId}>
{props.children}
</AnalysisPageLayout>
);
};
export default AnalysisLayout;

View File

@@ -0,0 +1,8 @@
import { redirect } from "next/navigation";
const AnalysisPage = async (props: { params: Promise<{ environmentId: string }> }) => {
const { environmentId } = await props.params;
return redirect(`/environments/${environmentId}/analysis/dashboards`);
};
export default AnalysisPage;

View File

@@ -2,6 +2,7 @@
import {
ArrowUpRightIcon,
ChartBar,
ChevronRightIcon,
Cog,
LogOutIcon,
@@ -114,6 +115,13 @@ export const MainNavigation = ({
pathname?.includes("/segments") ||
pathname?.includes("/attributes"),
},
{
name: t("common.analysis"),
href: `/environments/${environment.id}/analysis`,
icon: ChartBar,
isActive: pathname?.includes("/analysis"),
isHidden: false,
},
{
name: t("common.configuration"),
href: `/environments/${environment.id}/workspace/general`,
@@ -188,7 +196,7 @@ export const MainNavigation = ({
size="icon"
onClick={toggleSidebar}
className={cn(
"rounded-xl bg-slate-50 p-1 text-slate-600 transition-all hover:bg-slate-100 focus:ring-0 focus:ring-transparent focus:outline-none"
"rounded-xl bg-slate-50 p-1 text-slate-600 transition-all hover:bg-slate-100 focus:outline-none focus:ring-0 focus:ring-transparent"
)}>
{isCollapsed ? (
<PanelLeftOpenIcon strokeWidth={1.5} />

View File

@@ -106,6 +106,7 @@ checksums:
common/allow: 3e39cc5940255e6bff0fea95c817dd43
common/allow_users_to_exit_by_clicking_outside_the_survey: 1c09db6e85214f1b1c3d4774c4c5cd56
common/an_unknown_error_occurred_while_deleting_table_items: 06be3fd128aeb51eed4fba9a079ecee2
common/analysis: 409bac6215382c47e59f5039cc4cdcdd
common/and: dc75b95c804b16dc617a5f16f7393bca
common/and_response_limit_of: 05be41a1d7e8dafa4aa012dcba77f5d4
common/anonymous: 77b5222e710cc1dae073dae32309f8ed
@@ -122,6 +123,7 @@ checksums:
common/bottom_right: aaef9a70ef795affc806c6d1853d8373
common/cancel: 2e2a849c2223911717de8caa2c71bade
common/centered_modal: 982ff411cb7e91e30300c2ed56b7e507
common/charts: 1da4564d89264c89de4ed28d7451b43e
common/choices: 8a7a77a71ec6eebc363c5dc0f8490a4d
common/choose_environment: 5762cd499529815fc3e6a7feea39f90b
common/choose_organization: a8f5db68012323bfbb1a0ad0fb194603
@@ -160,6 +162,7 @@ checksums:
common/created_by: 6775c2fa7d495fea48f1ad816daea93b
common/customer_success: 2b0c99a5f57e1d16cf0a998f9bb116c4
common/dark_overlay: 173e84b526414dbc70dbf9737e443b60
common/dashboards: 4bc47e48559a6b688684dcb7ac4babc9
common/date: 56f41c5d30a76295bb087b20b7bee4c3
common/days: c95fe8aedde21a0b5653dbd0b3c58b48
common/default: d9c6dc5c412fe94143dfd1d332ec81d4

View File

@@ -133,6 +133,7 @@
"allow": "erlauben",
"allow_users_to_exit_by_clicking_outside_the_survey": "Erlaube Nutzern, die Umfrage zu verlassen, indem sie außerhalb klicken",
"an_unknown_error_occurred_while_deleting_table_items": "Beim Löschen von {type}s ist ein unbekannter Fehler aufgetreten",
"analysis": "Analyse",
"and": "und",
"and_response_limit_of": "und Antwortlimit von",
"anonymous": "Anonym",
@@ -149,6 +150,7 @@
"bottom_right": "Unten rechts",
"cancel": "Abbrechen",
"centered_modal": "Zentriertes Modalfenster",
"charts": "Diagramme",
"choices": "Entscheidungen",
"choose_environment": "Umgebung auswählen",
"choose_organization": "Organisation auswählen",
@@ -187,6 +189,7 @@
"created_by": "Erstellt von",
"customer_success": "Kundenerfolg",
"dark_overlay": "Dunkle Überlagerung",
"dashboards": "Dashboards",
"date": "Datum",
"days": "Tage",
"default": "Standard",

View File

@@ -133,6 +133,7 @@
"allow": "Allow",
"allow_users_to_exit_by_clicking_outside_the_survey": "Allow users to exit by clicking outside the survey",
"an_unknown_error_occurred_while_deleting_table_items": "An unknown error occurred while deleting {type}s",
"analysis": "Analysis",
"and": "And",
"and_response_limit_of": "and response limit of",
"anonymous": "Anonymous",
@@ -149,6 +150,7 @@
"bottom_right": "Bottom Right",
"cancel": "Cancel",
"centered_modal": "Centered Modal",
"charts": "Charts",
"choices": "Choices",
"choose_environment": "Choose environment",
"choose_organization": "Choose organization",
@@ -187,6 +189,7 @@
"created_by": "Created by",
"customer_success": "Customer Success",
"dark_overlay": "Dark overlay",
"dashboards": "Dashboards",
"date": "Date",
"days": "days",
"default": "Default",

View File

@@ -133,6 +133,7 @@
"allow": "Permitir",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permitir a los usuarios salir haciendo clic fuera de la encuesta",
"an_unknown_error_occurred_while_deleting_table_items": "Se ha producido un error desconocido al eliminar {type}s",
"analysis": "Análisis",
"and": "Y",
"and_response_limit_of": "y límite de respuesta de",
"anonymous": "Anónimo",
@@ -149,6 +150,7 @@
"bottom_right": "Inferior derecha",
"cancel": "Cancelar",
"centered_modal": "Modal centrado",
"charts": "Gráficos",
"choices": "Opciones",
"choose_environment": "Elegir entorno",
"choose_organization": "Elegir organización",
@@ -187,6 +189,7 @@
"created_by": "Creado por",
"customer_success": "Éxito del cliente",
"dark_overlay": "Superposición oscura",
"dashboards": "Paneles",
"date": "Fecha",
"days": "días",
"default": "Predeterminado",

View File

@@ -133,6 +133,7 @@
"allow": "Autoriser",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permettre aux utilisateurs de quitter en cliquant hors de l'enquête",
"an_unknown_error_occurred_while_deleting_table_items": "Une erreur inconnue est survenue lors de la suppression des {type}s",
"analysis": "Analyse",
"and": "Et",
"and_response_limit_of": "et limite de réponse de",
"anonymous": "Anonyme",
@@ -149,6 +150,7 @@
"bottom_right": "En bas à droite",
"cancel": "Annuler",
"centered_modal": "Au centre",
"charts": "Graphiques",
"choices": "Choix",
"choose_environment": "Choisir l'environnement",
"choose_organization": "Choisir l'organisation",
@@ -187,6 +189,7 @@
"created_by": "Créé par",
"customer_success": "Succès Client",
"dark_overlay": "Foncée",
"dashboards": "Tableaux de bord",
"date": "Date",
"days": "jours",
"default": "Par défaut",

View File

@@ -133,6 +133,7 @@
"allow": "Engedélyezés",
"allow_users_to_exit_by_clicking_outside_the_survey": "Lehetővé tétel a felhasználók számára, hogy a kérdőíven kívülre kattintva kilépjenek",
"an_unknown_error_occurred_while_deleting_table_items": "{type} típusok törlésekor ismeretlen hiba történt",
"analysis": "Elemzés",
"and": "És",
"and_response_limit_of": "és kérdéskorlátja ennek:",
"anonymous": "Névtelen",
@@ -149,6 +150,7 @@
"bottom_right": "Jobbra lent",
"cancel": "Mégse",
"centered_modal": "Középre helyezett kizárólagos",
"charts": "Diagramok",
"choices": "Választási lehetőségek",
"choose_environment": "Környezet kiválasztása",
"choose_organization": "Szervezet kiválasztása",
@@ -187,6 +189,7 @@
"created_by": "Létrehozta",
"customer_success": "Ügyfélsiker",
"dark_overlay": "Sötét rávetítés",
"dashboards": "Irányítópultok",
"date": "Dátum",
"days": "napok",
"default": "Alapértelmezett",

View File

@@ -133,6 +133,7 @@
"allow": "許可",
"allow_users_to_exit_by_clicking_outside_the_survey": "フォームの外側をクリックしてユーザーが終了できるようにする",
"an_unknown_error_occurred_while_deleting_table_items": "{type}の削除中に不明なエラーが発生しました",
"analysis": "分析",
"and": "および",
"and_response_limit_of": "と回答数の上限",
"anonymous": "匿名",
@@ -149,6 +150,7 @@
"bottom_right": "右下",
"cancel": "キャンセル",
"centered_modal": "中央モーダル",
"charts": "チャート",
"choices": "選択肢",
"choose_environment": "環境を選択",
"choose_organization": "組織を選択",
@@ -187,6 +189,7 @@
"created_by": "作成者",
"customer_success": "カスタマーサクセス",
"dark_overlay": "暗いオーバーレイ",
"dashboards": "ダッシュボード",
"date": "日付",
"days": "日",
"default": "デフォルト",

View File

@@ -133,6 +133,7 @@
"allow": "Toestaan",
"allow_users_to_exit_by_clicking_outside_the_survey": "Laat gebruikers afsluiten door buiten de enquête te klikken",
"an_unknown_error_occurred_while_deleting_table_items": "Er is een onbekende fout opgetreden bij het verwijderen van {type}s",
"analysis": "Analyse",
"and": "En",
"and_response_limit_of": "en responslimiet van",
"anonymous": "Anoniem",
@@ -149,6 +150,7 @@
"bottom_right": "Rechtsonder",
"cancel": "Annuleren",
"centered_modal": "Gecentreerd modaal",
"charts": "Grafieken",
"choices": "Keuzes",
"choose_environment": "Kies omgeving",
"choose_organization": "Kies organisatie",
@@ -187,6 +189,7 @@
"created_by": "Gemaakt door",
"customer_success": "Klant succes",
"dark_overlay": "Donkere overlay",
"dashboards": "Dashboards",
"date": "Datum",
"days": "dagen",
"default": "Standaard",

View File

@@ -133,6 +133,7 @@
"allow": "permitir",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permitir que os usuários saiam clicando fora da pesquisa",
"an_unknown_error_occurred_while_deleting_table_items": "Ocorreu um erro desconhecido ao deletar {type}s",
"analysis": "Análise",
"and": "E",
"and_response_limit_of": "e limite de resposta de",
"anonymous": "Anônimo",
@@ -149,6 +150,7 @@
"bottom_right": "Canto Inferior Direito",
"cancel": "Cancelar",
"centered_modal": "Modal Centralizado",
"charts": "Gráficos",
"choices": "Escolhas",
"choose_environment": "Escolher ambiente",
"choose_organization": "Escolher organização",
@@ -187,6 +189,7 @@
"created_by": "Criado por",
"customer_success": "Sucesso do Cliente",
"dark_overlay": "sobreposição escura",
"dashboards": "Painéis",
"date": "Encontro",
"days": "dias",
"default": "Padrão",

View File

@@ -133,6 +133,7 @@
"allow": "Permitir",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permitir que os utilizadores saiam se clicarem 'sair do questionário'",
"an_unknown_error_occurred_while_deleting_table_items": "Ocorreu um erro desconhecido ao eliminar {type}s",
"analysis": "Análise",
"and": "E",
"and_response_limit_of": "e limite de resposta de",
"anonymous": "Anónimo",
@@ -149,6 +150,7 @@
"bottom_right": "Inferior Direito",
"cancel": "Cancelar",
"centered_modal": "Modal Centralizado",
"charts": "Gráficos",
"choices": "Escolhas",
"choose_environment": "Escolha o ambiente",
"choose_organization": "Escolher organização",
@@ -187,6 +189,7 @@
"created_by": "Criado por",
"customer_success": "Sucesso do Cliente",
"dark_overlay": "Sobreposição escura",
"dashboards": "Dashboards",
"date": "Data",
"days": "dias",
"default": "Padrão",

View File

@@ -133,6 +133,7 @@
"allow": "Permite",
"allow_users_to_exit_by_clicking_outside_the_survey": "Permite utilizatorilor să iasă făcând clic în afara sondajului",
"an_unknown_error_occurred_while_deleting_table_items": "A apărut o eroare necunoscută la ștergerea elementelor de tipul {type}",
"analysis": "Analiză",
"and": "Și",
"and_response_limit_of": "și limită răspuns",
"anonymous": "Anonim",
@@ -149,6 +150,7 @@
"bottom_right": "Dreapta Jos",
"cancel": "Anulare",
"centered_modal": "Modală centralizată",
"charts": "Grafice",
"choices": "Alegeri",
"choose_environment": "Alege mediul",
"choose_organization": "Alege organizația",
@@ -187,6 +189,7 @@
"created_by": "Creat de",
"customer_success": "Succesul Clientului",
"dark_overlay": "Suprapunere întunecată",
"dashboards": "Tablouri de bord",
"date": "Dată",
"days": "zile",
"default": "Implicit",

View File

@@ -133,6 +133,7 @@
"allow": "Разрешить",
"allow_users_to_exit_by_clicking_outside_the_survey": "Разрешить пользователям выходить, кликнув вне опроса",
"an_unknown_error_occurred_while_deleting_table_items": "Произошла неизвестная ошибка при удалении {type}ов",
"analysis": "Аналитика",
"and": "и",
"and_response_limit_of": "и лимит ответов",
"anonymous": "Аноним",
@@ -149,6 +150,7 @@
"bottom_right": "Внизу справа",
"cancel": "Отмена",
"centered_modal": "Центрированное модальное окно",
"charts": "Графики",
"choices": "Варианты",
"choose_environment": "Выберите среду",
"choose_organization": "Выберите организацию",
@@ -187,6 +189,7 @@
"created_by": "Создано пользователем",
"customer_success": "Customer Success",
"dark_overlay": "Тёмный оверлей",
"dashboards": "Дашборды",
"date": "Дата",
"days": "дни",
"default": "По умолчанию",

View File

@@ -133,6 +133,7 @@
"allow": "Tillåt",
"allow_users_to_exit_by_clicking_outside_the_survey": "Tillåt användare att avsluta genom att klicka utanför enkäten",
"an_unknown_error_occurred_while_deleting_table_items": "Ett okänt fel uppstod vid borttagning av {type}",
"analysis": "Analys",
"and": "Och",
"and_response_limit_of": "och svarsgräns på",
"anonymous": "Anonym",
@@ -149,6 +150,7 @@
"bottom_right": "Nedre höger",
"cancel": "Avbryt",
"centered_modal": "Centrerad modal",
"charts": "Diagram",
"choices": "Val",
"choose_environment": "Välj miljö",
"choose_organization": "Välj organisation",
@@ -187,6 +189,7 @@
"created_by": "Skapad av",
"customer_success": "Kundframgång",
"dark_overlay": "Mörkt överlägg",
"dashboards": "Instrumentpaneler",
"date": "Datum",
"days": "dagar",
"default": "Standard",

View File

@@ -133,6 +133,7 @@
"allow": "允许",
"allow_users_to_exit_by_clicking_outside_the_survey": "允许 用户 通过 点击 调查 外部 退出",
"an_unknown_error_occurred_while_deleting_table_items": "删除 {type} 时发生未知错误",
"analysis": "分析",
"and": "和",
"and_response_limit_of": "和 响应限制",
"anonymous": "匿名",
@@ -149,6 +150,7 @@
"bottom_right": "右下",
"cancel": "取消",
"centered_modal": "居中 模态",
"charts": "图表",
"choices": "选项",
"choose_environment": "选择 环境",
"choose_organization": "选择 组织",
@@ -187,6 +189,7 @@
"created_by": "由 创建",
"customer_success": "客户成功",
"dark_overlay": "深色遮罩层",
"dashboards": "仪表盘",
"date": "日期",
"days": "天",
"default": "默认",

View File

@@ -133,6 +133,7 @@
"allow": "允許",
"allow_users_to_exit_by_clicking_outside_the_survey": "允許使用者點擊問卷外退出",
"an_unknown_error_occurred_while_deleting_table_items": "刪除 '{'type'}' 時發生未知錯誤",
"analysis": "分析",
"and": "且",
"and_response_limit_of": "且回應上限為",
"anonymous": "匿名",
@@ -149,6 +150,7 @@
"bottom_right": "右下",
"cancel": "取消",
"centered_modal": "置中彈窗",
"charts": "圖表",
"choices": "選項",
"choose_environment": "選擇環境",
"choose_organization": "選擇 組織",
@@ -187,6 +189,7 @@
"created_by": "建立者",
"customer_success": "客戶成功",
"dark_overlay": "深色覆蓋",
"dashboards": "儀表板",
"date": "日期",
"days": "天",
"default": "預設",

View File

@@ -0,0 +1,27 @@
import { ReactNode } from "react";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { AnalysisSecondaryNavigation } from "./analysis-secondary-navigation";
interface AnalysisPageLayoutProps {
pageTitle: string;
environmentId: string;
cta?: ReactNode;
children: ReactNode;
}
export function AnalysisPageLayout({
pageTitle,
environmentId,
cta,
children,
}: Readonly<AnalysisPageLayoutProps>) {
return (
<PageContentWrapper>
<PageHeader pageTitle={pageTitle} cta={cta}>
<AnalysisSecondaryNavigation environmentId={environmentId} />
</PageHeader>
{children}
</PageContentWrapper>
);
}

View File

@@ -0,0 +1,31 @@
"use client";
import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next";
import { SecondaryNavigation } from "@/modules/ui/components/secondary-navigation";
interface AnalysisSecondaryNavigationProps {
environmentId: string;
}
export function AnalysisSecondaryNavigation({ environmentId }: Readonly<AnalysisSecondaryNavigationProps>) {
const { t } = useTranslation();
const pathname = usePathname();
const activeId = pathname?.includes("/charts") ? "charts" : "dashboards";
const navigation = [
{
id: "dashboards",
label: t("common.dashboards"),
href: `/environments/${environmentId}/analysis/dashboards`,
},
{
id: "charts",
label: t("common.charts"),
href: `/environments/${environmentId}/analysis/charts`,
},
];
return <SecondaryNavigation navigation={navigation} activeId={activeId} />;
}