diff --git a/packages/constants/src/analytics-v2/common.ts b/packages/constants/src/analytics-v2/common.ts deleted file mode 100644 index 6eab3ab2966..00000000000 --- a/packages/constants/src/analytics-v2/common.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { TAnalyticsTabsV2Base } from "@plane/types"; -import { ChartXAxisProperty, ChartYAxisMetric } from "../chart"; - -export const insightsFields: Record = { - overview: [ - "total_users", - "total_admins", - "total_members", - "total_guests", - "total_projects", - "total_work_items", - "total_cycles", - "total_intake", - ], - "work-items": [ - "total_work_items", - "started_work_items", - "backlog_work_items", - "un_started_work_items", - "completed_work_items", - ], -}; - -export const ANALYTICS_V2_DURATION_FILTER_OPTIONS = [ - { - name: "Yesterday", - value: "yesterday", - }, - { - name: "Last 7 days", - value: "last_7_days", - }, - { - name: "Last 30 days", - value: "last_30_days", - }, - { - name: "Last 3 months", - value: "last_3_months", - }, -]; - -export const ANALYTICS_V2_X_AXIS_VALUES: { value: ChartXAxisProperty; label: string }[] = [ - { - value: ChartXAxisProperty.STATES, - label: "State name", - }, - { - value: ChartXAxisProperty.STATE_GROUPS, - label: "State group", - }, - { - value: ChartXAxisProperty.PRIORITY, - label: "Priority", - }, - { - value: ChartXAxisProperty.LABELS, - label: "Label", - }, - { - value: ChartXAxisProperty.ASSIGNEES, - label: "Assignee", - }, - { - value: ChartXAxisProperty.ESTIMATE_POINTS, - label: "Estimate point", - }, - { - value: ChartXAxisProperty.CYCLES, - label: "Cycle", - }, - { - value: ChartXAxisProperty.MODULES, - label: "Module", - }, - { - value: ChartXAxisProperty.COMPLETED_AT, - label: "Completed date", - }, - { - value: ChartXAxisProperty.TARGET_DATE, - label: "Due date", - }, - { - value: ChartXAxisProperty.START_DATE, - label: "Start date", - }, - { - value: ChartXAxisProperty.CREATED_AT, - label: "Created date", - }, -]; - -export const ANALYTICS_V2_Y_AXIS_VALUES: { value: ChartYAxisMetric; label: string }[] = [ - { - value: ChartYAxisMetric.WORK_ITEM_COUNT, - label: "Work item", - }, - { - value: ChartYAxisMetric.ESTIMATE_POINT_COUNT, - label: "Estimate", - }, -]; - -export const ANALYTICS_V2_DATE_KEYS = ["completed_at", "target_date", "start_date", "created_at"]; diff --git a/packages/constants/src/analytics.ts b/packages/constants/src/analytics.ts deleted file mode 100644 index 6c8211ae0ab..00000000000 --- a/packages/constants/src/analytics.ts +++ /dev/null @@ -1,81 +0,0 @@ -// types -import { TXAxisValues, TYAxisValues } from "@plane/types"; - -export const ANALYTICS_TABS = [ - { - key: "scope_and_demand", - i18n_title: "workspace_analytics.tabs.scope_and_demand", - }, - { key: "custom", i18n_title: "workspace_analytics.tabs.custom" }, -]; - -export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] = - [ - { - value: "state_id", - label: "State name", - }, - { - value: "state__group", - label: "State group", - }, - { - value: "priority", - label: "Priority", - }, - { - value: "labels__id", - label: "Label", - }, - { - value: "assignees__id", - label: "Assignee", - }, - { - value: "estimate_point__value", - label: "Estimate point", - }, - { - value: "issue_cycle__cycle_id", - label: "Cycle", - }, - { - value: "issue_module__module_id", - label: "Module", - }, - { - value: "completed_at", - label: "Completed date", - }, - { - value: "target_date", - label: "Due date", - }, - { - value: "start_date", - label: "Start date", - }, - { - value: "created_at", - label: "Created date", - }, - ]; - -export const ANALYTICS_Y_AXIS_VALUES: { value: TYAxisValues; label: string }[] = - [ - { - value: "issue_count", - label: "Work item Count", - }, - { - value: "estimate", - label: "Estimate", - }, - ]; - -export const ANALYTICS_DATE_KEYS = [ - "completed_at", - "target_date", - "start_date", - "created_at", -]; diff --git a/packages/constants/src/analytics/common.ts b/packages/constants/src/analytics/common.ts new file mode 100644 index 00000000000..cf0624e9137 --- /dev/null +++ b/packages/constants/src/analytics/common.ts @@ -0,0 +1,178 @@ +import { TAnalyticsTabsBase } from "@plane/types"; +import { ChartXAxisProperty, ChartYAxisMetric } from "../chart"; + +export interface IInsightField { + key: string; + i18nKey: string; + i18nProps?: { + entity?: string; + entityPlural?: string; + [key: string]: any; + }; +} + +export const insightsFields: Record = { + overview: [ + { + key: "total_users", + i18nKey: "workspace_analytics.total", + i18nProps: { + entity: "common.users", + }, + }, + { + key: "total_admins", + i18nKey: "workspace_analytics.total", + i18nProps: { + entity: "common.admins", + }, + }, + { + key: "total_members", + i18nKey: "workspace_analytics.total", + i18nProps: { + entity: "common.members", + }, + }, + { + key: "total_guests", + i18nKey: "workspace_analytics.total", + i18nProps: { + entity: "common.guests", + }, + }, + { + key: "total_projects", + i18nKey: "workspace_analytics.total", + i18nProps: { + entity: "common.projects", + }, + }, + { + key: "total_work_items", + i18nKey: "workspace_analytics.total", + i18nProps: { + entity: "common.work_items", + }, + }, + { + key: "total_cycles", + i18nKey: "workspace_analytics.total", + i18nProps: { + entity: "common.cycles", + }, + }, + { + key: "total_intake", + i18nKey: "workspace_analytics.total", + i18nProps: { + entity: "sidebar.intake", + }, + }, + ], + "work-items": [ + { + key: "total_work_items", + i18nKey: "workspace_analytics.total", + }, + { + key: "started_work_items", + i18nKey: "workspace_analytics.started_work_items", + }, + { + key: "backlog_work_items", + i18nKey: "workspace_analytics.backlog_work_items", + }, + { + key: "un_started_work_items", + i18nKey: "workspace_analytics.un_started_work_items", + }, + { + key: "completed_work_items", + i18nKey: "workspace_analytics.completed_work_items", + }, + ], +}; + +export const ANALYTICS_DURATION_FILTER_OPTIONS = [ + { + name: "Yesterday", + value: "yesterday", + }, + { + name: "Last 7 days", + value: "last_7_days", + }, + { + name: "Last 30 days", + value: "last_30_days", + }, + { + name: "Last 3 months", + value: "last_3_months", + }, +]; + +export const ANALYTICS_X_AXIS_VALUES: { value: ChartXAxisProperty; label: string }[] = [ + { + value: ChartXAxisProperty.STATES, + label: "State name", + }, + { + value: ChartXAxisProperty.STATE_GROUPS, + label: "State group", + }, + { + value: ChartXAxisProperty.PRIORITY, + label: "Priority", + }, + { + value: ChartXAxisProperty.LABELS, + label: "Label", + }, + { + value: ChartXAxisProperty.ASSIGNEES, + label: "Assignee", + }, + { + value: ChartXAxisProperty.ESTIMATE_POINTS, + label: "Estimate point", + }, + { + value: ChartXAxisProperty.CYCLES, + label: "Cycle", + }, + { + value: ChartXAxisProperty.MODULES, + label: "Module", + }, + { + value: ChartXAxisProperty.COMPLETED_AT, + label: "Completed date", + }, + { + value: ChartXAxisProperty.TARGET_DATE, + label: "Due date", + }, + { + value: ChartXAxisProperty.START_DATE, + label: "Start date", + }, + { + value: ChartXAxisProperty.CREATED_AT, + label: "Created date", + }, +]; + +export const ANALYTICS_Y_AXIS_VALUES: { value: ChartYAxisMetric; label: string }[] = [ + { + value: ChartYAxisMetric.WORK_ITEM_COUNT, + label: "Work item", + }, + { + value: ChartYAxisMetric.ESTIMATE_POINT_COUNT, + label: "Estimate", + }, +]; + +export const ANALYTICS_V2_DATE_KEYS = ["completed_at", "target_date", "start_date", "created_at"]; diff --git a/packages/constants/src/analytics-v2/index.ts b/packages/constants/src/analytics/index.ts similarity index 100% rename from packages/constants/src/analytics-v2/index.ts rename to packages/constants/src/analytics/index.ts diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts index 58b51ed723a..0f5610350c4 100644 --- a/packages/constants/src/index.ts +++ b/packages/constants/src/index.ts @@ -1,5 +1,4 @@ export * from "./ai"; -export * from "./analytics"; export * from "./auth"; export * from "./chart"; export * from "./endpoints"; @@ -34,4 +33,4 @@ export * from "./emoji"; export * from "./subscription"; export * from "./settings"; export * from "./icon"; -export * from "./analytics-v2"; +export * from "./analytics"; diff --git a/packages/i18n/src/locales/cs/translations.json b/packages/i18n/src/locales/cs/translations.json index 3c1e1d26ab4..b2ac82a65e3 100644 --- a/packages/i18n/src/locales/cs/translations.json +++ b/packages/i18n/src/locales/cs/translations.json @@ -866,7 +866,19 @@ "view": "Pohled", "deactivated_user": "Deaktivovaný uživatel", "apply": "Použít", - "applying": "Používání" + "applying": "Používání", + "users": "Uživatelé", + "admins": "Administrátoři", + "guests": "Hosté", + "on_track": "Na správné cestě", + "off_track": "Mimo plán", + "timeline": "Časová osa", + "completion": "Dokončení", + "upcoming": "Nadcházející", + "completed": "Dokončeno", + "in_progress": "Probíhá", + "planned": "Plánováno", + "paused": "Pozastaveno" }, "chart": { "x_axis": "Osa X", @@ -1316,19 +1328,6 @@ "custom": "Vlastní analytika" }, "empty_state": { - "general": { - "title": "Sledujte pokrok, vytížení a alokace. Identifikujte trendy, odstraňte překážky a zrychlete práci", - "description": "Sledujte rozsah vs. poptávku, odhady a rozsah. Zjistěte výkonnost členů a týmů, zajistěte včasné dokončení projektů.", - "primary_button": { - "text": "Začněte první projekt", - "comic": { - "title": "Analytika funguje nejlépe s Cykly + Moduly", - "description": "Nejprve časově ohraničte práci do Cyklů a seskupte položky přesahující cyklus do Modulů. Najdete je v levém menu." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Pracovní položky přiřazené vám, rozdělené podle stavu, se zde zobrazí.", "title": "Zatím žádná data" @@ -1344,21 +1343,22 @@ }, "created_vs_resolved": "Vytvořeno vs Vyřešeno", "customized_insights": "Přizpůsobené přehledy", - "backlog_work_items": "Pracovní položky v backlogu", + "backlog_work_items": "Backlog {entity}", "active_projects": "Aktivní projekty", "trend_on_charts": "Trend na grafech", "all_projects": "Všechny projekty", "summary_of_projects": "Souhrn projektů", "project_insights": "Přehled projektu", - "started_work_items": "Zahájené pracovní položky", - "total_work_items": "Celkový počet pracovních položek", + "started_work_items": "Zahájené {entity}", + "total_work_items": "Celkový počet {entity}", "total_projects": "Celkový počet projektů", "total_admins": "Celkový počet administrátorů", "total_users": "Celkový počet uživatelů", "total_intake": "Celkový příjem", - "un_started_work_items": "Nezahájené pracovní položky", + "un_started_work_items": "Nezahájené {entity}", "total_guests": "Celkový počet hostů", - "completed_work_items": "Dokončené pracovní položky" + "completed_work_items": "Dokončené {entity}", + "total": "Celkový počet {entity}" }, "workspace_projects": { "label": "{count, plural, one {Projekt} few {Projekty} other {Projektů}}", diff --git a/packages/i18n/src/locales/de/translations.json b/packages/i18n/src/locales/de/translations.json index 640aa4e2a76..b895435ae87 100644 --- a/packages/i18n/src/locales/de/translations.json +++ b/packages/i18n/src/locales/de/translations.json @@ -866,7 +866,19 @@ "view": "Ansicht", "deactivated_user": "Deaktivierter Benutzer", "apply": "Anwenden", - "applying": "Wird angewendet" + "applying": "Wird angewendet", + "users": "Benutzer", + "admins": "Administratoren", + "guests": "Gäste", + "on_track": "Im Plan", + "off_track": "Außer Plan", + "timeline": "Zeitleiste", + "completion": "Fertigstellung", + "upcoming": "Bevorstehend", + "completed": "Abgeschlossen", + "in_progress": "In Bearbeitung", + "planned": "Geplant", + "paused": "Pausiert" }, "chart": { "x_axis": "X-Achse", @@ -1316,19 +1328,6 @@ "custom": "Benutzerdefinierte Analysen" }, "empty_state": { - "general": { - "title": "Verfolgen Sie Fortschritt, Auslastung und Zuordnungen. Erkennen Sie Trends, entfernen Sie Blocker und beschleunigen Sie die Arbeit", - "description": "Behalten Sie Umfang vs. Nachfrage, Schätzungen und Umfang im Blick. Verfolgen Sie die Leistung von Mitgliedern und Teams, um sicherzustellen, dass Projekte pünktlich abgeschlossen werden.", - "primary_button": { - "text": "Erstes Projekt starten", - "comic": { - "title": "Analysen funktionieren am besten mit Zyklen + Modulen", - "description": "Begrenzen Sie zuerst Arbeit zeitlich in Zyklen und gruppieren Sie die übergreifenden Elemente in Module. Sie finden sie im linken Menü." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Ihnen zugewiesene Arbeitselemente, aufgeschlüsselt nach Status, werden hier angezeigt.", "title": "Noch keine Daten" @@ -1344,21 +1343,22 @@ }, "created_vs_resolved": "Erstellt vs Gelöst", "customized_insights": "Individuelle Einblicke", - "backlog_work_items": "Backlog-Arbeitselemente", + "backlog_work_items": "Backlog-{entity}", "active_projects": "Aktive Projekte", "trend_on_charts": "Trend in Diagrammen", "all_projects": "Alle Projekte", "summary_of_projects": "Projektübersicht", "project_insights": "Projekteinblicke", - "started_work_items": "Begonnene Arbeitselemente", - "total_work_items": "Gesamte Arbeitselemente", + "started_work_items": "Begonnene {entity}", + "total_work_items": "Gesamte {entity}", "total_projects": "Gesamtprojekte", "total_admins": "Gesamtanzahl der Admins", "total_users": "Gesamtanzahl der Benutzer", "total_intake": "Gesamteinnahmen", - "un_started_work_items": "Nicht begonnene Arbeitselemente", + "un_started_work_items": "Nicht begonnene {entity}", "total_guests": "Gesamtanzahl der Gäste", - "completed_work_items": "Abgeschlossene Arbeitselemente" + "completed_work_items": "Abgeschlossene {entity}", + "total": "Gesamte {entity}" }, "workspace_projects": { "label": "{count, plural, one {Projekt} few {Projekte} other {Projekte}}", @@ -2466,4 +2466,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane ist nicht gestartet. Dies könnte daran liegen, dass einer oder mehrere Plane-Services nicht starten konnten.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Wählen Sie View Logs aus setup.sh und Docker-Logs, um sicherzugehen." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index 35f852918be..aaf83df2f4f 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -476,6 +476,9 @@ "modules": "Modules", "labels": "Labels", "label": "Label", + "admins": "Admins", + "users": "Users", + "guests": "Guests", "assignees": "Assignees", "assignee": "Assignee", "created_by": "Created by", @@ -612,6 +615,15 @@ "quarter": "Quarter", "press_for_commands": "Press '/' for commands", "click_to_add_description": "Click to add description", + "on_track": "On-Track", + "off_track": "Off-Track", + "timeline": "Timeline", + "completion": "Completion", + "upcoming": "Upcoming", + "completed": "Completed", + "in_progress": "In progress", + "planned": "Planned", + "paused": "Paused", "search": { "label": "Search", "placeholder": "Type to search", @@ -1158,29 +1170,11 @@ "scope_and_demand": "Scope and Demand", "custom": "Custom Analytics" }, - "empty_state": { - "general": { - "title": "Track progress, workloads, and allocations. Spot trends, remove blockers, and move work faster", - "description": "See scope versus demand, estimates, and scope creep. Get performance by team members and teams, and make sure your project runs on time.", - "primary_button": { - "text": "Start your first project", - "comic": { - "title": "Analytics works best with Cycles + Modules", - "description": "First, timebox your work items into Cycles and, if you can, group work items that span more than a cycle into Modules. Check out both on the left nav." - } - } - } - }, - "total_work_items": "Total work items", - "started_work_items": "Started work items", - "backlog_work_items": "Backlog work items", - "un_started_work_items": "Unstarted work items", - "completed_work_items": "Completed work items", - "total_guests": "Total Guests", - "total_intake": "Total Intake", - "total_users": "Total Users", - "total_admins": "Total Admins", - "total_projects": "Total Projects", + "total": "Total {entity}", + "started_work_items": "Started {entity}", + "backlog_work_items": "Backlog {entity}", + "un_started_work_items": "Unstarted {entity}", + "completed_work_items": "Completed {entity}", "project_insights": "Project Insights", "summary_of_projects": "Summary of Projects", "all_projects": "All Projects", @@ -1188,7 +1182,7 @@ "active_projects": "Active Projects", "customized_insights": "Customized Insights", "created_vs_resolved": "Created vs Resolved", - "empty_state_v2": { + "empty_state": { "project_insights": { "title": "No data yet", "description": "Work items assigned to you, broken down by state, will show up here." @@ -1312,23 +1306,23 @@ } }, "account_settings": { - "profile":{}, - "preferences":{ + "profile": {}, + "preferences": { "heading": "Preferences", "description": "Customize your app experience the way you work" }, - "notifications":{ + "notifications": { "heading": "Email notifications", - "description": "Stay in the loop on Work items you are subscribed to. Enable this to get notified." + "description": "Stay in the loop on Work items you are subscribed to. Enable this to get notified." }, - "security":{ + "security": { "heading": "Security" }, - "api_tokens":{ + "api_tokens": { "heading": "Personal Access Tokens", "description": "Generate secure API tokens to integrate your data with external systems and applications." }, - "activity":{ + "activity": { "heading": "Activity", "description": "Track your recent actions and changes across all projects and work items." } @@ -1400,7 +1394,7 @@ }, "billing_and_plans": { "heading": "Billing & Plans", - "description":"Choose your plan, manage subscriptions, and easily upgrade as your needs grow.", + "description": "Choose your plan, manage subscriptions, and easily upgrade as your needs grow.", "title": "Billing & Plans", "current_plan": "Current plan", "free_plan": "You are currently using the free plan", diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json index 6c699212a5d..31c8f75e3c3 100644 --- a/packages/i18n/src/locales/es/translations.json +++ b/packages/i18n/src/locales/es/translations.json @@ -869,7 +869,19 @@ "view": "Ver", "deactivated_user": "Usuario desactivado", "apply": "Aplicar", - "applying": "Aplicando" + "applying": "Aplicando", + "users": "Usuarios", + "admins": "Administradores", + "guests": "Invitados", + "on_track": "En camino", + "off_track": "Fuera de camino", + "timeline": "Cronograma", + "completion": "Finalización", + "upcoming": "Próximo", + "completed": "Completado", + "in_progress": "En progreso", + "planned": "Planificado", + "paused": "Pausado" }, "chart": { "x_axis": "Eje X", @@ -1319,19 +1331,6 @@ "custom": "Análisis Personalizado" }, "empty_state": { - "general": { - "title": "Rastrea el progreso, cargas de trabajo y asignaciones. Identifica tendencias, elimina bloqueos y mueve el trabajo más rápido", - "description": "Observa el alcance versus la demanda, estimaciones y el aumento del alcance. Obtén el rendimiento por miembros del equipo y equipos, y asegúrate de que tu proyecto se ejecute a tiempo.", - "primary_button": { - "text": "Inicia tu primer proyecto", - "comic": { - "title": "El análisis funciona mejor con Ciclos + Módulos", - "description": "Primero, organiza tus elementos de trabajo en Ciclos y, si puedes, agrupa los elementos de trabajo que abarcan más de un ciclo en Módulos. Revisa ambos en la navegación izquierda." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Los elementos de trabajo asignados a ti, desglosados por estado, aparecerán aquí.", "title": "Aún no hay datos" @@ -1347,21 +1346,22 @@ }, "created_vs_resolved": "Creado vs Resuelto", "customized_insights": "Información personalizada", - "backlog_work_items": "Elementos de trabajo en backlog", + "backlog_work_items": "{entity} en backlog", "active_projects": "Proyectos activos", "trend_on_charts": "Tendencia en gráficos", "all_projects": "Todos los proyectos", "summary_of_projects": "Resumen de proyectos", "project_insights": "Información del proyecto", - "started_work_items": "Elementos de trabajo iniciados", - "total_work_items": "Total de elementos de trabajo", + "started_work_items": "{entity} iniciados", + "total_work_items": "Total de {entity}", "total_projects": "Total de proyectos", "total_admins": "Total de administradores", "total_users": "Total de usuarios", "total_intake": "Ingreso total", - "un_started_work_items": "Elementos de trabajo no iniciados", + "un_started_work_items": "{entity} no iniciados", "total_guests": "Total de invitados", - "completed_work_items": "Elementos de trabajo completados" + "completed_work_items": "{entity} completados", + "total": "Total de {entity}" }, "workspace_projects": { "label": "{count, plural, one {Proyecto} other {Proyectos}}", diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index b8b7723a0ab..afa6f184426 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -867,7 +867,19 @@ "view": "Afficher", "deactivated_user": "Utilisateur désactivé", "apply": "Appliquer", - "applying": "Application" + "applying": "Application", + "users": "Utilisateurs", + "admins": "Administrateurs", + "guests": "Invités", + "on_track": "Sur la bonne voie", + "off_track": "Hors de la bonne voie", + "timeline": "Chronologie", + "completion": "Achèvement", + "upcoming": "À venir", + "completed": "Terminé", + "in_progress": "En cours", + "planned": "Planifié", + "paused": "En pause" }, "chart": { "x_axis": "Axe X", @@ -1317,19 +1329,6 @@ "custom": "Analytique Personnalisée" }, "empty_state": { - "general": { - "title": "Suivez les progrès, les charges de travail et les allocations. Repérez les tendances, supprimez les blocages et accélérez le travail", - "description": "Visualisez la portée par rapport à la demande, les estimations et l'augmentation de la portée. Obtenez les performances par membres de l'équipe et équipes, et assurez-vous que votre projet se déroule dans les délais.", - "primary_button": { - "text": "Commencez votre premier projet", - "comic": { - "title": "L'analytique fonctionne mieux avec les Cycles + Modules", - "description": "D'abord, planifiez vos éléments de travail dans des Cycles et, si possible, regroupez les éléments de travail qui s'étendent sur plus d'un cycle dans des Modules. Consultez les deux dans la navigation de gauche." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Les éléments de travail qui vous sont assignés, répartis par état, s'afficheront ici.", "title": "Pas encore de données" @@ -1345,21 +1344,22 @@ }, "created_vs_resolved": "Créé vs Résolu", "customized_insights": "Informations personnalisées", - "backlog_work_items": "Éléments de travail en backlog", + "backlog_work_items": "{entity} en backlog", "active_projects": "Projets actifs", "trend_on_charts": "Tendance sur les graphiques", "all_projects": "Tous les projets", "summary_of_projects": "Résumé des projets", "project_insights": "Aperçus du projet", - "started_work_items": "Éléments de travail commencés", - "total_work_items": "Total des éléments de travail", + "started_work_items": "{entity} commencés", + "total_work_items": "Total des {entity}", "total_projects": "Total des projets", "total_admins": "Total des administrateurs", "total_users": "Nombre total d'utilisateurs", "total_intake": "Revenu total", - "un_started_work_items": "Éléments de travail non commencés", + "un_started_work_items": "{entity} non commencés", "total_guests": "Nombre total d'invités", - "completed_work_items": "Éléments de travail terminés" + "completed_work_items": "{entity} terminés", + "total": "Total des {entity}" }, "workspace_projects": { "label": "{count, plural, one {Projet} other {Projets}}", diff --git a/packages/i18n/src/locales/id/translations.json b/packages/i18n/src/locales/id/translations.json index 6190f5cee99..dc59bdaf191 100644 --- a/packages/i18n/src/locales/id/translations.json +++ b/packages/i18n/src/locales/id/translations.json @@ -866,7 +866,19 @@ "view": "Lihat", "deactivated_user": "Pengguna dinonaktifkan", "apply": "Terapkan", - "applying": "Terapkan" + "applying": "Terapkan", + "users": "Pengguna", + "admins": "Admin", + "guests": "Tamu", + "on_track": "Sesuai Jalur", + "off_track": "Menyimpang", + "timeline": "Linimasa", + "completion": "Penyelesaian", + "upcoming": "Mendatang", + "completed": "Selesai", + "in_progress": "Sedang berlangsung", + "planned": "Direncanakan", + "paused": "Dijedaikan" }, "chart": { "x_axis": "Sumbu-X", @@ -1316,19 +1328,6 @@ "custom": "Analitik Kustom" }, "empty_state": { - "general": { - "title": "Lacak kemajuan, beban kerja, dan alokasi. Temukan tren, hilangkan penghalang, dan percepat pekerjaan", - "description": "Lihat lingkup dibandingkan permintaan, perkiraan, dan lingkup cree. Dapatkan kinerja oleh anggota tim dan tim, dan pastikan proyek Anda berjalan tepat waktu.", - "primary_button": { - "text": "Mulai proyek pertama Anda", - "comic": { - "title": "Analitik bekerja terbaik dengan Siklus + Modul", - "description": "Pertama, bagi item kerja Anda ke dalam Siklus dan, jika memungkinkan, kelompokkan item kerja yang menjangkau lebih dari satu siklus ke dalam Modul. Lihat kedua fungsi pada navigasi kiri." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Item pekerjaan yang ditugaskan kepada Anda, dipecah berdasarkan status, akan muncul di sini.", "title": "Belum ada data" @@ -1344,21 +1343,22 @@ }, "created_vs_resolved": "Dibuat vs Diselesaikan", "customized_insights": "Wawasan yang Disesuaikan", - "backlog_work_items": "Item pekerjaan backlog", + "backlog_work_items": "{entity} backlog", "active_projects": "Proyek Aktif", "trend_on_charts": "Tren pada grafik", "all_projects": "Semua Proyek", "summary_of_projects": "Ringkasan Proyek", "project_insights": "Wawasan Proyek", - "started_work_items": "Item pekerjaan yang telah dimulai", - "total_work_items": "Total item pekerjaan", + "started_work_items": "{entity} yang telah dimulai", + "total_work_items": "Total {entity}", "total_projects": "Total Proyek", "total_admins": "Total Admin", "total_users": "Total Pengguna", "total_intake": "Total Pemasukan", - "un_started_work_items": "Item pekerjaan yang belum dimulai", + "un_started_work_items": "{entity} yang belum dimulai", "total_guests": "Total Tamu", - "completed_work_items": "Item pekerjaan yang telah selesai" + "completed_work_items": "{entity} yang telah selesai", + "total": "Total {entity}" }, "workspace_projects": { "label": "{count, plural, one {Proyek} other {Proyek}}", diff --git a/packages/i18n/src/locales/it/translations.json b/packages/i18n/src/locales/it/translations.json index 86537bd4f92..03a1160bf26 100644 --- a/packages/i18n/src/locales/it/translations.json +++ b/packages/i18n/src/locales/it/translations.json @@ -865,7 +865,19 @@ "view": "Visualizza", "deactivated_user": "Utente disattivato", "apply": "Applica", - "applying": "Applicazione" + "applying": "Applicazione", + "users": "Utenti", + "admins": "Amministratori", + "guests": "Ospiti", + "on_track": "In linea", + "off_track": "Fuori rotta", + "timeline": "Cronologia", + "completion": "Completamento", + "upcoming": "In arrivo", + "completed": "Completato", + "in_progress": "In corso", + "planned": "Pianificato", + "paused": "In pausa" }, "chart": { "x_axis": "Asse X", @@ -1315,19 +1327,6 @@ "custom": "Analisi personalizzata" }, "empty_state": { - "general": { - "title": "Traccia il progresso, i carichi di lavoro e le assegnazioni. Individua tendenze, rimuovi gli ostacoli e accelera il lavoro", - "description": "Visualizza l'ambito rispetto alla domanda, le stime e il fenomeno del scope creep. Ottieni le prestazioni dei membri del team e dei team, e assicurati che il tuo progetto rispetti le scadenze.", - "primary_button": { - "text": "Inizia il tuo primo progetto", - "comic": { - "title": "Le analisi funzionano meglio con Cicli + Moduli", - "description": "Prima, definisci i tuoi elementi di lavoro in cicli e, se puoi, raggruppa quelli che si estendono per più di un ciclo in moduli. Dai un'occhiata ad entrambi nel menu di sinistra." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Gli elementi di lavoro assegnati a te, suddivisi per stato, verranno visualizzati qui.", "title": "Nessun dato disponibile" @@ -1343,21 +1342,22 @@ }, "created_vs_resolved": "Creato vs Risolto", "customized_insights": "Approfondimenti personalizzati", - "backlog_work_items": "Elementi di lavoro nel backlog", + "backlog_work_items": "{entity} nel backlog", "active_projects": "Progetti attivi", "trend_on_charts": "Tendenza nei grafici", "all_projects": "Tutti i progetti", "summary_of_projects": "Riepilogo dei progetti", "project_insights": "Approfondimenti sul progetto", - "started_work_items": "Elementi di lavoro iniziati", - "total_work_items": "Totale elementi di lavoro", + "started_work_items": "{entity} iniziati", + "total_work_items": "Totale {entity}", "total_projects": "Progetti totali", "total_admins": "Totale amministratori", "total_users": "Totale utenti", "total_intake": "Entrate totali", - "un_started_work_items": "Elementi di lavoro non avviati", + "un_started_work_items": "{entity} non avviati", "total_guests": "Totale ospiti", - "completed_work_items": "Elementi di lavoro completati" + "completed_work_items": "{entity} completati", + "total": "Totale {entity}" }, "workspace_projects": { "label": "{count, plural, one {Progetto} other {Progetti}}", diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json index 52d58c8a502..0c081974e3b 100644 --- a/packages/i18n/src/locales/ja/translations.json +++ b/packages/i18n/src/locales/ja/translations.json @@ -867,7 +867,19 @@ "view": "ビュー", "deactivated_user": "無効化されたユーザー", "apply": "適用", - "applying": "適用中" + "applying": "適用中", + "users": "ユーザー", + "admins": "管理者", + "guests": "ゲスト", + "on_track": "順調", + "off_track": "遅れ", + "timeline": "タイムライン", + "completion": "完了", + "upcoming": "今後の予定", + "completed": "完了", + "in_progress": "進行中", + "planned": "計画済み", + "paused": "一時停止" }, "chart": { "x_axis": "エックス アクシス", @@ -1317,19 +1329,6 @@ "custom": "カスタムアナリティクス" }, "empty_state": { - "general": { - "title": "進捗、ワークロード、割り当てを追跡。傾向を把握し、ブロッカーを解消して、作業をより速く進めましょう", - "description": "スコープと需要、見積もり、スコープクリープを確認できます。チームメンバーとチームのパフォーマンスを把握し、プロジェクトが予定通りに進むようにします。", - "primary_button": { - "text": "最初のプロジェクトを開始", - "comic": { - "title": "アナリティクスはサイクル + モジュールで最も効果を発揮", - "description": "まず、作業項目をサイクルでタイムボックス化し、可能であれば、複数のサイクルにまたがる作業項目をモジュールにグループ化します。左のナビゲーションで両方を確認してください。" - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "あなたに割り当てられた作業項目は、ステータスごとに分類されてここに表示されます。", "title": "まだデータがありません" @@ -1345,21 +1344,22 @@ }, "created_vs_resolved": "作成 vs 解決", "customized_insights": "カスタマイズされたインサイト", - "backlog_work_items": "バックログの作業項目", + "backlog_work_items": "バックログの{entity}", "active_projects": "アクティブなプロジェクト", "trend_on_charts": "グラフの傾向", "all_projects": "すべてのプロジェクト", "summary_of_projects": "プロジェクトの概要", "project_insights": "プロジェクトのインサイト", - "started_work_items": "開始された作業項目", - "total_work_items": "作業項目の合計", + "started_work_items": "開始された{entity}", + "total_work_items": "{entity}の合計", "total_projects": "プロジェクト合計", "total_admins": "管理者の合計", "total_users": "ユーザー総数", "total_intake": "総収入", - "un_started_work_items": "未開始の作業項目", + "un_started_work_items": "未開始の{entity}", "total_guests": "ゲストの合計", - "completed_work_items": "完了した作業項目" + "completed_work_items": "完了した{entity}", + "total": "{entity}の合計" }, "workspace_projects": { "label": "{count, plural, one {プロジェクト} other {プロジェクト}}", diff --git a/packages/i18n/src/locales/ko/translations.json b/packages/i18n/src/locales/ko/translations.json index 7340ec40099..66be56308db 100644 --- a/packages/i18n/src/locales/ko/translations.json +++ b/packages/i18n/src/locales/ko/translations.json @@ -868,7 +868,19 @@ "view": "보기", "deactivated_user": "비활성화된 사용자", "apply": "적용", - "applying": "적용 중" + "applying": "적용 중", + "users": "사용자", + "admins": "관리자", + "guests": "게스트", + "on_track": "계획대로 진행 중", + "off_track": "계획 이탈", + "timeline": "타임라인", + "completion": "완료", + "upcoming": "예정된", + "completed": "완료됨", + "in_progress": "진행 중", + "planned": "계획된", + "paused": "일시 중지됨" }, "chart": { "x_axis": "X축", @@ -1318,19 +1330,6 @@ "custom": "맞춤형 분석" }, "empty_state": { - "general": { - "title": "진행 상황, 작업량 및 할당을 추적하세요. 트렌드를 파악하고, 차단 요소를 제거하며, 작업을 더 빠르게 진행하세요", - "description": "범위 대 수요, 추정치 및 범위 크리프를 확인하세요. 팀원과 팀의 성과를 확인하고 프로젝트가 제시간에 진행되도록 하세요.", - "primary_button": { - "text": "첫 번째 프로젝트 시작", - "comic": { - "title": "분석은 주기 + 모듈과 함께 작동합니다", - "description": "먼저 작업 항목을 주기로 시간 상자화하고, 주기를 초과하는 작업 항목을 모듈로 그룹화하세요. 왼쪽 탐색에서 둘 다 확인하세요." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "귀하에게 할당된 작업 항목이 상태별로 나누어 여기에 표시됩니다.", "title": "아직 데이터가 없습니다" @@ -1346,21 +1345,22 @@ }, "created_vs_resolved": "생성됨 vs 해결됨", "customized_insights": "맞춤형 인사이트", - "backlog_work_items": "백로그 작업 항목", + "backlog_work_items": "백로그 {entity}", "active_projects": "활성 프로젝트", "trend_on_charts": "차트의 추세", "all_projects": "모든 프로젝트", "summary_of_projects": "프로젝트 요약", "project_insights": "프로젝트 인사이트", - "started_work_items": "시작된 작업 항목", - "total_work_items": "총 작업 항목", + "started_work_items": "시작된 {entity}", + "total_work_items": "총 {entity}", "total_projects": "총 프로젝트 수", "total_admins": "총 관리자 수", "total_users": "총 사용자 수", "total_intake": "총 수입", - "un_started_work_items": "시작되지 않은 작업 항목", + "un_started_work_items": "시작되지 않은 {entity}", "total_guests": "총 게스트 수", - "completed_work_items": "완료된 작업 항목" + "completed_work_items": "완료된 {entity}", + "total": "총 {entity}" }, "workspace_projects": { "label": "{count, plural, one {프로젝트} other {프로젝트}}", diff --git a/packages/i18n/src/locales/pl/translations.json b/packages/i18n/src/locales/pl/translations.json index 1563a064d02..fe74d5c4f15 100644 --- a/packages/i18n/src/locales/pl/translations.json +++ b/packages/i18n/src/locales/pl/translations.json @@ -868,7 +868,19 @@ "view": "Widok", "deactivated_user": "Dezaktywowany użytkownik", "apply": "Zastosuj", - "applying": "Zastosowanie" + "applying": "Zastosowanie", + "users": "Użytkownicy", + "admins": "Administratorzy", + "guests": "Goście", + "on_track": "Na dobrej drodze", + "off_track": "Poza planem", + "timeline": "Oś czasu", + "completion": "Zakończenie", + "upcoming": "Nadchodzące", + "completed": "Zakończone", + "in_progress": "W trakcie", + "planned": "Zaplanowane", + "paused": "Wstrzymane" }, "chart": { "x_axis": "Oś X", @@ -1318,19 +1330,6 @@ "custom": "Analizy niestandardowe" }, "empty_state": { - "general": { - "title": "Śledź postępy, obciążenie i alokacje. Identyfikuj trendy, usuwaj przeszkody i przyspieszaj pracę", - "description": "Obserwuj zakres vs. zapotrzebowanie, szacunki i zakres. Sprawdzaj wydajność członków i zespołów, upewnij się, że projekty kończą się na czas.", - "primary_button": { - "text": "Zacznij pierwszy projekt", - "comic": { - "title": "Analizy najlepiej działają z Cyklem + Modułami", - "description": "Najpierw ogranicz pracę w cyklach i grupuj zadania w modułach obejmujących wiele cykli. Znajdziesz je w menu po lewej." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Przypisane do Ciebie elementy pracy, podzielone według stanu, pojawią się tutaj.", "title": "Brak danych" @@ -1346,21 +1345,22 @@ }, "created_vs_resolved": "Utworzone vs Rozwiązane", "customized_insights": "Dostosowane informacje", - "backlog_work_items": "Elementy pracy w backlogu", + "backlog_work_items": "{entity} w backlogu", "active_projects": "Aktywne projekty", "trend_on_charts": "Trend na wykresach", "all_projects": "Wszystkie projekty", "summary_of_projects": "Podsumowanie projektów", "project_insights": "Wgląd w projekt", - "started_work_items": "Rozpoczęte elementy pracy", - "total_work_items": "Łączna liczba elementów pracy", + "started_work_items": "Rozpoczęte {entity}", + "total_work_items": "Łączna liczba {entity}", "total_projects": "Łączna liczba projektów", "total_admins": "Łączna liczba administratorów", "total_users": "Łączna liczba użytkowników", "total_intake": "Całkowity dochód", - "un_started_work_items": "Nierozpoczęte elementy pracy", + "un_started_work_items": "Nierozpoczęte {entity}", "total_guests": "Łączna liczba gości", - "completed_work_items": "Ukończone elementy pracy" + "completed_work_items": "Ukończone {entity}", + "total": "Łączna liczba {entity}" }, "workspace_projects": { "label": "{count, plural, one {Projekt} few {Projekty} other {Projektów}}", diff --git a/packages/i18n/src/locales/pt-BR/translations.json b/packages/i18n/src/locales/pt-BR/translations.json index 4f578de1cc0..bb8d1da6784 100644 --- a/packages/i18n/src/locales/pt-BR/translations.json +++ b/packages/i18n/src/locales/pt-BR/translations.json @@ -868,7 +868,19 @@ "view": "Visualizar", "deactivated_user": "Usuário desativado", "apply": "Aplicar", - "applying": "Aplicando" + "applying": "Aplicando", + "users": "Usuários", + "admins": "Administradores", + "guests": "Convidados", + "on_track": "No caminho certo", + "off_track": "Fora do caminho", + "timeline": "Linha do tempo", + "completion": "Conclusão", + "upcoming": "Próximo", + "completed": "Concluído", + "in_progress": "Em andamento", + "planned": "Planejado", + "paused": "Pausado" }, "chart": { "x_axis": "Eixo X", @@ -1318,19 +1330,6 @@ "custom": "Análises Personalizadas" }, "empty_state": { - "general": { - "title": "Acompanhe o progresso, as cargas de trabalho e as alocações. Identifique tendências, remova bloqueadores e mova o trabalho mais rapidamente", - "description": "Veja o escopo versus a demanda, as estimativas e o aumento do escopo. Obtenha o desempenho por membros da equipe e equipes, e certifique-se de que seu projeto seja executado no prazo.", - "primary_button": { - "text": "Comece seu primeiro projeto", - "comic": { - "title": "A análise funciona melhor com Ciclos + Módulos", - "description": "Primeiro, coloque seus itens de trabalho em Ciclos e, se puder, agrupe os itens de trabalho que abrangem mais de um ciclo em Módulos. Confira ambos na navegação à esquerda." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Os itens de trabalho atribuídos a você, divididos por estado, aparecerão aqui.", "title": "Ainda não há dados" @@ -1346,21 +1345,22 @@ }, "created_vs_resolved": "Criado vs Resolvido", "customized_insights": "Insights personalizados", - "backlog_work_items": "Itens de trabalho no backlog", + "backlog_work_items": "{entity} no backlog", "active_projects": "Projetos ativos", "trend_on_charts": "Tendência nos gráficos", "all_projects": "Todos os projetos", "summary_of_projects": "Resumo dos projetos", "project_insights": "Insights do projeto", - "started_work_items": "Itens de trabalho iniciados", - "total_work_items": "Total de itens de trabalho", + "started_work_items": "{entity} iniciados", + "total_work_items": "Total de {entity}", "total_projects": "Total de projetos", "total_admins": "Total de administradores", "total_users": "Total de usuários", "total_intake": "Receita total", - "un_started_work_items": "Itens de trabalho não iniciados", + "un_started_work_items": "{entity} não iniciados", "total_guests": "Total de convidados", - "completed_work_items": "Itens de trabalho concluídos" + "completed_work_items": "{entity} concluídos", + "total": "Total de {entity}" }, "workspace_projects": { "label": "{count, plural, one {Projeto} other {Projetos}}", diff --git a/packages/i18n/src/locales/ro/translations.json b/packages/i18n/src/locales/ro/translations.json index 0b53d018a0e..3c8ab4502ed 100644 --- a/packages/i18n/src/locales/ro/translations.json +++ b/packages/i18n/src/locales/ro/translations.json @@ -866,7 +866,19 @@ "view": "Vizualizează", "deactivated_user": "Utilizator dezactivat", "apply": "Aplică", - "applying": "Aplicând" + "applying": "Aplicând", + "users": "Utilizatori", + "admins": "Administratori", + "guests": "Invitați", + "on_track": "Pe drumul cel bun", + "off_track": "În afara traiectoriei", + "timeline": "Cronologie", + "completion": "Finalizare", + "upcoming": "Viitor", + "completed": "Finalizat", + "in_progress": "În desfășurare", + "planned": "Planificat", + "paused": "Pauzat" }, "chart": { "x_axis": "axa-X", @@ -1316,19 +1328,6 @@ "custom": "Analitice personalizate" }, "empty_state": { - "general": { - "title": "Urmărește progresul, activitățile și alocările. Observă tendințele, elimină blocajele și accelerează munca", - "description": "Vezi raportul dintre activitățile asumate și cerere, estimările și eventualele extinderi neplanificate ale activităților asumate. Obține performanța pe membri și echipe și asigură-te că proiectul tău se încadrează în timp.", - "primary_button": { - "text": "Începe primul tău proiect", - "comic": { - "title": "Statisticile funcționează cel mai bine cu Cicluri + Module", - "description": "Mai întâi, încadrează-ți activitățile în Cicluri și, dacă poți, grupează-le pe cele care se întind pe mai multe cicluri în Module. Le găsești în meniul din stânga." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Elementele de lucru atribuite ție, împărțite pe stări, vor apărea aici.", "title": "Nu există date încă" @@ -1344,21 +1343,22 @@ }, "created_vs_resolved": "Creat vs Rezolvat", "customized_insights": "Perspective personalizate", - "backlog_work_items": "Elemente de lucru din backlog", + "backlog_work_items": "{entity} din backlog", "active_projects": "Proiecte active", "trend_on_charts": "Tendință în grafice", "all_projects": "Toate proiectele", "summary_of_projects": "Sumarul proiectelor", "project_insights": "Informații despre proiect", - "started_work_items": "Elemente de lucru începute", - "total_work_items": "Totalul elementelor de lucru", + "started_work_items": "{entity} începute", + "total_work_items": "Totalul {entity}", "total_projects": "Total proiecte", "total_admins": "Total administratori", "total_users": "Total utilizatori", "total_intake": "Venit total", - "un_started_work_items": "Elemente de lucru neîncepute", + "un_started_work_items": "{entity} neîncepute", "total_guests": "Total invitați", - "completed_work_items": "Elemente de lucru finalizate" + "completed_work_items": "{entity} finalizate", + "total": "Totalul {entity}" }, "workspace_projects": { "label": "{count, plural, one {Proiect} other {Proiecte}}", diff --git a/packages/i18n/src/locales/ru/translations.json b/packages/i18n/src/locales/ru/translations.json index 483d1baeab1..624179ef63a 100644 --- a/packages/i18n/src/locales/ru/translations.json +++ b/packages/i18n/src/locales/ru/translations.json @@ -868,7 +868,19 @@ "view": "Просмотр", "deactivated_user": "Деактивированный пользователь", "apply": "Применить", - "applying": "Применение" + "applying": "Применение", + "users": "Пользователи", + "admins": "Администраторы", + "guests": "Гости", + "on_track": "По плану", + "off_track": "Отклонение от плана", + "timeline": "Хронология", + "completion": "Завершение", + "upcoming": "Предстоящие", + "completed": "Завершено", + "in_progress": "В процессе", + "planned": "Запланировано", + "paused": "На паузе" }, "chart": { "x_axis": "Ось X", @@ -1318,19 +1330,6 @@ "custom": "Пользовательская аналитика" }, "empty_state": { - "general": { - "title": "Отслеживайте прогресс, загрузку и распределение ресурсов", - "description": "Анализируйте объёмы работ, оценивайте сроки и контролируйте выполнение проектов. Отслеживайте производительность команды и соблюдайте сроки.", - "primary_button": { - "text": "Начать первый проект", - "comic": { - "title": "Аналитика лучше всего работает с Циклами + Модулями", - "description": "Сначала группируйте рабочие элементы в Циклы, а при возможности - объединяйте рабочие элементы в Модули. Найдите оба раздела в левом меню." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Назначенные вам рабочие элементы, разбитые по статусам, появятся здесь.", "title": "Данных пока нет" @@ -1346,21 +1345,22 @@ }, "created_vs_resolved": "Создано vs Решено", "customized_insights": "Индивидуальные аналитические данные", - "backlog_work_items": "Элементы работы в бэклоге", + "backlog_work_items": "{entity} в бэклоге", "active_projects": "Активные проекты", "trend_on_charts": "Тренд на графиках", "all_projects": "Все проекты", "summary_of_projects": "Сводка по проектам", "project_insights": "Аналитика проекта", - "started_work_items": "Начатые рабочие элементы", - "total_work_items": "Общее количество рабочих элементов", + "started_work_items": "Начатые {entity}", + "total_work_items": "Общее количество {entity}", "total_projects": "Всего проектов", "total_admins": "Всего администраторов", "total_users": "Всего пользователей", "total_intake": "Общий доход", - "un_started_work_items": "Не начатые рабочие элементы", + "un_started_work_items": "Не начатые {entity}", "total_guests": "Всего гостей", - "completed_work_items": "Завершённые рабочие элементы" + "completed_work_items": "Завершённые {entity}", + "total": "Общее количество {entity}" }, "workspace_projects": { "label": "{count, plural, one {Проект} other {Проекты}}", diff --git a/packages/i18n/src/locales/sk/translations.json b/packages/i18n/src/locales/sk/translations.json index 55d7f01c1f4..d87be6ce5bb 100644 --- a/packages/i18n/src/locales/sk/translations.json +++ b/packages/i18n/src/locales/sk/translations.json @@ -868,7 +868,19 @@ "view": "Zobraziť", "deactivated_user": "Deaktivovaný používateľ", "apply": "Použiť", - "applying": "Používanie" + "applying": "Používanie", + "users": "Používatelia", + "admins": "Administrátori", + "guests": "Hostia", + "on_track": "Na správnej ceste", + "off_track": "Mimo plán", + "timeline": "Časová os", + "completion": "Dokončenie", + "upcoming": "Nadchádzajúce", + "completed": "Dokončené", + "in_progress": "Prebieha", + "planned": "Plánované", + "paused": "Pozastavené" }, "chart": { "x_axis": "Os X", @@ -1318,19 +1330,6 @@ "custom": "Vlastná analytika" }, "empty_state": { - "general": { - "title": "Sledujte pokrok, vyťaženie a alokácie. Identifikujte trendy, odstráňte prekážky a zrýchlite prácu", - "description": "Sledujte rozsah vs. dopyt, odhady a rozsah. Zistite výkonnosť členov a tímov, zabezpečte včasné dokončenie projektov.", - "primary_button": { - "text": "Začnite prvý projekt", - "comic": { - "title": "Analytika funguje najlepšie s Cykly + Moduly", - "description": "Najprv časovo ohraničte prácu do cyklov a zoskupte položky presahujúce cyklus do modulov. Nájdete ich v ľavom menu." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Pracovné položky priradené vám, rozdelené podľa stavu, sa zobrazia tu.", "title": "Zatiaľ žiadne údaje" @@ -1346,21 +1345,22 @@ }, "created_vs_resolved": "Vytvorené vs Vyriešené", "customized_insights": "Prispôsobené prehľady", - "backlog_work_items": "Pracovné položky v backlogu", + "backlog_work_items": "{entity} v backlogu", "active_projects": "Aktívne projekty", "trend_on_charts": "Trend na grafoch", "all_projects": "Všetky projekty", "summary_of_projects": "Súhrn projektov", "project_insights": "Prehľad projektu", - "started_work_items": "Spustené pracovné položky", - "total_work_items": "Celkový počet pracovných položiek", + "started_work_items": "Spustené {entity}", + "total_work_items": "Celkový počet {entity}", "total_projects": "Celkový počet projektov", "total_admins": "Celkový počet administrátorov", "total_users": "Celkový počet používateľov", "total_intake": "Celkový príjem", - "un_started_work_items": "Nespustené pracovné položky", + "un_started_work_items": "Nespustené {entity}", "total_guests": "Celkový počet hostí", - "completed_work_items": "Dokončené pracovné položky" + "completed_work_items": "Dokončené {entity}", + "total": "Celkový počet {entity}" }, "workspace_projects": { "label": "{count, plural, one {Projekt} few {Projekty} other {Projektov}}", diff --git a/packages/i18n/src/locales/tr-TR/translations.json b/packages/i18n/src/locales/tr-TR/translations.json index 8684f9edc30..7c9219b858e 100644 --- a/packages/i18n/src/locales/tr-TR/translations.json +++ b/packages/i18n/src/locales/tr-TR/translations.json @@ -869,7 +869,19 @@ "view": "Görünüm", "deactivated_user": "Devre dışı bırakılmış kullanıcı", "apply": "Uygula", - "applying": "Uygulanıyor" + "applying": "Uygulanıyor", + "users": "Kullanıcılar", + "admins": "Yöneticiler", + "guests": "Misafirler", + "on_track": "Yolunda", + "off_track": "Yolunda değil", + "timeline": "Zaman çizelgesi", + "completion": "Tamamlama", + "upcoming": "Yaklaşan", + "completed": "Tamamlandı", + "in_progress": "Devam ediyor", + "planned": "Planlandı", + "paused": "Durduruldu" }, "chart": { "x_axis": "X ekseni", @@ -1319,19 +1331,6 @@ "custom": "Özel Analitik" }, "empty_state": { - "general": { - "title": "İlerlemeyi, iş yükünü ve tahsisatları izleyin. Eğilimleri tespit edin, engelleri kaldırın ve işleri hızlandırın", - "description": "Kapsam ve talep, tahminler ve kapsam genişlemesini görün. Takım üyeleri ve ekiplerin performansını izleyin ve projenizin zamanında ilerlemesini sağlayın.", - "primary_button": { - "text": "İlk projenizi başlatın", - "comic": { - "title": "Analitik Döngüler + Modüllerle en iyi şekilde çalışır", - "description": "Öncelikle, iş öğelerinizi Döngülere zamanlayın ve mümkünse, bir döngüden uzun süren iş öğelerini Modüllerde gruplayın. Her ikisini de sol gezintide bulabilirsiniz." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Size atanan iş öğeleri, duruma göre ayrılarak burada gösterilecektir.", "title": "Henüz veri yok" @@ -1347,21 +1346,22 @@ }, "created_vs_resolved": "Oluşturulan vs Çözülen", "customized_insights": "Özelleştirilmiş İçgörüler", - "backlog_work_items": "Backlog iş öğeleri", + "backlog_work_items": "Backlog {entity}", "active_projects": "Aktif Projeler", "trend_on_charts": "Grafiklerdeki eğilim", "all_projects": "Tüm Projeler", "summary_of_projects": "Projelerin Özeti", "project_insights": "Proje İçgörüleri", - "started_work_items": "Başlatılan iş öğeleri", - "total_work_items": "Toplam iş öğesi", + "started_work_items": "Başlatılan {entity}", + "total_work_items": "Toplam {entity}", "total_projects": "Toplam Proje", "total_admins": "Toplam Yönetici", "total_users": "Toplam Kullanıcı", "total_intake": "Toplam Gelir", - "un_started_work_items": "Başlanmamış iş öğeleri", + "un_started_work_items": "Başlanmamış {entity}", "total_guests": "Toplam Misafir", - "completed_work_items": "Tamamlanmış iş öğeleri" + "completed_work_items": "Tamamlanmış {entity}", + "total": "Toplam {entity}" }, "workspace_projects": { "label": "{count, plural, one {Proje} other {Projeler}}", diff --git a/packages/i18n/src/locales/ua/translations.json b/packages/i18n/src/locales/ua/translations.json index f9b43a08d2a..603f6c0c56c 100644 --- a/packages/i18n/src/locales/ua/translations.json +++ b/packages/i18n/src/locales/ua/translations.json @@ -868,7 +868,19 @@ "view": "Подання", "deactivated_user": "Деактивований користувач", "apply": "Застосувати", - "applying": "Застосовується" + "applying": "Застосовується", + "users": "Користувачі", + "admins": "Адміністратори", + "guests": "Гості", + "on_track": "У межах графіку", + "off_track": "Поза графіком", + "timeline": "Хронологія", + "completion": "Завершення", + "upcoming": "Майбутнє", + "completed": "Завершено", + "in_progress": "В процесі", + "planned": "Заплановано", + "paused": "Призупинено" }, "chart": { "x_axis": "Вісь X", @@ -1318,19 +1330,6 @@ "custom": "Користувацька аналітика" }, "empty_state": { - "general": { - "title": "Відстежуйте прогрес, навантаження й розподіл. Виявляйте тенденції, усувайте перешкоди й прискорюйте роботу", - "description": "Стежте за обсягом проти попиту, оцінками та обсягом. Визначайте ефективність учасників і команд, аби вчасно виконувати проєкти.", - "primary_button": { - "text": "Розпочніть перший проєкт", - "comic": { - "title": "Аналітика найкраще працює з Циклами + Модулями", - "description": "Спочатку обмежте роботу в часі через Цикли та згрупуйте робочі одиниці, які тривають довше, у Модулі. Все це в лівому меню." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Призначені вам робочі елементи, розбиті за станом, з’являться тут.", "title": "Ще немає даних" @@ -1346,21 +1345,22 @@ }, "created_vs_resolved": "Створено vs Вирішено", "customized_insights": "Персоналізовані аналітичні дані", - "backlog_work_items": "Робочі елементи у беклозі", + "backlog_work_items": "{entity} у беклозі", "active_projects": "Активні проєкти", "trend_on_charts": "Тенденція на графіках", "all_projects": "Усі проєкти", "summary_of_projects": "Зведення проєктів", "project_insights": "Аналітика проєкту", - "started_work_items": "Розпочаті робочі елементи", - "total_work_items": "Усього робочих елементів", + "started_work_items": "Розпочаті {entity}", + "total_work_items": "Усього {entity}", "total_projects": "Усього проєктів", "total_admins": "Усього адміністраторів", "total_users": "Усього користувачів", "total_intake": "Загальний дохід", - "un_started_work_items": "Нерозпочаті робочі елементи", + "un_started_work_items": "Нерозпочаті {entity}", "total_guests": "Усього гостей", - "completed_work_items": "Завершені робочі елементи" + "completed_work_items": "Завершені {entity}", + "total": "Усього {entity}" }, "workspace_projects": { "label": "{count, plural, one {Проєкт} few {Проєкти} other {Проєктів}}", diff --git a/packages/i18n/src/locales/vi-VN/translations.json b/packages/i18n/src/locales/vi-VN/translations.json index 1bef1be3fb5..f78aea31a8d 100644 --- a/packages/i18n/src/locales/vi-VN/translations.json +++ b/packages/i18n/src/locales/vi-VN/translations.json @@ -867,7 +867,19 @@ "view": "Xem", "deactivated_user": "Người dùng bị vô hiệu hóa", "apply": "Áp dụng", - "applying": "Đang áp dụng" + "applying": "Đang áp dụng", + "users": "Người dùng", + "admins": "Quản trị viên", + "guests": "Khách", + "on_track": "Đúng tiến độ", + "off_track": "Chệch hướng", + "timeline": "Dòng thời gian", + "completion": "Hoàn thành", + "upcoming": "Sắp tới", + "completed": "Đã hoàn thành", + "in_progress": "Đang tiến hành", + "planned": "Đã lên kế hoạch", + "paused": "Tạm dừng" }, "chart": { "x_axis": "Trục X", @@ -1317,19 +1329,6 @@ "custom": "Phân tích tùy chỉnh" }, "empty_state": { - "general": { - "title": "Theo dõi tiến độ, khối lượng công việc và phân công. Khám phá xu hướng, loại bỏ rào cản và đẩy nhanh công việc", - "description": "Xem phạm vi so với nhu cầu, ước tính và mở rộng phạm vi. Nhận hiệu suất của thành viên nhóm và nhóm, đảm bảo dự án của bạn đúng tiến độ.", - "primary_button": { - "text": "Bắt đầu dự án đầu tiên của bạn", - "comic": { - "title": "Phân tích hoạt động tốt nhất trong chu kỳ + mô-đun", - "description": "Đầu tiên, giới hạn mục công việc của bạn trong chu kỳ và nếu có thể, nhóm mục công việc kéo dài nhiều chu kỳ thành mô-đun. Xem cả hai trong thanh điều hướng bên trái." - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "Các hạng mục công việc được giao cho bạn, phân loại theo trạng thái, sẽ hiển thị tại đây.", "title": "Chưa có dữ liệu" @@ -1345,21 +1344,22 @@ }, "created_vs_resolved": "Đã tạo vs Đã giải quyết", "customized_insights": "Thông tin chi tiết tùy chỉnh", - "backlog_work_items": "Các hạng mục công việc tồn đọng", + "backlog_work_items": "{entity} tồn đọng", "active_projects": "Dự án đang hoạt động", "trend_on_charts": "Xu hướng trên biểu đồ", "all_projects": "Tất cả dự án", "summary_of_projects": "Tóm tắt dự án", "project_insights": "Thông tin chi tiết dự án", - "started_work_items": "Hạng mục công việc đã bắt đầu", - "total_work_items": "Tổng số hạng mục công việc", + "started_work_items": "{entity} đã bắt đầu", + "total_work_items": "Tổng số {entity}", "total_projects": "Tổng số dự án", "total_admins": "Tổng số quản trị viên", "total_users": "Tổng số người dùng", "total_intake": "Tổng thu", - "un_started_work_items": "Hạng mục công việc chưa bắt đầu", + "un_started_work_items": "{entity} chưa bắt đầu", "total_guests": "Tổng số khách", - "completed_work_items": "Hạng mục công việc đã hoàn thành" + "completed_work_items": "{entity} đã hoàn thành", + "total": "Tổng số {entity}" }, "workspace_projects": { "label": "{count, plural, one {dự án} other {dự án}}", diff --git a/packages/i18n/src/locales/zh-CN/translations.json b/packages/i18n/src/locales/zh-CN/translations.json index 1a417d6baa5..b785d864981 100644 --- a/packages/i18n/src/locales/zh-CN/translations.json +++ b/packages/i18n/src/locales/zh-CN/translations.json @@ -867,7 +867,19 @@ "view": "查看", "deactivated_user": "已停用用户", "apply": "应用", - "applying": "应用中" + "applying": "应用中", + "users": "用户", + "admins": "管理员", + "guests": "访客", + "on_track": "进展顺利", + "off_track": "偏离轨道", + "timeline": "时间轴", + "completion": "完成", + "upcoming": "即将发生", + "completed": "已完成", + "in_progress": "进行中", + "planned": "已计划", + "paused": "暂停" }, "chart": { "x_axis": "X轴", @@ -1317,19 +1329,6 @@ "custom": "自定义分析" }, "empty_state": { - "general": { - "title": "跟踪进度、工作量和分配。发现趋势、消除障碍并加快工作进度", - "description": "查看范围与需求、估算和范围蔓延。获取团队成员和团队的表现,确保您的项目按时运行。", - "primary_button": { - "text": "开始您的第一个项目", - "comic": { - "title": "分析在周期 + 模块中效果最佳", - "description": "首先,将您的工作项限定在周期中,如果可能的话,将跨越多个周期的工作项分组到模块中。在左侧导航栏中查看这两项。" - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "分配给您的工作项将按状态分类显示在此处。", "title": "暂无数据" @@ -1345,21 +1344,22 @@ }, "created_vs_resolved": "已创建 vs 已解决", "customized_insights": "自定义洞察", - "backlog_work_items": "待办工作项", + "backlog_work_items": "待办的{entity}", "active_projects": "活跃项目", "trend_on_charts": "图表趋势", "all_projects": "所有项目", "summary_of_projects": "项目概览", "project_insights": "项目洞察", - "started_work_items": "已开始的工作项", - "total_work_items": "工作项总数", + "started_work_items": "已开始的{entity}", + "total_work_items": "{entity}总数", "total_projects": "项目总数", "total_admins": "管理员总数", "total_users": "用户总数", "total_intake": "总收入", - "un_started_work_items": "未开始的工作项", + "un_started_work_items": "未开始的{entity}", "total_guests": "访客总数", - "completed_work_items": "已完成的工作项" + "completed_work_items": "已完成的{entity}", + "total": "{entity}总数" }, "workspace_projects": { "label": "{count, plural, one {项目} other {项目}}", diff --git a/packages/i18n/src/locales/zh-TW/translations.json b/packages/i18n/src/locales/zh-TW/translations.json index 611875f2ac2..b75d720f549 100644 --- a/packages/i18n/src/locales/zh-TW/translations.json +++ b/packages/i18n/src/locales/zh-TW/translations.json @@ -868,7 +868,19 @@ "view": "檢視", "deactivated_user": "已停用用戶", "apply": "應用", - "applying": "應用中" + "applying": "應用中", + "users": "使用者", + "admins": "管理員", + "guests": "訪客", + "on_track": "進展順利", + "off_track": "偏離軌道", + "timeline": "時間軸", + "completion": "完成", + "upcoming": "即將發生", + "completed": "已完成", + "in_progress": "進行中", + "planned": "已計劃", + "paused": "暫停" }, "chart": { "x_axis": "X 軸", @@ -1318,19 +1330,6 @@ "custom": "自訂分析" }, "empty_state": { - "general": { - "title": "追蹤進度、工作量和分配。發現趨勢、移除阻礙,加快工作進展", - "description": "檢視範圍與需求、評估和範圍擴展。取得團隊成員和團隊的績效,確保您的專案按時進行。", - "primary_button": { - "text": "開始您的第一個專案", - "comic": { - "title": "分析最適合搭配週期 + 模組使用", - "description": "首先,將您的工作事項時間區段到週期中,如果可以的話,將跨週期的工作事項分組到模組中。請檢視左側導覽列中的兩個功能。" - } - } - } - }, - "empty_state_v2": { "customized_insights": { "description": "指派給您的工作項目將依狀態分類顯示在此處。", "title": "尚無資料" @@ -1346,21 +1345,22 @@ }, "created_vs_resolved": "已建立 vs 已解決", "customized_insights": "自訂化洞察", - "backlog_work_items": "待辦工作項目", + "backlog_work_items": "待辦的{entity}", "active_projects": "啟用中的專案", "trend_on_charts": "圖表趨勢", "all_projects": "所有專案", "summary_of_projects": "專案摘要", "project_insights": "專案洞察", - "started_work_items": "已開始的工作項目", - "total_work_items": "工作項目總數", + "started_work_items": "已開始的{entity}", + "total_work_items": "{entity}總數", "total_projects": "專案總數", "total_admins": "管理員總數", "total_users": "使用者總數", "total_intake": "總收入", - "un_started_work_items": "未開始的工作項目", + "un_started_work_items": "未開始的{entity}", "total_guests": "訪客總數", - "completed_work_items": "已完成的工作項目" + "completed_work_items": "已完成的{entity}", + "total": "{entity}總數" }, "workspace_projects": { "label": "{count, plural, one {專案} other {專案}}", diff --git a/packages/services/src/analytics/analytics.service.ts b/packages/services/src/analytics/analytics.service.ts deleted file mode 100644 index c012fd26f47..00000000000 --- a/packages/services/src/analytics/analytics.service.ts +++ /dev/null @@ -1,93 +0,0 @@ -// constants -import { API_BASE_URL } from "@plane/constants"; -// types -import { - IAnalyticsParams, - IAnalyticsResponse, - IDefaultAnalyticsResponse, - IExportAnalyticsFormData, - ISaveAnalyticsFormData, -} from "@plane/types"; -// services -import { APIService } from "../api.service"; - -export class AnalyticsService extends APIService { - constructor(BASE_URL?: string) { - super(BASE_URL || API_BASE_URL); - } - - /** - * Retrieves analytics data for a specific workspace - * @param {string} workspaceSlug - The unique identifier for the workspace - * @param {IAnalyticsParams} params - Parameters for filtering analytics data - * @param {string|number} [params.project] - Optional project identifier that will be converted to string - * @returns {Promise} The analytics data for the workspace - * @throws {Error} Throws response data if the request fails - */ - async getAnalytics(workspaceSlug: string, params: IAnalyticsParams): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/analytics/`, { - params: { - ...params, - project: params?.project ? params.project.toString() : null, - }, - }) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - - /** - * Retrieves default analytics data for a workspace - * @param {string} workspaceSlug - The unique identifier for the workspace - * @param {Partial} [params] - Optional parameters for filtering default analytics - * @param {string|number} [params.project] - Optional project identifier that will be converted to string - * @returns {Promise} The default analytics data - * @throws {Error} Throws response data if the request fails - */ - async getDefaultAnalytics( - workspaceSlug: string, - params?: Partial - ): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/default-analytics/`, { - params: { - ...params, - project: params?.project ? params.project.toString() : null, - }, - }) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - - /** - * Saves analytics view configuration for a workspace - * @param {string} workspaceSlug - The unique identifier for the workspace - * @param {ISaveAnalyticsFormData} data - The analytics configuration data to save - * @returns {Promise} The response from saving the analytics view - * @throws {Error} Throws response data if the request fails - */ - async save(workspaceSlug: string, data: ISaveAnalyticsFormData): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/analytic-view/`, data) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - - /** - * Exports analytics data for a workspace - * @param {string} workspaceSlug - The unique identifier for the workspace - * @param {IExportAnalyticsFormData} data - Configuration for the analytics export - * @returns {Promise} The exported analytics data - * @throws {Error} Throws response data if the request fails - */ - async export(workspaceSlug: string, data: IExportAnalyticsFormData): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/export-analytics/`, data) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } -} diff --git a/packages/services/src/analytics/index.ts b/packages/services/src/analytics/index.ts deleted file mode 100644 index 7655bd44242..00000000000 --- a/packages/services/src/analytics/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./analytics.service"; diff --git a/packages/services/src/index.ts b/packages/services/src/index.ts index 3c49084628a..b1f966840dc 100644 --- a/packages/services/src/index.ts +++ b/packages/services/src/index.ts @@ -1,5 +1,4 @@ export * from "./ai"; -export * from "./analytics"; export * from "./developer"; export * from "./auth"; export * from "./cycle"; diff --git a/packages/types/src/analytics-v2.d.ts b/packages/types/src/analytics-v2.d.ts deleted file mode 100644 index 1a8652b70a3..00000000000 --- a/packages/types/src/analytics-v2.d.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ChartXAxisProperty, ChartYAxisMetric } from "@plane/constants"; -import { TChartData } from "./charts"; - -export type TAnalyticsTabsV2Base = "overview" | "work-items"; -export type TAnalyticsGraphsV2Base = "projects" | "work-items" | "custom-work-items"; - -// service types - -export interface IAnalyticsResponseV2 { - [key: string]: any; -} - -export interface IAnalyticsResponseFieldsV2 { - count: number; - filter_count: number; -} - -export interface IAnalyticsRadarEntityV2 { - key: string; - name: string; - count: number; -} - -// chart types - -export interface IChartResponseV2 { - schema: Record; - data: TChartData[]; -} - -// table types - -export interface WorkItemInsightColumns { - project_id?: string; - project__name?: string; - cancelled_work_items: number; - completed_work_items: number; - backlog_work_items: number; - un_started_work_items: number; - started_work_items: number; - // because of the peek view, we will display the name of the project instead of project__name - display_name?: string; - avatar_url?: string; - assignee_id?: string; -} - -export type AnalyticsTableDataMap = { - "work-items": WorkItemInsightColumns; -}; - -export interface IAnalyticsV2Params { - x_axis: ChartXAxisProperty; - y_axis: ChartYAxisMetric; - group_by?: ChartXAxisProperty; -} diff --git a/packages/types/src/analytics.d.ts b/packages/types/src/analytics.d.ts index ec417e73fe3..d55d74f4f5d 100644 --- a/packages/types/src/analytics.d.ts +++ b/packages/types/src/analytics.d.ts @@ -1,116 +1,60 @@ -export interface IAnalyticsResponse { - total: number; - distribution: IAnalyticsData; - extras: { - assignee_details: IAnalyticsAssigneeDetails[]; - cycle_details: IAnalyticsCycleDetails[]; - label_details: IAnalyticsLabelDetails[]; - module_details: IAnalyticsModuleDetails[]; - state_details: IAnalyticsStateDetails[]; - }; -} +import { ChartXAxisProperty, ChartYAxisMetric } from "@plane/constants"; +import { TChartData } from "./charts"; -export interface IAnalyticsData { - [key: string]: { - dimension: string | null; - segment?: string; - count?: number; - estimate?: number | null; - }[]; -} +export type TAnalyticsTabsBase = "overview" | "work-items"; +export type TAnalyticsGraphsBase = "projects" | "work-items" | "custom-work-items"; +export type TAnalyticsFilterParams = { + project_ids?: string; + cycle_id?: string; + module_id?: string; +}; -export interface IAnalyticsAssigneeDetails { - assignees__avatar_url: string | null; - assignees__display_name: string | null; - assignees__first_name: string; - assignees__id: string | null; - assignees__last_name: string; -} +// service types -export interface IAnalyticsCycleDetails { - issue_cycle__cycle__name: string | null; - issue_cycle__cycle_id: string | null; -} - -export interface IAnalyticsLabelDetails { - labels__color: string | null; - labels__id: string | null; - labels__name: string | null; +export interface IAnalyticsResponse { + [key: string]: any; } -export interface IAnalyticsModuleDetails { - issue_module__module__name: string | null; - issue_module__module_id: string | null; +export interface IAnalyticsResponseFields { + count: number; + filter_count: number; } -export interface IAnalyticsStateDetails { - state__color: string; - state__name: string; - state_id: string; +export interface IAnalyticsRadarEntity { + key: string; + name: string; + count: number; } -export type TXAxisValues = - | "state_id" - | "state__group" - | "labels__id" - | "assignees__id" - | "estimate_point__value" - | "issue_cycle__cycle_id" - | "issue_module__module_id" - | "priority" - | "start_date" - | "target_date" - | "created_at" - | "completed_at"; - -export type TYAxisValues = "issue_count" | "estimate"; +// chart types -export interface IAnalyticsParams { - x_axis: TXAxisValues; - y_axis: TYAxisValues; - segment?: TXAxisValues | null; - project?: string[] | null; - cycle?: string | null; - module?: string | null; +export interface IChartResponse { + schema: Record; + data: TChartData[]; } -export interface ISaveAnalyticsFormData { - name: string; - description: string; - query_dict: IExportAnalyticsFormData; -} -export interface IExportAnalyticsFormData { - x_axis: TXAxisValues; - y_axis: TYAxisValues; - segment?: TXAxisValues | null; - project?: string[]; -} +// table types -export interface IDefaultAnalyticsUser { - assignees__avatar_url: string | null; - assignees__first_name: string; - assignees__last_name: string; - assignees__display_name: string; - assignees__id: string; - count: number; +export interface WorkItemInsightColumns { + project_id?: string; + project__name?: string; + cancelled_work_items: number; + completed_work_items: number; + backlog_work_items: number; + un_started_work_items: number; + started_work_items: number; + // because of the peek view, we will display the name of the project instead of project__name + display_name?: string; + avatar_url?: string; + assignee_id?: string; } -export interface IDefaultAnalyticsResponse { - issue_completed_month_wise: { month: number; count: number }[]; - most_issue_closed_user: IDefaultAnalyticsUser[]; - most_issue_created_user: { - created_by__avatar_url: string | null; - created_by__first_name: string; - created_by__last_name: string; - created_by__display_name: string; - created_by__id: string; - count: number; - }[]; - open_estimate_sum: number; - open_issues: number; - open_issues_classified: { state_group: string; state_count: number }[]; - pending_issue_user: IDefaultAnalyticsUser[]; - total_estimate_sum: number; - total_issues: number; - total_issues_classified: { state_group: string; state_count: number }[]; +export type AnalyticsTableDataMap = { + "work-items": WorkItemInsightColumns; +}; + +export interface IAnalyticsParams { + x_axis: ChartXAxisProperty; + y_axis: ChartYAxisMetric; + group_by?: ChartXAxisProperty; } diff --git a/packages/types/src/index.d.ts b/packages/types/src/index.d.ts index 0ac656fc69f..9a6711421f1 100644 --- a/packages/types/src/index.d.ts +++ b/packages/types/src/index.d.ts @@ -43,4 +43,4 @@ export * from "./home"; export * from "./stickies"; export * from "./utils"; export * from "./payment"; -export * from "./analytics-v2"; \ No newline at end of file +export * from "./analytics"; diff --git a/web/app/(all)/[workspaceSlug]/(projects)/analytics/page.tsx b/web/app/(all)/[workspaceSlug]/(projects)/analytics/page.tsx index b4972363355..389a80a9018 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/analytics/page.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/analytics/page.tsx @@ -8,13 +8,13 @@ import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { Tabs } from "@plane/ui"; // components -import AnalyticsFilterActions from "@/components/analytics-v2/analytics-filter-actions"; +import AnalyticsFilterActions from "@/components/analytics/analytics-filter-actions"; import { PageHead } from "@/components/core"; import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state"; // hooks import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; -import { ANALYTICS_TABS } from "@/plane-web/components/analytics-v2/tabs"; +import { ANALYTICS_TABS } from "@/plane-web/components/analytics/tabs"; const AnalyticsPage = observer(() => { const router = useRouter(); diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx index 07dd0fc4dfa..7d15cd22da5 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx @@ -27,7 +27,7 @@ import { // ui import { Breadcrumbs, Button, ContrastIcon, Tooltip, Header, CustomSearchSelect } from "@plane/ui"; // components -import { WorkItemsModal } from "@/components/analytics-v2/work-items/modal"; +import { WorkItemsModal } from "@/components/analytics/work-items/modal"; import { BreadcrumbLink, SwitcherLabel } from "@/components/common"; import { CycleQuickActions } from "@/components/cycles"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx index 4efa986b4d9..0494f5f33a4 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx @@ -19,7 +19,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption // ui import { CustomMenu } from "@plane/ui"; // components -import { WorkItemsModal } from "@/components/analytics-v2/work-items/modal"; +import { WorkItemsModal } from "@/components/analytics/work-items/modal"; import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues"; // helpers import { isIssueFilterActive } from "@/helpers/filter.helper"; diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx index a9777ce23d5..def0504628a 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx @@ -20,7 +20,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption // ui import { CustomMenu } from "@plane/ui"; // components -import { WorkItemsModal } from "@/components/analytics-v2/work-items/modal"; +import { WorkItemsModal } from "@/components/analytics/work-items/modal"; import { DisplayFiltersSelection, FilterSelection, diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx index 6830b5532d9..0fb2b138f49 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx @@ -25,7 +25,7 @@ import { // ui import { Breadcrumbs, Button, DiceIcon, Tooltip, Header, CustomSearchSelect } from "@plane/ui"; // components -import { WorkItemsModal } from "@/components/analytics-v2/work-items/modal"; +import { WorkItemsModal } from "@/components/analytics/work-items/modal"; import { BreadcrumbLink, SwitcherLabel } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; // helpers diff --git a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx index 741fe3b5391..0501f5527dd 100644 --- a/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx +++ b/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx @@ -20,8 +20,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption // ui import { CustomMenu } from "@plane/ui"; // components -import { ProjectAnalyticsModal } from "@/components/analytics"; -import { WorkItemsModal } from "@/components/analytics-v2/work-items/modal"; +import { WorkItemsModal } from "@/components/analytics/work-items/modal"; import { DisplayFiltersSelection, FilterSelection, diff --git a/web/ce/components/analytics-v2/tabs.ts b/web/ce/components/analytics-v2/tabs.ts deleted file mode 100644 index 8390601eba3..00000000000 --- a/web/ce/components/analytics-v2/tabs.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TAnalyticsTabsV2Base } from "@plane/types"; -import { Overview } from "@/components/analytics-v2/overview"; -import { WorkItems } from "@/components/analytics-v2/work-items"; -export const ANALYTICS_TABS: { - key: TAnalyticsTabsV2Base; - i18nKey: string; - content: React.FC; -}[] = [ - { key: "overview", i18nKey: "common.overview", content: Overview }, - { key: "work-items", i18nKey: "sidebar.work_items", content: WorkItems }, - ]; diff --git a/web/ce/components/analytics/tabs.ts b/web/ce/components/analytics/tabs.ts new file mode 100644 index 00000000000..6ce6daf8e7f --- /dev/null +++ b/web/ce/components/analytics/tabs.ts @@ -0,0 +1,11 @@ +import { TAnalyticsTabsBase } from "@plane/types"; +import { Overview } from "@/components/analytics/overview"; +import { WorkItems } from "@/components/analytics/work-items"; +export const ANALYTICS_TABS: { + key: TAnalyticsTabsBase; + i18nKey: string; + content: React.FC; +}[] = [ + { key: "overview", i18nKey: "common.overview", content: Overview }, + { key: "work-items", i18nKey: "sidebar.work_items", content: WorkItems }, +]; diff --git a/web/ce/components/cycles/analytics-sidebar/base.tsx b/web/ce/components/cycles/analytics-sidebar/base.tsx index 40f098a2d03..881ab6ab315 100644 --- a/web/ce/components/cycles/analytics-sidebar/base.tsx +++ b/web/ce/components/cycles/analytics-sidebar/base.tsx @@ -65,22 +65,10 @@ export const SidebarChart: FC = observer((props) => {
-
-
- - {t("ideal")} -
-
- - {t("current")} -
-
{cycleStartDate && cycleEndDate && completionChartDistributionData ? ( diff --git a/web/ce/store/analytics.store.ts b/web/ce/store/analytics.store.ts new file mode 100644 index 00000000000..ef866f65a78 --- /dev/null +++ b/web/ce/store/analytics.store.ts @@ -0,0 +1,8 @@ +import { BaseAnalyticsStore, IBaseAnalyticsStore } from "@/store/analytics.store"; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface IAnalyticsStore extends IBaseAnalyticsStore { + //observables +} + +export class AnalyticsStore extends BaseAnalyticsStore {} diff --git a/web/core/components/analytics-v2/index.ts b/web/core/components/analytics-v2/index.ts deleted file mode 100644 index 8ac82df5dfb..00000000000 --- a/web/core/components/analytics-v2/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./overview/root"; diff --git a/web/core/components/analytics-v2/insight-table/root.tsx b/web/core/components/analytics-v2/insight-table/root.tsx deleted file mode 100644 index 1ee90c726aa..00000000000 --- a/web/core/components/analytics-v2/insight-table/root.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { ColumnDef, Row, Table } from "@tanstack/react-table"; -import { download, generateCsv, mkConfig } from "export-to-csv"; -import { useParams } from "next/navigation"; -import { Download } from "lucide-react"; -import { useTranslation } from "@plane/i18n"; -import { AnalyticsTableDataMap, TAnalyticsTabsV2Base } from "@plane/types"; -import { Button } from "@plane/ui"; -import { DataTable } from "./data-table"; -import { TableLoader } from "./loader"; -interface InsightTableProps> { - analyticsType: T; - data?: AnalyticsTableDataMap[T][]; - isLoading?: boolean; - columns: ColumnDef[]; - columnsLabels?: Record; - headerText: string; -} - -export const InsightTable = >( - props: InsightTableProps -): React.ReactElement => { - const { data, isLoading, columns, columnsLabels, headerText } = props; - const params = useParams(); - const { t } = useTranslation(); - const workspaceSlug = params.workspaceSlug.toString(); - if (isLoading) { - return ; - } - - const csvConfig = mkConfig({ - fieldSeparator: ",", - filename: `${workspaceSlug}-analytics`, - decimalSeparator: ".", - useKeysAsHeaders: true, - }); - - const exportCSV = (rows: Row[]) => { - const rowData: any = rows.map((row) => { - const { project_id, avatar_url, assignee_id, ...exportableData } = row.original; - return Object.fromEntries( - Object.entries(exportableData).map(([key, value]) => { - if (columnsLabels?.[key]) { - return [columnsLabels[key], value]; - } - return [key, value]; - }) - ); - }); - const csv = generateCsv(csvConfig)(rowData); - download(csvConfig)(csv); - }; - - return ( -
- {data ? ( - ) => ( - - )} - /> - ) : ( -
No data
- )} -
- ); -}; diff --git a/web/core/components/analytics-v2/total-insights.tsx b/web/core/components/analytics-v2/total-insights.tsx deleted file mode 100644 index 5fc8b2239eb..00000000000 --- a/web/core/components/analytics-v2/total-insights.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// plane package imports -import { observer } from "mobx-react-lite"; -import { useParams } from "next/navigation"; -import useSWR from "swr"; -import { insightsFields } from "@plane/constants"; -import { useTranslation } from "@plane/i18n"; -import { IAnalyticsResponseV2, TAnalyticsTabsV2Base } from "@plane/types"; -//hooks -import { cn } from "@/helpers/common.helper"; -import { useAnalyticsV2 } from "@/hooks/store/use-analytics-v2"; -//services -import { AnalyticsV2Service } from "@/services/analytics-v2.service"; -// plane web components -import InsightCard from "./insight-card"; - -const analyticsV2Service = new AnalyticsV2Service(); - -const TotalInsights: React.FC<{ analyticsType: TAnalyticsTabsV2Base; peekView?: boolean }> = observer( - ({ analyticsType, peekView }) => { - const params = useParams(); - const workspaceSlug = params.workspaceSlug.toString(); - const { t } = useTranslation(); - const { selectedDuration, selectedProjects, selectedDurationLabel, selectedCycle, selectedModule, isPeekView } = - useAnalyticsV2(); - - const { data: totalInsightsData, isLoading } = useSWR( - `total-insights-${analyticsType}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`, - () => - analyticsV2Service.getAdvanceAnalytics( - workspaceSlug, - analyticsType, - { - // date_filter: selectedDuration, - ...(selectedProjects?.length > 0 ? { project_ids: selectedProjects.join(",") } : {}), - ...(selectedCycle ? { cycle_id: selectedCycle } : {}), - ...(selectedModule ? { module_id: selectedModule } : {}), - }, - isPeekView - ) - ); - return ( -
- {insightsFields[analyticsType]?.map((item: string) => ( - - ))} -
- ); - } -); - -export default TotalInsights; diff --git a/web/core/components/analytics-v2/analytics-filter-actions.tsx b/web/core/components/analytics/analytics-filter-actions.tsx similarity index 88% rename from web/core/components/analytics-v2/analytics-filter-actions.tsx rename to web/core/components/analytics/analytics-filter-actions.tsx index aae166bf4f4..adfe7822b7e 100644 --- a/web/core/components/analytics-v2/analytics-filter-actions.tsx +++ b/web/core/components/analytics/analytics-filter-actions.tsx @@ -2,13 +2,13 @@ import { observer } from "mobx-react-lite"; // hooks import { useProject } from "@/hooks/store"; -import { useAnalyticsV2 } from "@/hooks/store/use-analytics-v2"; +import { useAnalytics } from "@/hooks/store/use-analytics"; // components import DurationDropdown from "./select/duration"; import { ProjectSelect } from "./select/project"; const AnalyticsFilterActions = observer(() => { - const { selectedProjects, selectedDuration, updateSelectedProjects, updateSelectedDuration } = useAnalyticsV2(); + const { selectedProjects, selectedDuration, updateSelectedProjects, updateSelectedDuration } = useAnalytics(); const { workspaceProjectIds } = useProject(); return (
diff --git a/web/core/components/analytics-v2/analytics-section-wrapper.tsx b/web/core/components/analytics/analytics-section-wrapper.tsx similarity index 100% rename from web/core/components/analytics-v2/analytics-section-wrapper.tsx rename to web/core/components/analytics/analytics-section-wrapper.tsx diff --git a/web/core/components/analytics-v2/analytics-wrapper.tsx b/web/core/components/analytics/analytics-wrapper.tsx similarity index 100% rename from web/core/components/analytics-v2/analytics-wrapper.tsx rename to web/core/components/analytics/analytics-wrapper.tsx diff --git a/web/core/components/analytics/config.ts b/web/core/components/analytics/config.ts new file mode 100644 index 00000000000..5e297e908bf --- /dev/null +++ b/web/core/components/analytics/config.ts @@ -0,0 +1,9 @@ +import { mkConfig } from "export-to-csv"; + +export const csvConfig = (workspaceSlug: string) => + mkConfig({ + fieldSeparator: ",", + filename: `${workspaceSlug}-analytics`, + decimalSeparator: ".", + useKeysAsHeaders: true, + }); diff --git a/web/core/components/analytics/custom-analytics/custom-analytics.tsx b/web/core/components/analytics/custom-analytics/custom-analytics.tsx deleted file mode 100644 index bb801616fcb..00000000000 --- a/web/core/components/analytics/custom-analytics/custom-analytics.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -import { useForm } from "react-hook-form"; -import useSWR from "swr"; -import { IAnalyticsParams } from "@plane/types"; -// services -// components -import { CustomAnalyticsSelectBar, CustomAnalyticsMainContent, CustomAnalyticsSidebar } from "@/components/analytics"; -// types -// fetch-keys -import { ANALYTICS } from "@/constants/fetch-keys"; -import { cn } from "@/helpers/common.helper"; -import { useAppTheme } from "@/hooks/store"; -import { hideFloatingBot, showFloatingBot } from "@/plane-web/helpers/pi-chat.helper"; -import { AnalyticsService } from "@/services/analytics.service"; - -type Props = { - additionalParams?: Partial; - fullScreen: boolean; -}; - -const defaultValues: IAnalyticsParams = { - x_axis: "priority", - y_axis: "issue_count", - segment: null, - project: null, -}; - -const analyticsService = new AnalyticsService(); - -export const CustomAnalytics: React.FC = observer((props) => { - const { additionalParams, fullScreen } = props; - - const { workspaceSlug, projectId } = useParams(); - - const { control, watch, setValue } = useForm({ defaultValues }); - - const params: IAnalyticsParams = { - x_axis: watch("x_axis"), - y_axis: watch("y_axis"), - segment: watch("segment"), - project: projectId ? [projectId.toString()] : watch("project"), - ...additionalParams, - }; - - const { data: analytics, error: analyticsError } = useSWR( - workspaceSlug ? ANALYTICS(workspaceSlug.toString(), params) : null, - workspaceSlug ? () => analyticsService.getAnalytics(workspaceSlug.toString(), params) : null - ); - - const { workspaceAnalyticsSidebarCollapsed } = useAppTheme(); - - const isProjectLevel = projectId ? true : false; - - useEffect(() => { - hideFloatingBot(); - return () => { - showFloatingBot(); - }; - }, []); - - return ( -
-
- - -
- -
- -
-
- ); -}); diff --git a/web/core/components/analytics/custom-analytics/graph/custom-tooltip.tsx b/web/core/components/analytics/custom-analytics/graph/custom-tooltip.tsx deleted file mode 100644 index 853c541e717..00000000000 --- a/web/core/components/analytics/custom-analytics/graph/custom-tooltip.tsx +++ /dev/null @@ -1,73 +0,0 @@ -// nivo -import { BarTooltipProps } from "@nivo/bar"; -// plane imports -import { ANALYTICS_DATE_KEYS } from "@plane/constants"; -import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types"; -// helpers -import { renderMonthAndYear } from "@/helpers/analytics.helper"; - -type Props = { - datum: BarTooltipProps; - analytics: IAnalyticsResponse; - params: IAnalyticsParams; -}; - -export const CustomTooltip: React.FC = ({ datum, analytics, params }) => { - let tooltipValue: string | number = ""; - - const renderAssigneeName = (assigneeId: string): string => { - const assignee = analytics.extras.assignee_details.find((a) => a.assignees__id === assigneeId); - - if (!assignee) return "No assignee"; - - return assignee.assignees__display_name || "No assignee"; - }; - - if (params.segment) { - if (ANALYTICS_DATE_KEYS.includes(params.segment)) tooltipValue = renderMonthAndYear(datum.id); - else if (params.segment === "labels__id") { - const label = analytics.extras.label_details.find((l) => l.labels__id === datum.id); - tooltipValue = label && label.labels__name ? label.labels__name : "None"; - } else if (params.segment === "state_id") { - const state = analytics.extras.state_details.find((s) => s.state_id === datum.id); - tooltipValue = state && state.state__name ? state.state__name : "None"; - } else if (params.segment === "issue_cycle__cycle_id") { - const cycle = analytics.extras.cycle_details.find((c) => c.issue_cycle__cycle_id === datum.id); - tooltipValue = cycle && cycle.issue_cycle__cycle__name ? cycle.issue_cycle__cycle__name : "None"; - } else if (params.segment === "issue_module__module_id") { - const selectedModule = analytics.extras.module_details.find((m) => m.issue_module__module_id === datum.id); - tooltipValue = - selectedModule && selectedModule.issue_module__module__name - ? selectedModule.issue_module__module__name - : "None"; - } else tooltipValue = datum.id; - } else { - if (ANALYTICS_DATE_KEYS.includes(params.x_axis)) tooltipValue = datum.indexValue; - else tooltipValue = datum.id === "count" ? "Work item count" : "Estimate"; - } - - return ( -
- - - {params.segment === "assignees__id" ? renderAssigneeName(tooltipValue.toString()) : tooltipValue}: - - {datum.value} -
- ); -}; diff --git a/web/core/components/analytics/custom-analytics/graph/index.tsx b/web/core/components/analytics/custom-analytics/graph/index.tsx deleted file mode 100644 index 87627bba373..00000000000 --- a/web/core/components/analytics/custom-analytics/graph/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -"use client"; - -// nivo -import { BarDatum } from "@nivo/bar"; -// components -import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types"; -import { Tooltip } from "@plane/ui"; -// ui -import { BarGraph } from "@/components/ui"; -// helpers -import { generateBarColor, generateDisplayName, renderChartDynamicLabel } from "@/helpers/analytics.helper"; -import { findStringWithMostCharacters } from "@/helpers/array.helper"; -import { getFileURL } from "@/helpers/file.helper"; -// types -import { CustomTooltip } from "./custom-tooltip"; - -type Props = { - analytics: IAnalyticsResponse; - barGraphData: { - data: BarDatum[]; - xAxisKeys: string[]; - }; - params: IAnalyticsParams; - yAxisKey: "count" | "estimate"; - fullScreen: boolean; -}; - -export const AnalyticsGraph: React.FC = ({ analytics, barGraphData, params, yAxisKey, fullScreen }) => { - const generateYAxisTickValues = () => { - if (!analytics) return []; - - let data: number[] = []; - - if (params.segment) - // find the total no of work items in each segment - data = Object.keys(analytics.distribution).map((segment) => { - let totalSegmentIssues = 0; - - analytics.distribution[segment].map((s) => { - totalSegmentIssues += s[yAxisKey] as number; - }); - - return totalSegmentIssues; - }); - else data = barGraphData.data.map((d) => d[yAxisKey] as number); - - return data; - }; - - const longestXAxisLabel = findStringWithMostCharacters(barGraphData.data.map((d) => `${d.name}`)); - - return ( - - generateBarColor( - params.segment ? `${datum.id}` : `${datum.indexValue}`, - analytics, - params, - params.segment ? "segment" : "x_axis" - ) - } - customYAxisTickValues={generateYAxisTickValues()} - tooltip={(datum) => } - height={fullScreen ? "400px" : "300px"} - margin={{ - right: 20, - bottom: params.x_axis === "assignees__id" ? 50 : renderChartDynamicLabel(longestXAxisLabel)?.length * 5 + 20, - }} - axisBottom={{ - tickSize: 0, - tickPadding: 10, - tickRotation: barGraphData.data.length > 7 ? -45 : 0, - renderTick: - params.x_axis === "assignees__id" - ? (datum) => { - const assignee = analytics.extras.assignee_details?.find((a) => a?.assignees__id === datum?.value); - - if (assignee?.assignees__avatar_url && assignee?.assignees__avatar_url !== "") - return ( - - - - - - ); - else - return ( - - - - - {params.x_axis === "assignees__id" - ? datum.value && datum.value !== "None" - ? generateDisplayName(datum.value, analytics, params, "x_axis")[0].toUpperCase() - : "?" - : datum.value && datum.value !== "None" - ? `${datum.value}`.toUpperCase()[0] - : "?"} - - - - ); - } - : (datum) => ( - - - 7 ? "end" : "middle"}`} - fontSize={10} - fill="rgb(var(--color-text-200))" - className={`${barGraphData.data.length > 7 ? "-rotate-45" : ""}`} - > - {renderChartDynamicLabel(generateDisplayName(datum.value, analytics, params, "x_axis"))?.label} - - - - ), - }} - theme={{ - axis: {}, - }} - /> - ); -}; diff --git a/web/core/components/analytics/custom-analytics/index.ts b/web/core/components/analytics/custom-analytics/index.ts deleted file mode 100644 index 9cb0ddd5b07..00000000000 --- a/web/core/components/analytics/custom-analytics/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from "./graph"; -export * from "./select"; -export * from "./custom-analytics"; -export * from "./main-content"; -export * from "./select-bar"; -export * from "./sidebar"; -export * from "./table"; diff --git a/web/core/components/analytics/custom-analytics/main-content.tsx b/web/core/components/analytics/custom-analytics/main-content.tsx deleted file mode 100644 index b990c53bc76..00000000000 --- a/web/core/components/analytics/custom-analytics/main-content.tsx +++ /dev/null @@ -1,85 +0,0 @@ -"use client"; - -import { useParams } from "next/navigation"; -import { mutate } from "swr"; -// types -import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types"; -// ui -import { Button, Loader } from "@plane/ui"; -// components -import { AnalyticsGraph, AnalyticsTable } from "@/components/analytics"; -// fetch-keys -import { ANALYTICS } from "@/constants/fetch-keys"; -// helpers -import { convertResponseToBarGraphData } from "@/helpers/analytics.helper"; - -type Props = { - analytics: IAnalyticsResponse | undefined; - error: any; - fullScreen: boolean; - params: IAnalyticsParams; -}; - -export const CustomAnalyticsMainContent: React.FC = (props) => { - const { analytics, error, fullScreen, params } = props; - - const { workspaceSlug } = useParams(); - - const yAxisKey = params.y_axis === "issue_count" ? "count" : "estimate"; - const barGraphData = convertResponseToBarGraphData(analytics?.distribution, params); - - return ( - <> - {!error ? ( - analytics ? ( - analytics.total > 0 ? ( -
- - -
- ) : ( -
-
-

No matching work items found. Try changing the parameters.

-
-
- ) - ) : ( - - - - - - - - - - ) - ) : ( -
-
-

There was some error in fetching the data.

-
- -
-
-
- )} - - ); -}; diff --git a/web/core/components/analytics/custom-analytics/select-bar.tsx b/web/core/components/analytics/custom-analytics/select-bar.tsx deleted file mode 100644 index 27cfad236c1..00000000000 --- a/web/core/components/analytics/custom-analytics/select-bar.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { observer } from "mobx-react"; -import { Control, Controller, UseFormSetValue } from "react-hook-form"; -// plane imports -import { ANALYTICS_X_AXIS_VALUES } from "@plane/constants"; -import { IAnalyticsParams } from "@plane/types"; -import { Row } from "@plane/ui"; -// components -import { SelectProject, SelectSegment, SelectXAxis, SelectYAxis } from "@/components/analytics"; -// hooks -import { useProject } from "@/hooks/store"; - -type Props = { - control: Control; - setValue: UseFormSetValue; - params: IAnalyticsParams; - fullScreen: boolean; - isProjectLevel: boolean; -}; - -export const CustomAnalyticsSelectBar: React.FC = observer((props) => { - const { control, setValue, params, fullScreen, isProjectLevel } = props; - - const { workspaceProjectIds: workspaceProjectIds, currentProjectDetails } = useProject(); - - const analyticsOptions = isProjectLevel - ? ANALYTICS_X_AXIS_VALUES.filter((v) => { - if (v.value === "issue_cycle__cycle_id" && !currentProjectDetails?.cycle_view) return false; - if (v.value === "issue_module__module_id" && !currentProjectDetails?.module_view) return false; - return true; - }) - : ANALYTICS_X_AXIS_VALUES; - - return ( - - {!isProjectLevel && ( -
-
Project
- ( - - )} - /> -
- )} -
-
Measure (y-axis)
- } - /> -
-
-
Dimension (x-axis)
- ( - { - if (params.segment === val) setValue("segment", null); - - onChange(val); - }} - params={params} - analyticsOptions={analyticsOptions} - /> - )} - /> -
-
-
Group
- ( - - )} - /> -
-
- ); -}); diff --git a/web/core/components/analytics/custom-analytics/select/index.ts b/web/core/components/analytics/custom-analytics/select/index.ts deleted file mode 100644 index 0c89bd2ad42..00000000000 --- a/web/core/components/analytics/custom-analytics/select/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./project"; -export * from "./segment"; -export * from "./x-axis"; -export * from "./y-axis"; diff --git a/web/core/components/analytics/custom-analytics/select/project.tsx b/web/core/components/analytics/custom-analytics/select/project.tsx deleted file mode 100644 index 4038da4e1de..00000000000 --- a/web/core/components/analytics/custom-analytics/select/project.tsx +++ /dev/null @@ -1,52 +0,0 @@ -"use client"; - -import { observer } from "mobx-react"; -// hooks -import { CustomSearchSelect } from "@plane/ui"; -import { useProject } from "@/hooks/store"; -// ui - -type Props = { - value: string[] | undefined; - onChange: (val: string[] | null) => void; - projectIds: string[] | undefined; -}; - -export const SelectProject: React.FC = observer((props) => { - const { value, onChange, projectIds } = props; - const { getProjectById } = useProject(); - - const options = projectIds?.map((projectId) => { - const projectDetails = getProjectById(projectId); - - return { - value: projectDetails?.id, - query: `${projectDetails?.name} ${projectDetails?.identifier}`, - content: ( -
- {projectDetails?.identifier} - {projectDetails?.name} -
- ), - }; - }); - - return ( - onChange(val)} - options={options} - label={ -
- {value && value.length > 0 - ? projectIds - ?.filter((p) => value.includes(p)) - .map((p) => getProjectById(p)?.name) - .join(", ") - : "All projects"} -
- } - multiple - /> - ); -}); diff --git a/web/core/components/analytics/custom-analytics/select/segment.tsx b/web/core/components/analytics/custom-analytics/select/segment.tsx deleted file mode 100644 index ecaf0dc75e5..00000000000 --- a/web/core/components/analytics/custom-analytics/select/segment.tsx +++ /dev/null @@ -1,45 +0,0 @@ -"use client"; - -import { useParams } from "next/navigation"; -import { IAnalyticsParams, TXAxisValues } from "@plane/types"; -// ui -import { CustomSelect } from "@plane/ui"; - -type Props = { - value: TXAxisValues | null | undefined; - onChange: () => void; - params: IAnalyticsParams; - analyticsOptions: { value: TXAxisValues; label: string }[]; -}; - -export const SelectSegment: React.FC = ({ value, onChange, params, analyticsOptions }) => { - const { cycleId, moduleId } = useParams(); - - return ( - - {analyticsOptions.find((v) => v.value === value)?.label ?? ( - No value - )} - - } - onChange={onChange} - maxHeight="lg" - > - No value - {analyticsOptions.map((item) => { - if (params.x_axis === item.value) return null; - if (cycleId && item.value === "issue_cycle__cycle_id") return null; - if (moduleId && item.value === "issue_module__module_id") return null; - - return ( - - {item.label} - - ); - })} - - ); -}; diff --git a/web/core/components/analytics/custom-analytics/select/x-axis.tsx b/web/core/components/analytics/custom-analytics/select/x-axis.tsx deleted file mode 100644 index 032d4381fa7..00000000000 --- a/web/core/components/analytics/custom-analytics/select/x-axis.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import { useParams } from "next/navigation"; -import { IAnalyticsParams, TXAxisValues } from "@plane/types"; -// ui -import { CustomSelect } from "@plane/ui"; - -type Props = { - value: TXAxisValues; - onChange: (val: string) => void; - params: IAnalyticsParams; - analyticsOptions: { value: TXAxisValues; label: string }[]; -}; - -export const SelectXAxis: React.FC = (props) => { - const { value, onChange, params, analyticsOptions } = props; - - const { cycleId, moduleId } = useParams(); - - return ( - {analyticsOptions.find((v) => v.value === value)?.label}} - onChange={onChange} - maxHeight="lg" - > - {analyticsOptions.map((item) => { - if (params.segment === item.value) return null; - if (cycleId && item.value === "issue_cycle__cycle_id") return null; - if (moduleId && item.value === "issue_module__module_id") return null; - - return ( - - {item.label} - - ); - })} - - ); -}; diff --git a/web/core/components/analytics/custom-analytics/select/y-axis.tsx b/web/core/components/analytics/custom-analytics/select/y-axis.tsx deleted file mode 100644 index 42c9145e899..00000000000 --- a/web/core/components/analytics/custom-analytics/select/y-axis.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client"; - -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// plane imports -import { ANALYTICS_Y_AXIS_VALUES } from "@plane/constants"; -import { TYAxisValues } from "@plane/types"; -import { CustomSelect } from "@plane/ui"; -// hooks -import { useProjectEstimates } from "@/hooks/store"; -// plane web constants -import { EEstimateSystem } from "@/plane-web/constants/estimates"; - -type Props = { - value: TYAxisValues; - onChange: () => void; -}; - -export const SelectYAxis: React.FC = observer(({ value, onChange }) => { - // hooks - const { projectId } = useParams(); - const { areEstimateEnabledByProjectId, currentActiveEstimateId, estimateById } = useProjectEstimates(); - - const isEstimateEnabled = (analyticsOption: string) => { - if (analyticsOption === "estimate") { - if ( - projectId && - currentActiveEstimateId && - areEstimateEnabledByProjectId(projectId.toString()) && - estimateById(currentActiveEstimateId)?.type === EEstimateSystem.POINTS - ) { - return true; - } else { - return false; - } - } - - return true; - }; - - return ( - {ANALYTICS_Y_AXIS_VALUES.find((v) => v.value === value)?.label ?? "None"}} - onChange={onChange} - maxHeight="lg" - > - {ANALYTICS_Y_AXIS_VALUES.map( - (item) => - isEstimateEnabled(item.value) && ( - - {item.label} - - ) - )} - - ); -}); diff --git a/web/core/components/analytics/custom-analytics/sidebar/index.ts b/web/core/components/analytics/custom-analytics/sidebar/index.ts deleted file mode 100644 index db9adae856f..00000000000 --- a/web/core/components/analytics/custom-analytics/sidebar/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./projects-list"; -export * from "./sidebar-header"; -export * from "./sidebar"; diff --git a/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx b/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx deleted file mode 100644 index c07867378ad..00000000000 --- a/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { observer } from "mobx-react"; -// icons -import { Contrast, LayoutGrid, Users, Loader as Spinner } from "lucide-react"; -// plane imports -import { useTranslation } from "@plane/i18n"; -import { Loader } from "@plane/ui"; -// components -import { Logo } from "@/components/common"; -// helpers -import { truncateText } from "@/helpers/string.helper"; -// hooks -import { useProject } from "@/hooks/store"; - -type Props = { - projectIds: string[]; - isLoading: boolean; - isUpdating: boolean; -}; - -export const CustomAnalyticsSidebarProjectsList: React.FC = observer((props) => { - const { projectIds, isLoading, isUpdating } = props; - // store hooks - const { getProjectById, getProjectAnalyticsCountById } = useProject(); - const { t } = useTranslation(); - - return ( -
-
-

{t("workspace_analytics.selected_projects")}

- {isUpdating && } -
-
- {projectIds.map((projectId) => { - const project = getProjectById(projectId); - const projectAnalyticsCount = getProjectAnalyticsCountById(projectId); - - if (!project) return; - - return ( -
-
-
- -
-
-

{truncateText(project.name, 20)}

- ({project.identifier}) -
-
-
- {isLoading ? ( - - - - - - ) : ( - <> -
-
- -
{t("workspace_analytics.total_members")}
-
- {projectAnalyticsCount?.total_members} -
-
-
- -
{t("workspace_analytics.total_cycles")}
-
- {projectAnalyticsCount?.total_cycles} -
-
-
- -
{t("workspace_analytics.total_modules")}
-
- {projectAnalyticsCount?.total_modules} -
- - )} -
-
- ); - })} -
-
- ); -}); diff --git a/web/core/components/analytics/custom-analytics/sidebar/sidebar-header.tsx b/web/core/components/analytics/custom-analytics/sidebar/sidebar-header.tsx deleted file mode 100644 index 707a149221b..00000000000 --- a/web/core/components/analytics/custom-analytics/sidebar/sidebar-header.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// components -import { NETWORK_CHOICES } from "@plane/constants"; -import { useTranslation } from "@plane/i18n"; -import { Logo } from "@/components/common"; -// constants -// helpers -import { renderFormattedDate } from "@/helpers/date-time.helper"; -// hooks -import { useCycle, useMember, useModule, useProject } from "@/hooks/store"; - -export const CustomAnalyticsSidebarHeader = observer(() => { - const { projectId, cycleId, moduleId } = useParams(); - - const { getProjectById } = useProject(); - const { getCycleById } = useCycle(); - const { getModuleById } = useModule(); - const { getUserDetails } = useMember(); - const { t } = useTranslation(); - - const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined; - const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined; - const projectDetails = projectId ? getProjectById(projectId.toString()) : undefined; - const cycleOwnerDetails = cycleDetails ? getUserDetails(cycleDetails.owned_by_id) : undefined; - const moduleLeadDetails = moduleDetails && moduleDetails.lead_id ? getUserDetails(moduleDetails.lead_id) : undefined; - - return ( - <> - {projectId ? ( - cycleDetails ? ( -
-

Analytics for {cycleDetails.name}

-
-
-
Lead
- {cycleOwnerDetails?.display_name} -
-
-
Start Date
- - {cycleDetails.start_date && cycleDetails.start_date !== "" - ? renderFormattedDate(cycleDetails.start_date) - : "No start date"} - -
-
-
Target Date
- - {cycleDetails.end_date && cycleDetails.end_date !== "" - ? renderFormattedDate(cycleDetails.end_date) - : "No end date"} - -
-
-
- ) : moduleDetails ? ( -
-

Analytics for {moduleDetails.name}

-
-
-
Lead
- {moduleLeadDetails && {moduleLeadDetails?.display_name}} -
-
-
Start Date
- - {moduleDetails.start_date && moduleDetails.start_date !== "" - ? renderFormattedDate(moduleDetails.start_date) - : "No start date"} - -
-
-
Target Date
- - {moduleDetails.target_date && moduleDetails.target_date !== "" - ? renderFormattedDate(moduleDetails.target_date) - : "No end date"} - -
-
-
- ) : ( -
-
- {projectDetails && ( - - - - )} -

{projectDetails?.name}

-
-
-
-
Network
- {t(NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.i18n_label ?? "")} -
-
-
- ) - ) : null} - - ); -}); diff --git a/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx b/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx deleted file mode 100644 index 5df139205a8..00000000000 --- a/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx +++ /dev/null @@ -1,212 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -import useSWR, { mutate } from "swr"; -// icons -import { CalendarDays, Download, RefreshCw } from "lucide-react"; -// types -import { useTranslation } from "@plane/i18n"; -import { IAnalyticsParams, IAnalyticsResponse, IExportAnalyticsFormData, IWorkspace } from "@plane/types"; -// ui -import { Button, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui"; -// components -import { CustomAnalyticsSidebarHeader, CustomAnalyticsSidebarProjectsList } from "@/components/analytics"; -// constants -import { ANALYTICS } from "@/constants/fetch-keys"; -// helpers -import { cn } from "@/helpers/common.helper"; -import { renderFormattedDate } from "@/helpers/date-time.helper"; -// hooks -import { useCycle, useModule, useProject, useWorkspace, useUser } from "@/hooks/store"; -// services -import { AnalyticsService } from "@/services/analytics.service"; - -type Props = { - analytics: IAnalyticsResponse | undefined; - params: IAnalyticsParams; - isProjectLevel: boolean; -}; - -const analyticsService = new AnalyticsService(); - -const PROJECT_ANALYTICS_COUNT_PARAMS = { - fields: "total_members,total_cycles,total_modules", -}; - -export const CustomAnalyticsSidebar: React.FC = observer((props) => { - const { analytics, params, isProjectLevel = false } = props; - // router - const { workspaceSlug, projectId, cycleId, moduleId } = useParams(); - // store hooks - const { data: currentUser } = useUser(); - const { workspaceProjectIds, getProjectById, fetchProjectAnalyticsCount } = useProject(); - const { getWorkspaceById } = useWorkspace(); - const { t } = useTranslation(); - const { fetchCycleDetails, getCycleById } = useCycle(); - const { fetchModuleDetails, getModuleById } = useModule(); - // fetch project analytics count - const { isLoading: isProjectAnalyticsLoading, isValidating: isProjectAnalyticsUpdating } = useSWR( - workspaceSlug ? ["projectAnalyticsCount", workspaceSlug] : null, - workspaceSlug ? () => fetchProjectAnalyticsCount(workspaceSlug.toString(), PROJECT_ANALYTICS_COUNT_PARAMS) : null - ); - - const projectDetails = projectId ? (getProjectById(projectId.toString()) ?? undefined) : undefined; - - const trackExportAnalytics = () => { - if (!currentUser) return; - - const eventPayload: any = { - workspaceSlug: workspaceSlug?.toString(), - params: { - x_axis: params.x_axis, - y_axis: params.y_axis, - group: params.segment, - project: params.project, - }, - }; - - if (projectDetails) { - const workspaceDetails = projectDetails.workspace as IWorkspace; - - eventPayload.workspaceId = workspaceDetails.id; - eventPayload.workspaceName = workspaceDetails.name; - eventPayload.projectId = projectDetails.id; - eventPayload.projectIdentifier = projectDetails.identifier; - eventPayload.projectName = projectDetails.name; - } - - if (cycleDetails || moduleDetails) { - const details = cycleDetails || moduleDetails; - - const currentProjectDetails = getProjectById(details?.project_id || ""); - const currentWorkspaceDetails = getWorkspaceById(details?.workspace_id || ""); - - eventPayload.workspaceId = details?.workspace_id; - eventPayload.workspaceName = currentWorkspaceDetails?.name; - eventPayload.projectId = details?.project_id; - eventPayload.projectIdentifier = currentProjectDetails?.identifier; - eventPayload.projectName = currentProjectDetails?.name; - } - - if (cycleDetails) { - eventPayload.cycleId = cycleDetails.id; - eventPayload.cycleName = cycleDetails.name; - } - - if (moduleDetails) { - eventPayload.moduleId = moduleDetails.id; - eventPayload.moduleName = moduleDetails.name; - } - }; - - const exportAnalytics = () => { - if (!workspaceSlug) return; - - const data: IExportAnalyticsFormData = { - x_axis: params.x_axis, - y_axis: params.y_axis, - }; - - if (params.segment) data.segment = params.segment; - if (params.project) data.project = params.project; - - analyticsService - .exportAnalytics(workspaceSlug.toString(), data) - .then((res) => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: res.message, - }); - - trackExportAnalytics(); - }) - .catch(() => - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "There was some error in exporting the analytics. Please try again.", - }) - ); - }; - - const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined; - const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined; - - // fetch cycle details - useEffect(() => { - if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return; - - fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString()); - }, [cycleId, cycleDetails, fetchCycleDetails, projectId, workspaceSlug]); - - // fetch module details - useEffect(() => { - if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return; - - fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString()); - }, [moduleId, moduleDetails, fetchModuleDetails, projectId, workspaceSlug]); - - const selectedProjects = params.project && params.project.length > 0 ? params.project : workspaceProjectIds; - - return ( -
-
-
- - {analytics ? analytics.total : "..."} -
{t("work_items")}
-
- {isProjectLevel && ( -
- - {renderFormattedDate( - (cycleId - ? cycleDetails?.created_at - : moduleId - ? moduleDetails?.created_at - : projectDetails?.created_at) ?? "" - )} -
- )} -
- -
- <> - {!isProjectLevel && selectedProjects && selectedProjects.length > 0 && ( - - )} - - -
- -
- - -
-
- ); -}); diff --git a/web/core/components/analytics/custom-analytics/table.tsx b/web/core/components/analytics/custom-analytics/table.tsx deleted file mode 100644 index fd580d7277d..00000000000 --- a/web/core/components/analytics/custom-analytics/table.tsx +++ /dev/null @@ -1,106 +0,0 @@ -"use client"; - -import { BarDatum } from "@nivo/bar"; -// plane package imports -import { ANALYTICS_X_AXIS_VALUES, ANALYTICS_Y_AXIS_VALUES } from "@plane/constants"; -import { IAnalyticsParams, IAnalyticsResponse, TIssuePriorities } from "@plane/types"; -import { PriorityIcon, Tooltip } from "@plane/ui"; -// helpers -import { generateBarColor, generateDisplayName, renderChartDynamicLabel } from "@/helpers/analytics.helper"; -import { cn } from "@/helpers/common.helper"; - -type Props = { - analytics: IAnalyticsResponse; - barGraphData: { - data: BarDatum[]; - xAxisKeys: string[]; - }; - params: IAnalyticsParams; - yAxisKey: "count" | "estimate"; -}; - -export const AnalyticsTable: React.FC = ({ analytics, barGraphData, params, yAxisKey }) => ( -
- - - - - {params.segment ? ( - barGraphData.xAxisKeys.map((key) => ( - - )) - ) : ( - - )} - - - - {barGraphData.data.map((item, index) => ( - - - {params.segment ? ( - barGraphData.xAxisKeys.map((key, index) => ( - - )) - ) : ( - - )} - - ))} - -
- {ANALYTICS_X_AXIS_VALUES.find((v) => v.value === params.x_axis)?.label} - -
- {params.segment === "priority" ? ( - - ) : ( - - )} - {renderChartDynamicLabel(generateDisplayName(key, analytics, params, "segment"))?.label} -
-
- {ANALYTICS_Y_AXIS_VALUES.find((v) => v.value === params.y_axis)?.label} -
-
-
- {params.x_axis === "priority" ? ( - - ) : ( -
- )} -
-
- -
- {generateDisplayName(`${item.name}`, analytics, params, "x_axis")} -
-
-
-
-
- {item[key] ?? 0} - {item[yAxisKey]}
-
-); diff --git a/web/core/components/analytics-v2/empty-state.tsx b/web/core/components/analytics/empty-state.tsx similarity index 89% rename from web/core/components/analytics-v2/empty-state.tsx rename to web/core/components/analytics/empty-state.tsx index 1a1ee86e821..5243e6c880b 100644 --- a/web/core/components/analytics-v2/empty-state.tsx +++ b/web/core/components/analytics/empty-state.tsx @@ -11,8 +11,8 @@ type Props = { className?: string; }; -const AnalyticsV2EmptyState = ({ title, description, assetPath, className }: Props) => { - const backgroundReolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-grid-background" }); +const AnalyticsEmptyState = ({ title, description, assetPath, className }: Props) => { + const backgroundReolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-grid-background" }); return (
); }; -export default AnalyticsV2EmptyState; +export default AnalyticsEmptyState; diff --git a/web/core/components/analytics/index.ts b/web/core/components/analytics/index.ts index c5f76989085..8ac82df5dfb 100644 --- a/web/core/components/analytics/index.ts +++ b/web/core/components/analytics/index.ts @@ -1,3 +1 @@ -export * from "./custom-analytics"; -export * from "./scope-and-demand"; -export * from "./project-modal"; +export * from "./overview/root"; diff --git a/web/core/components/analytics-v2/insight-card.tsx b/web/core/components/analytics/insight-card.tsx similarity index 93% rename from web/core/components/analytics-v2/insight-card.tsx rename to web/core/components/analytics/insight-card.tsx index 2ce4c2fdc1c..739de6645c6 100644 --- a/web/core/components/analytics-v2/insight-card.tsx +++ b/web/core/components/analytics/insight-card.tsx @@ -1,12 +1,12 @@ // plane package imports import React, { useMemo } from "react"; -import { IAnalyticsResponseFieldsV2 } from "@plane/types"; +import { IAnalyticsResponseFields } from "@plane/types"; import { Loader } from "@plane/ui"; // components import TrendPiece from "./trend-piece"; export type InsightCardProps = { - data?: IAnalyticsResponseFieldsV2; + data?: IAnalyticsResponseFields; label: string; isLoading?: boolean; versus?: string | null; diff --git a/web/core/components/analytics-v2/insight-table/data-table.tsx b/web/core/components/analytics/insight-table/data-table.tsx similarity index 95% rename from web/core/components/analytics-v2/insight-table/data-table.tsx rename to web/core/components/analytics/insight-table/data-table.tsx index 61e02cb33f6..8a66c3caf2d 100644 --- a/web/core/components/analytics-v2/insight-table/data-table.tsx +++ b/web/core/components/analytics/insight-table/data-table.tsx @@ -23,7 +23,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { cn } from "@plane/utils"; // plane web components import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; -import AnalyticsV2EmptyState from "../empty-state"; +import AnalyticsEmptyState from "../empty-state"; interface DataTableProps { columns: ColumnDef[]; @@ -40,7 +40,7 @@ export function DataTable({ columns, data, searchPlaceholder, act const { t } = useTranslation(); const inputRef = React.useRef(null); const [isSearchOpen, setIsSearchOpen] = React.useState(false); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-table" }); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-table" }); const table = useReactTable({ data, @@ -155,9 +155,9 @@ export function DataTable({ columns, data, searchPlaceholder, act
- diff --git a/web/core/components/analytics-v2/insight-table/index.ts b/web/core/components/analytics/insight-table/index.ts similarity index 100% rename from web/core/components/analytics-v2/insight-table/index.ts rename to web/core/components/analytics/insight-table/index.ts diff --git a/web/core/components/analytics-v2/insight-table/loader.tsx b/web/core/components/analytics/insight-table/loader.tsx similarity index 100% rename from web/core/components/analytics-v2/insight-table/loader.tsx rename to web/core/components/analytics/insight-table/loader.tsx diff --git a/web/core/components/analytics/insight-table/root.tsx b/web/core/components/analytics/insight-table/root.tsx new file mode 100644 index 00000000000..583db1b7a07 --- /dev/null +++ b/web/core/components/analytics/insight-table/root.tsx @@ -0,0 +1,49 @@ +import { ColumnDef, Row, Table } from "@tanstack/react-table"; +import { Download } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; +import { AnalyticsTableDataMap, TAnalyticsTabsBase } from "@plane/types"; +import { Button } from "@plane/ui"; +import { DataTable } from "./data-table"; +import { TableLoader } from "./loader"; +interface InsightTableProps> { + analyticsType: T; + data?: AnalyticsTableDataMap[T][]; + isLoading?: boolean; + columns: ColumnDef[]; + columnsLabels?: Record; + headerText: string; + onExport?: (rows: Row[]) => void; +} + +export const InsightTable = >( + props: InsightTableProps +): React.ReactElement => { + const { data, isLoading, columns, headerText, onExport } = props; + const { t } = useTranslation(); + if (isLoading) { + return ; + } + + return ( +
+ {data ? ( + ) => ( + + )} + /> + ) : ( +
{t("common.no_data_yet")}
+ )} +
+ ); +}; diff --git a/web/core/components/analytics-v2/loaders.tsx b/web/core/components/analytics/loaders.tsx similarity index 100% rename from web/core/components/analytics-v2/loaders.tsx rename to web/core/components/analytics/loaders.tsx diff --git a/web/core/components/analytics/old-page.tsx b/web/core/components/analytics/old-page.tsx deleted file mode 100644 index 719d6621431..00000000000 --- a/web/core/components/analytics/old-page.tsx +++ /dev/null @@ -1,107 +0,0 @@ -"use client"; - -import React, { Fragment } from "react"; -import { observer } from "mobx-react"; -import { useSearchParams } from "next/navigation"; -import { Tab } from "@headlessui/react"; -// plane package imports -import { ANALYTICS_TABS, EUserPermissionsLevel, EUserPermissions } from "@plane/constants"; -import { useTranslation } from "@plane/i18n"; -import { Header, EHeaderVariant } from "@plane/ui"; -// components -import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics"; -import { PageHead } from "@/components/core"; -import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state"; -// hooks -import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; - -const OldAnalyticsPage = observer(() => { - const searchParams = useSearchParams(); - const analytics_tab = searchParams.get("analytics_tab"); - // plane imports - const { t } = useTranslation(); - // store hooks - const { toggleCreateProjectModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); - const { workspaceProjectIds, loader } = useProject(); - const { currentWorkspace } = useWorkspace(); - const { allowPermissions } = useUserPermissions(); - // helper hooks - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" }); - // derived values - const pageTitle = currentWorkspace?.name - ? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name }) - : undefined; - - // permissions - const canPerformEmptyStateActions = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER], - EUserPermissionsLevel.WORKSPACE - ); - - // TODO: refactor loader implementation - return ( - <> - - {workspaceProjectIds && ( - <> - {workspaceProjectIds.length > 0 || loader === "init-loader" ? ( -
- -
- - {ANALYTICS_TABS.map((tab) => ( - - {({ selected }) => ( - - )} - - ))} - -
- - - - - - - - -
-
- ) : ( - { - setTrackElement("Analytics empty state"); - toggleCreateProjectModal(true); - }} - disabled={!canPerformEmptyStateActions} - /> - } - /> - )} - - )} - - ); -}); - -export default OldAnalyticsPage; diff --git a/web/core/components/analytics-v2/overview/active-project-item.tsx b/web/core/components/analytics/overview/active-project-item.tsx similarity index 100% rename from web/core/components/analytics-v2/overview/active-project-item.tsx rename to web/core/components/analytics/overview/active-project-item.tsx diff --git a/web/core/components/analytics-v2/overview/active-projects.tsx b/web/core/components/analytics/overview/active-projects.tsx similarity index 93% rename from web/core/components/analytics-v2/overview/active-projects.tsx rename to web/core/components/analytics/overview/active-projects.tsx index cb1e2ef693d..2bcc8e831de 100644 --- a/web/core/components/analytics-v2/overview/active-projects.tsx +++ b/web/core/components/analytics/overview/active-projects.tsx @@ -6,7 +6,7 @@ import useSWR from "swr"; import { useTranslation } from "@plane/i18n"; import { Loader } from "@plane/ui"; // plane web hooks -import { useAnalyticsV2, useProject } from "@/hooks/store"; +import { useAnalytics, useProject } from "@/hooks/store"; // plane web components import AnalyticsSectionWrapper from "../analytics-section-wrapper"; import ActiveProjectItem from "./active-project-item"; @@ -15,7 +15,7 @@ const ActiveProjects = observer(() => { const { t } = useTranslation(); const { fetchProjectAnalyticsCount } = useProject(); const { workspaceSlug } = useParams(); - const { selectedDurationLabel } = useAnalyticsV2(); + const { selectedDurationLabel } = useAnalytics(); const { data: projectAnalyticsCount, isLoading: isProjectAnalyticsCountLoading } = useSWR( workspaceSlug ? ["projectAnalyticsCount", workspaceSlug] : null, workspaceSlug diff --git a/web/core/components/analytics-v2/overview/index.ts b/web/core/components/analytics/overview/index.ts similarity index 100% rename from web/core/components/analytics-v2/overview/index.ts rename to web/core/components/analytics/overview/index.ts diff --git a/web/core/components/analytics-v2/overview/project-insights.tsx b/web/core/components/analytics/overview/project-insights.tsx similarity index 80% rename from web/core/components/analytics-v2/overview/project-insights.tsx rename to web/core/components/analytics/overview/project-insights.tsx index 9c8f829a14d..9844a4b4f8d 100644 --- a/web/core/components/analytics-v2/overview/project-insights.tsx +++ b/web/core/components/analytics/overview/project-insights.tsx @@ -6,13 +6,13 @@ import useSWR from "swr"; import { useTranslation } from "@plane/i18n"; import { TChartData } from "@plane/types"; // hooks -import { useAnalyticsV2 } from "@/hooks/store/use-analytics-v2"; +import { useAnalytics } from "@/hooks/store/use-analytics"; // services import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; -import { AnalyticsV2Service } from "@/services/analytics-v2.service"; +import { AnalyticsService } from "@/services/analytics.service"; // plane web components import AnalyticsSectionWrapper from "../analytics-section-wrapper"; -import AnalyticsV2EmptyState from "../empty-state"; +import AnalyticsEmptyState from "../empty-state"; import { ProjectInsightsLoader } from "../loaders"; const RadarChart = dynamic(() => @@ -21,25 +21,28 @@ const RadarChart = dynamic(() => })) ); -const analyticsV2Service = new AnalyticsV2Service(); +const analyticsService = new AnalyticsService(); const ProjectInsights = observer(() => { const params = useParams(); const { t } = useTranslation(); const workspaceSlug = params.workspaceSlug.toString(); const { selectedDuration, selectedDurationLabel, selectedProjects, selectedCycle, selectedModule, isPeekView } = - useAnalyticsV2(); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-chart-radar" }); + useAnalytics(); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-chart-radar" }); const { data: projectInsightsData, isLoading: isLoadingProjectInsight } = useSWR( `radar-chart-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`, () => - analyticsV2Service.getAdvanceAnalyticsCharts[]>(workspaceSlug, "projects", { - // date_filter: selectedDuration, - ...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }), - ...(selectedCycle ? { cycle_id: selectedCycle } : {}), - ...(selectedModule ? { module_id: selectedModule } : {}), - }, + analyticsService.getAdvanceAnalyticsCharts[]>( + workspaceSlug, + "projects", + { + // date_filter: selectedDuration, + ...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }), + ...(selectedCycle ? { cycle_id: selectedCycle } : {}), + ...(selectedModule ? { module_id: selectedModule } : {}), + }, isPeekView ) ); @@ -53,9 +56,9 @@ const ProjectInsights = observer(() => { {isLoadingProjectInsight ? ( ) : projectInsightsData && projectInsightsData?.length == 0 ? ( - diff --git a/web/core/components/analytics-v2/overview/root.tsx b/web/core/components/analytics/overview/root.tsx similarity index 100% rename from web/core/components/analytics-v2/overview/root.tsx rename to web/core/components/analytics/overview/root.tsx diff --git a/web/core/components/analytics/project-modal/header.tsx b/web/core/components/analytics/project-modal/header.tsx deleted file mode 100644 index 79d63ce3c0f..00000000000 --- a/web/core/components/analytics/project-modal/header.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { observer } from "mobx-react"; - -// icons -import { Expand, Shrink, X } from "lucide-react"; - -type Props = { - fullScreen: boolean; - handleClose: () => void; - setFullScreen: React.Dispatch>; - title: string; -}; - -export const ProjectAnalyticsModalHeader: React.FC = observer((props) => { - const { fullScreen, handleClose, setFullScreen, title } = props; - - return ( -
-

Analytics for {title}

-
- - -
-
- ); -}); diff --git a/web/core/components/analytics/project-modal/index.ts b/web/core/components/analytics/project-modal/index.ts deleted file mode 100644 index 70ca0260a4f..00000000000 --- a/web/core/components/analytics/project-modal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./header"; -export * from "./main-content"; -export * from "./modal"; diff --git a/web/core/components/analytics/project-modal/main-content.tsx b/web/core/components/analytics/project-modal/main-content.tsx deleted file mode 100644 index cd3813f80cf..00000000000 --- a/web/core/components/analytics/project-modal/main-content.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { Fragment } from "react"; -import { observer } from "mobx-react"; -import { Tab } from "@headlessui/react"; -// plane package imports -import { ANALYTICS_TABS } from "@plane/constants"; -import { useTranslation } from "@plane/i18n"; -import { ICycle, IModule, IProject } from "@plane/types"; -// components -import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics"; - -type Props = { - fullScreen: boolean; - cycleDetails: ICycle | undefined; - moduleDetails: IModule | undefined; - projectDetails: IProject | undefined; -}; - -export const ProjectAnalyticsModalMainContent: React.FC = observer((props) => { - const { fullScreen, cycleDetails, moduleDetails } = props; - const { t } = useTranslation(); - return ( - - - {ANALYTICS_TABS.map((tab) => ( - - {({ selected }) => ( - - )} - - ))} - - - - - - - - - - - ); -}); diff --git a/web/core/components/analytics/project-modal/modal.tsx b/web/core/components/analytics/project-modal/modal.tsx deleted file mode 100644 index fb45d6aa9dd..00000000000 --- a/web/core/components/analytics/project-modal/modal.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useState } from "react"; -import { observer } from "mobx-react"; -import { Dialog, Transition } from "@headlessui/react"; -import { ICycle, IModule, IProject } from "@plane/types"; - -// components -import { ProjectAnalyticsModalHeader, ProjectAnalyticsModalMainContent } from "@/components/analytics"; -// types - -type Props = { - isOpen: boolean; - onClose: () => void; - cycleDetails?: ICycle | undefined; - moduleDetails?: IModule | undefined; - projectDetails?: IProject | undefined; -}; - -export const ProjectAnalyticsModal: React.FC = observer((props) => { - const { isOpen, onClose, cycleDetails, moduleDetails, projectDetails } = props; - - const [fullScreen, setFullScreen] = useState(false); - - const handleClose = () => { - onClose(); - }; - - return ( - - - -
- -
-
- - -
-
-
-
-
-
-
- ); -}); diff --git a/web/core/components/analytics/scope-and-demand/demand.tsx b/web/core/components/analytics/scope-and-demand/demand.tsx deleted file mode 100644 index fefdc8fc6ee..00000000000 --- a/web/core/components/analytics/scope-and-demand/demand.tsx +++ /dev/null @@ -1,58 +0,0 @@ -// plane imports -import { STATE_GROUPS } from "@plane/constants"; -import { useTranslation } from "@plane/i18n"; -// types -import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types"; -// constants -import { Card } from "@plane/ui"; - -type Props = { - defaultAnalytics: IDefaultAnalyticsResponse; -}; - -export const AnalyticsDemand: React.FC = ({ defaultAnalytics }) => { - const { t } = useTranslation(); - - return ( - -
-

{t("workspace_analytics.open_tasks")}

-

{defaultAnalytics.open_issues}

-
-
- {defaultAnalytics?.open_issues_classified.map((group) => { - const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0); - - return ( -
-
-
- -
{group.state_group}
- - {group.state_count} - -
-

{percentage}%

-
-
-
-
-
- ); - })} -
- - ); -}; diff --git a/web/core/components/analytics/scope-and-demand/index.ts b/web/core/components/analytics/scope-and-demand/index.ts deleted file mode 100644 index ae756a961e2..00000000000 --- a/web/core/components/analytics/scope-and-demand/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./demand"; -export * from "./leaderboard"; -export * from "./scope-and-demand"; -export * from "./scope"; -export * from "./year-wise-issues"; diff --git a/web/core/components/analytics/scope-and-demand/leaderboard.tsx b/web/core/components/analytics/scope-and-demand/leaderboard.tsx deleted file mode 100644 index 76f96be4a27..00000000000 --- a/web/core/components/analytics/scope-and-demand/leaderboard.tsx +++ /dev/null @@ -1,69 +0,0 @@ -// plane ui -import { useTranslation } from "@plane/i18n"; -import { Card } from "@plane/ui"; -// components -import { ProfileEmptyState } from "@/components/ui"; -// helpers -import { getFileURL } from "@/helpers/file.helper"; -// image -import emptyUsers from "@/public/empty-state/empty_users.svg"; - -type Props = { - users: { - avatar_url: string | null; - display_name: string | null; - firstName: string; - lastName: string; - count: number; - id: string; - }[]; - title: string; - emptyStateMessage: string; - workspaceSlug: string; -}; - -export const AnalyticsLeaderBoard: React.FC = ({ users, title, emptyStateMessage, workspaceSlug }) => { - const { t } = useTranslation(); - return ( - -
{title}
- {users.length > 0 ? ( - - ) : ( -
- -
- )} -
- ); -}; diff --git a/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx b/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx deleted file mode 100644 index ae51727aa01..00000000000 --- a/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx +++ /dev/null @@ -1,115 +0,0 @@ -"use client"; -import { useParams } from "next/navigation"; -import useSWR from "swr"; -// ui -import { useTranslation } from "@plane/i18n"; -import { Button, ContentWrapper, Loader } from "@plane/ui"; -// components -import { AnalyticsDemand, AnalyticsLeaderBoard, AnalyticsScope, AnalyticsYearWiseIssues } from "@/components/analytics"; -// fetch-keys -import { DEFAULT_ANALYTICS } from "@/constants/fetch-keys"; -// services -import { AnalyticsService } from "@/services/analytics.service"; - -type Props = { - fullScreen?: boolean; -}; - -// services -const analyticsService = new AnalyticsService(); - -export const ScopeAndDemand: React.FC = (props) => { - const { fullScreen = true } = props; - - const { workspaceSlug, projectId, cycleId, moduleId } = useParams(); - const { t } = useTranslation(); - - const isProjectLevel = projectId ? true : false; - - const params = isProjectLevel - ? { - project: projectId ? [projectId.toString()] : null, - cycle: cycleId ? cycleId.toString() : null, - module: moduleId ? moduleId.toString() : null, - } - : undefined; - - const { - data: defaultAnalytics, - error: defaultAnalyticsError, - mutate: mutateDefaultAnalytics, - } = useSWR( - workspaceSlug ? DEFAULT_ANALYTICS(workspaceSlug.toString(), params) : null, - workspaceSlug ? () => analyticsService.getDefaultAnalytics(workspaceSlug.toString(), params) : null - ); - - // scope data - const pendingIssues = defaultAnalytics?.pending_issue_user ?? []; - const pendingUnAssignedIssuesUser = pendingIssues?.find((issue) => issue.assignees__id === null); - const pendingAssignedIssues = pendingIssues?.filter((issue) => issue.assignees__id !== null); - - return ( - <> - {!defaultAnalyticsError ? ( - defaultAnalytics ? ( - -
- - - ({ - avatar_url: user?.created_by__avatar_url, - firstName: user?.created_by__first_name, - lastName: user?.created_by__last_name, - display_name: user?.created_by__display_name, - count: user?.count, - id: user?.created_by__id, - }))} - title={t("workspace_analytics.most_work_items_created.title")} - emptyStateMessage={t("workspace_analytics.most_work_items_created.empty_state")} - workspaceSlug={workspaceSlug?.toString() ?? ""} - /> - ({ - avatar_url: user?.assignees__avatar_url, - firstName: user?.assignees__first_name, - lastName: user?.assignees__last_name, - display_name: user?.assignees__display_name, - count: user?.count, - id: user?.assignees__id, - }))} - title={t("workspace_analytics.most_work_items_closed.title")} - emptyStateMessage={t("workspace_analytics.most_work_items_closed.empty_state")} - workspaceSlug={workspaceSlug?.toString() ?? ""} - /> -
- -
-
-
- ) : ( - - - - - - - ) - ) : ( -
-
-

{t("workspace_analytics.error")}

-
- -
-
-
- )} - - ); -}; diff --git a/web/core/components/analytics/scope-and-demand/scope.tsx b/web/core/components/analytics/scope-and-demand/scope.tsx deleted file mode 100644 index 13cd60d5661..00000000000 --- a/web/core/components/analytics/scope-and-demand/scope.tsx +++ /dev/null @@ -1,99 +0,0 @@ -// plane types -import { useTranslation } from "@plane/i18n"; -import { IDefaultAnalyticsUser } from "@plane/types"; -// plane ui -import { Card } from "@plane/ui"; -// components -import { BarGraph, ProfileEmptyState } from "@/components/ui"; -// helpers -import { getFileURL } from "@/helpers/file.helper"; -// image -import emptyBarGraph from "@/public/empty-state/empty_bar_graph.svg"; - -type Props = { - pendingUnAssignedIssuesUser: IDefaultAnalyticsUser | undefined; - pendingAssignedIssues: IDefaultAnalyticsUser[]; -}; - -export const AnalyticsScope: React.FC = ({ pendingUnAssignedIssuesUser, pendingAssignedIssues }) => { - const { t } = useTranslation(); - return ( - -
-
-
-
{t("workspace_analytics.pending_work_items.title")}
- {pendingUnAssignedIssuesUser && ( -
- {t("unassigned")}: {pendingUnAssignedIssuesUser.count} -
- )} -
- - {pendingAssignedIssues && pendingAssignedIssues.length > 0 ? ( - `#f97316`} - customYAxisTickValues={pendingAssignedIssues.map((d) => (d.count > 0 ? d.count : 50))} - tooltip={(datum) => { - const assignee = pendingAssignedIssues.find((a) => a.assignees__id === `${datum.indexValue}`); - - return ( -
- - {assignee ? assignee.assignees__display_name : "No assignee"}:{" "} - - {datum.value} -
- ); - }} - axisBottom={{ - renderTick: (datum) => { - const assignee = pendingAssignedIssues[datum.tickIndex] ?? ""; - - if (assignee && assignee?.assignees__avatar_url && assignee?.assignees__avatar_url !== "") - return ( - - - - ); - else - return ( - - - - {datum.value ? `${assignee.assignees__display_name}`.toUpperCase()[0] : "?"} - - - ); - }, - }} - margin={{ top: 20 }} - theme={{ - axis: {}, - }} - /> - ) : ( -
- -
- )} -
-
-
- ); -}; diff --git a/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx b/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx deleted file mode 100644 index 0f469db7022..00000000000 --- a/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// ui -import { useTranslation } from "@plane/i18n"; -import { IDefaultAnalyticsResponse } from "@plane/types"; -import { Card } from "@plane/ui"; -import { LineGraph, ProfileEmptyState } from "@/components/ui"; -// image -import { MONTHS_LIST } from "@/constants/calendar"; -import emptyGraph from "@/public/empty-state/empty_graph.svg"; -// types -// constants - -type Props = { - defaultAnalytics: IDefaultAnalyticsResponse; -}; - -export const AnalyticsYearWiseIssues: React.FC = ({ defaultAnalytics }) => { - const { t } = useTranslation(); - return ( - -

{t("workspace_analytics.work_items_closed_in_a_year.title")}

- {defaultAnalytics.issue_completed_month_wise.length > 0 ? ( - ({ - x: t(month.shortTitle), - y: - defaultAnalytics.issue_completed_month_wise.find((data) => data.month === parseInt(index, 10)) - ?.count || 0, - })), - }, - ]} - customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map((data) => data.count)} - height="300px" - colors={(datum) => datum.color} - curve="monotoneX" - margin={{ top: 20 }} - enableSlices="x" - sliceTooltip={(datum) => ( -
- {datum.slice.points[0].data.yFormatted} - {t("workspace_analytics.work_items_closed_in")} - {datum.slice.points[0].data.xFormatted} -
- )} - theme={{ - background: "rgb(var(--color-background-100))", - }} - enableArea - /> - ) : ( -
- -
- )} -
- ); -}; diff --git a/web/core/components/analytics-v2/select/analytics-params.tsx b/web/core/components/analytics/select/analytics-params.tsx similarity index 80% rename from web/core/components/analytics-v2/select/analytics-params.tsx rename to web/core/components/analytics/select/analytics-params.tsx index 61a9d1b1f9e..f4ef0d9eb52 100644 --- a/web/core/components/analytics-v2/select/analytics-params.tsx +++ b/web/core/components/analytics/select/analytics-params.tsx @@ -3,31 +3,30 @@ import { observer } from "mobx-react"; import { Control, Controller, UseFormSetValue } from "react-hook-form"; import { Calendar, SlidersHorizontal } from "lucide-react"; // plane package imports -import { ANALYTICS_V2_X_AXIS_VALUES, ANALYTICS_V2_Y_AXIS_VALUES, ChartYAxisMetric } from "@plane/constants"; +import { ANALYTICS_X_AXIS_VALUES, ANALYTICS_Y_AXIS_VALUES, ChartYAxisMetric } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import { IAnalyticsV2Params } from "@plane/types"; +import { IAnalyticsParams } from "@plane/types"; import { cn } from "@plane/utils"; // plane web components -import { AnalyticsV2Service } from "@/services/analytics-v2.service"; import { SelectXAxis } from "./select-x-axis"; import { SelectYAxis } from "./select-y-axis"; type Props = { - control: Control; - setValue: UseFormSetValue; - params: IAnalyticsV2Params; + control: Control; + setValue: UseFormSetValue; + params: IAnalyticsParams; workspaceSlug: string; classNames?: string; }; -export const AnalyticsV2SelectParams: React.FC = observer((props) => { +export const AnalyticsSelectParams: React.FC = observer((props) => { const { control, params, classNames } = props; const xAxisOptions = useMemo( - () => ANALYTICS_V2_X_AXIS_VALUES.filter((option) => option.value !== params.group_by), + () => ANALYTICS_X_AXIS_VALUES.filter((option) => option.value !== params.group_by), [params.group_by] ); const groupByOptions = useMemo( - () => ANALYTICS_V2_X_AXIS_VALUES.filter((option) => option.value !== params.x_axis), + () => ANALYTICS_X_AXIS_VALUES.filter((option) => option.value !== params.x_axis), [params.x_axis] ); @@ -43,7 +42,7 @@ export const AnalyticsV2SelectParams: React.FC = observer((props) => { onChange={(val: ChartYAxisMetric | null) => { onChange(val); }} - options={ANALYTICS_V2_Y_AXIS_VALUES} + options={ANALYTICS_Y_AXIS_VALUES} hiddenOptions={[ChartYAxisMetric.ESTIMATE_POINT_COUNT]} /> )} diff --git a/web/core/components/analytics-v2/select/duration.tsx b/web/core/components/analytics/select/duration.tsx similarity index 76% rename from web/core/components/analytics-v2/select/duration.tsx rename to web/core/components/analytics/select/duration.tsx index de18ab2023f..5c99a61b0e9 100644 --- a/web/core/components/analytics-v2/select/duration.tsx +++ b/web/core/components/analytics/select/duration.tsx @@ -2,7 +2,7 @@ import React, { ReactNode } from "react"; import { Calendar } from "lucide-react"; // plane package imports -import { ANALYTICS_V2_DURATION_FILTER_OPTIONS } from "@plane/constants"; +import { ANALYTICS_DURATION_FILTER_OPTIONS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { CustomSearchSelect } from "@plane/ui"; // types @@ -10,7 +10,7 @@ import { TDropdownProps } from "@/components/dropdowns/types"; type Props = TDropdownProps & { value: string | null; - onChange: (val: (typeof ANALYTICS_V2_DURATION_FILTER_OPTIONS)[number]["value"]) => void; + onChange: (val: (typeof ANALYTICS_DURATION_FILTER_OPTIONS)[number]["value"]) => void; //optional button?: ReactNode; dropdownArrow?: boolean; @@ -23,7 +23,7 @@ type Props = TDropdownProps & { function DurationDropdown({ placeholder = "Duration", onChange, value }: Props) { useTranslation(); - const options = ANALYTICS_V2_DURATION_FILTER_OPTIONS.map((option) => ({ + const options = ANALYTICS_DURATION_FILTER_OPTIONS.map((option) => ({ value: option.value, query: option.name, content: ( @@ -40,7 +40,7 @@ function DurationDropdown({ placeholder = "Duration", onChange, value }: Props) label={
- {value ? ANALYTICS_V2_DURATION_FILTER_OPTIONS.find((opt) => opt.value === value)?.name : placeholder} + {value ? ANALYTICS_DURATION_FILTER_OPTIONS.find((opt) => opt.value === value)?.name : placeholder}
} /> diff --git a/web/core/components/analytics-v2/select/project.tsx b/web/core/components/analytics/select/project.tsx similarity index 100% rename from web/core/components/analytics-v2/select/project.tsx rename to web/core/components/analytics/select/project.tsx diff --git a/web/core/components/analytics-v2/select/select-x-axis.tsx b/web/core/components/analytics/select/select-x-axis.tsx similarity index 100% rename from web/core/components/analytics-v2/select/select-x-axis.tsx rename to web/core/components/analytics/select/select-x-axis.tsx diff --git a/web/core/components/analytics-v2/select/select-y-axis.tsx b/web/core/components/analytics/select/select-y-axis.tsx similarity index 100% rename from web/core/components/analytics-v2/select/select-y-axis.tsx rename to web/core/components/analytics/select/select-y-axis.tsx diff --git a/web/core/components/analytics/total-insights.tsx b/web/core/components/analytics/total-insights.tsx new file mode 100644 index 00000000000..61f3e7205b3 --- /dev/null +++ b/web/core/components/analytics/total-insights.tsx @@ -0,0 +1,102 @@ +// plane package imports +import { observer } from "mobx-react-lite"; +import { useParams } from "next/navigation"; +import useSWR from "swr"; +import { IInsightField, insightsFields } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; +import { IAnalyticsResponse, TAnalyticsTabsBase } from "@plane/types"; +//hooks +import { cn } from "@/helpers/common.helper"; +import { useAnalytics } from "@/hooks/store/use-analytics"; +//services +import { AnalyticsService } from "@/services/analytics.service"; +// plane web components +import InsightCard from "./insight-card"; + +const analyticsService = new AnalyticsService(); + +const getInsightLabel = ( + analyticsType: TAnalyticsTabsBase, + item: IInsightField, + isEpic: boolean | undefined, + t: (key: string, options?: any) => string +) => { + if (analyticsType === "work-items") { + return isEpic + ? t(item.i18nKey, { entity: t("common.epics") }) + : t(item.i18nKey, { entity: t("common.work_items") }); + } + + // Get the base translation with entity + const baseTranslation = t(item.i18nKey, { + ...item.i18nProps, + entity: item.i18nProps?.entity && t(item.i18nProps?.entity), + }); + + // Add prefix if available + const prefix = item.i18nProps?.prefix ? `${t(item.i18nProps.prefix)} ` : ""; + + // Add suffix if available + const suffix = item.i18nProps?.suffix ? ` ${t(item.i18nProps.suffix)}` : ""; + + // Combine prefix, base translation, and suffix + return `${prefix}${baseTranslation}${suffix}`; +}; + +const TotalInsights: React.FC<{ + analyticsType: TAnalyticsTabsBase; + peekView?: boolean; +}> = observer(({ analyticsType, peekView }) => { + const params = useParams(); + const workspaceSlug = params.workspaceSlug.toString(); + const { t } = useTranslation(); + const { + selectedDuration, + selectedProjects, + selectedDurationLabel, + selectedCycle, + selectedModule, + isPeekView, + isEpic, + } = useAnalytics(); + const { data: totalInsightsData, isLoading } = useSWR( + `total-insights-${analyticsType}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isEpic}`, + () => + analyticsService.getAdvanceAnalytics( + workspaceSlug, + analyticsType, + { + // date_filter: selectedDuration, + ...(selectedProjects?.length > 0 ? { project_ids: selectedProjects.join(",") } : {}), + ...(selectedCycle ? { cycle_id: selectedCycle } : {}), + ...(selectedModule ? { module_id: selectedModule } : {}), + ...(isEpic ? { epic: true } : {}), + }, + isPeekView + ) + ); + return ( +
+ {insightsFields[analyticsType]?.map((item) => ( + + ))} +
+ ); +}); + +export default TotalInsights; diff --git a/web/core/components/analytics-v2/trend-piece.tsx b/web/core/components/analytics/trend-piece.tsx similarity index 100% rename from web/core/components/analytics-v2/trend-piece.tsx rename to web/core/components/analytics/trend-piece.tsx diff --git a/web/core/components/analytics-v2/work-items/created-vs-resolved.tsx b/web/core/components/analytics/work-items/created-vs-resolved.tsx similarity index 70% rename from web/core/components/analytics-v2/work-items/created-vs-resolved.tsx rename to web/core/components/analytics/work-items/created-vs-resolved.tsx index 46f3faf54e4..76215238d76 100644 --- a/web/core/components/analytics-v2/work-items/created-vs-resolved.tsx +++ b/web/core/components/analytics/work-items/created-vs-resolved.tsx @@ -5,35 +5,46 @@ import useSWR from "swr"; // plane package imports import { useTranslation } from "@plane/i18n"; import { AreaChart } from "@plane/propel/charts/area-chart"; -import { IChartResponseV2, TChartData } from "@plane/types"; +import { IChartResponse, TChartData } from "@plane/types"; import { renderFormattedDate } from "@plane/utils"; // hooks -import { useAnalyticsV2 } from "@/hooks/store/use-analytics-v2"; +import { useAnalytics } from "@/hooks/store/use-analytics"; // services import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; -import { AnalyticsV2Service } from "@/services/analytics-v2.service"; +import { AnalyticsService } from "@/services/analytics.service"; // plane web components import AnalyticsSectionWrapper from "../analytics-section-wrapper"; -import AnalyticsV2EmptyState from "../empty-state"; +import AnalyticsEmptyState from "../empty-state"; import { ChartLoader } from "../loaders"; -const analyticsV2Service = new AnalyticsV2Service(); +const analyticsService = new AnalyticsService(); const CreatedVsResolved = observer(() => { - const { selectedDuration, selectedDurationLabel, selectedProjects, selectedCycle, selectedModule, isPeekView } = - useAnalyticsV2(); + const { + selectedDuration, + selectedDurationLabel, + selectedProjects, + selectedCycle, + selectedModule, + isPeekView, + isEpic, + } = useAnalytics(); const params = useParams(); const { t } = useTranslation(); const workspaceSlug = params.workspaceSlug.toString(); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-chart-area" }); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-chart-area" }); const { data: createdVsResolvedData, isLoading: isCreatedVsResolvedLoading } = useSWR( - `created-vs-resolved-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`, + `created-vs-resolved-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}-${isEpic}`, () => - analyticsV2Service.getAdvanceAnalyticsCharts(workspaceSlug, "work-items", { - // date_filter: selectedDuration, - ...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }), - ...(selectedCycle ? { cycle_id: selectedCycle } : {}), - ...(selectedModule ? { module_id: selectedModule } : {}), - }, + analyticsService.getAdvanceAnalyticsCharts( + workspaceSlug, + "work-items", + { + // date_filter: selectedDuration, + ...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }), + ...(selectedCycle ? { cycle_id: selectedCycle } : {}), + ...(selectedModule ? { module_id: selectedModule } : {}), + ...(isEpic ? { epic: true } : {}), + }, isPeekView ) ); @@ -89,11 +100,11 @@ const CreatedVsResolved = observer(() => { areas={areas} xAxis={{ key: "name", - label: "Date", + label: t("date"), }} yAxis={{ key: "count", - label: "Number of Issues", + label: t("no_of", { entity: t("work_items") }), offset: -30, dx: -22, }} @@ -110,9 +121,9 @@ const CreatedVsResolved = observer(() => { }} /> ) : ( - diff --git a/web/core/components/analytics-v2/work-items/customized-insights.tsx b/web/core/components/analytics/work-items/customized-insights.tsx similarity index 84% rename from web/core/components/analytics-v2/work-items/customized-insights.tsx rename to web/core/components/analytics/work-items/customized-insights.tsx index 86fea0c83b7..6574658227f 100644 --- a/web/core/components/analytics-v2/work-items/customized-insights.tsx +++ b/web/core/components/analytics/work-items/customized-insights.tsx @@ -4,14 +4,14 @@ import { useForm } from "react-hook-form"; // plane package imports import { ChartXAxisProperty, ChartYAxisMetric } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import { IAnalyticsV2Params } from "@plane/types"; +import { IAnalyticsParams } from "@plane/types"; import { cn } from "@plane/utils"; // plane web components import AnalyticsSectionWrapper from "../analytics-section-wrapper"; -import { AnalyticsV2SelectParams } from "../select/analytics-params"; +import { AnalyticsSelectParams } from "../select/analytics-params"; import PriorityChart from "./priority-chart"; -const defaultValues: IAnalyticsV2Params = { +const defaultValues: IAnalyticsParams = { x_axis: ChartXAxisProperty.PRIORITY, y_axis: ChartYAxisMetric.WORK_ITEM_COUNT, }; @@ -19,7 +19,7 @@ const defaultValues: IAnalyticsV2Params = { const CustomizedInsights = observer(({ peekView }: { peekView?: boolean }) => { const { t } = useTranslation(); const { workspaceSlug } = useParams(); - const { control, watch, setValue } = useForm({ + const { control, watch, setValue } = useForm({ defaultValues: { ...defaultValues, }, @@ -37,7 +37,7 @@ const CustomizedInsights = observer(({ peekView }: { peekView?: boolean }) => { className="col-span-1" headerClassName={cn(peekView ? "flex-col items-start" : "")} actions={ - = observer((props) => { const { projectDetails, cycleDetails, moduleDetails, fullScreen } = props; - const { updateSelectedProjects, updateSelectedCycle, updateSelectedModule, updateIsPeekView } = useAnalyticsV2(); + const { updateSelectedProjects, updateSelectedCycle, updateSelectedModule, updateIsPeekView } = useAnalytics(); const [isModalConfigured, setIsModalConfigured] = useState(false); useEffect(() => { diff --git a/web/core/components/analytics-v2/work-items/modal/header.tsx b/web/core/components/analytics/work-items/modal/header.tsx similarity index 100% rename from web/core/components/analytics-v2/work-items/modal/header.tsx rename to web/core/components/analytics/work-items/modal/header.tsx diff --git a/web/core/components/analytics-v2/work-items/modal/index.tsx b/web/core/components/analytics/work-items/modal/index.tsx similarity index 90% rename from web/core/components/analytics-v2/work-items/modal/index.tsx rename to web/core/components/analytics/work-items/modal/index.tsx index c30c2687d5b..292dc1be5e0 100644 --- a/web/core/components/analytics-v2/work-items/modal/index.tsx +++ b/web/core/components/analytics/work-items/modal/index.tsx @@ -1,8 +1,9 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { observer } from "mobx-react"; import { Dialog, Transition } from "@headlessui/react"; // plane package imports import { ICycle, IModule, IProject } from "@plane/types"; +import { useAnalytics } from "@/hooks/store"; // plane web components import { WorkItemsModalMainContent } from "./content"; import { WorkItemsModalHeader } from "./header"; @@ -13,17 +14,22 @@ type Props = { projectDetails?: IProject | undefined; cycleDetails?: ICycle | undefined; moduleDetails?: IModule | undefined; + isEpic?: boolean; }; export const WorkItemsModal: React.FC = observer((props) => { - const { isOpen, onClose, projectDetails, moduleDetails, cycleDetails } = props; - + const { isOpen, onClose, projectDetails, moduleDetails, cycleDetails, isEpic } = props; + const { updateIsEpic } = useAnalytics(); const [fullScreen, setFullScreen] = useState(false); const handleClose = () => { onClose(); }; + useEffect(() => { + updateIsEpic(isEpic ?? false); + }, [isEpic, updateIsEpic]); + return ( diff --git a/web/core/components/analytics-v2/work-items/priority-chart.tsx b/web/core/components/analytics/work-items/priority-chart.tsx similarity index 88% rename from web/core/components/analytics-v2/work-items/priority-chart.tsx rename to web/core/components/analytics/work-items/priority-chart.tsx index 9d10c2b6add..78a05c2c788 100644 --- a/web/core/components/analytics-v2/work-items/priority-chart.tsx +++ b/web/core/components/analytics/work-items/priority-chart.tsx @@ -8,8 +8,8 @@ import useSWR from "swr"; // plane package imports import { Download } from "lucide-react"; import { - ANALYTICS_V2_X_AXIS_VALUES, - ANALYTICS_V2_Y_AXIS_VALUES, + ANALYTICS_X_AXIS_VALUES, + ANALYTICS_Y_AXIS_VALUES, CHART_COLOR_PALETTES, ChartXAxisDateGrouping, ChartXAxisProperty, @@ -18,17 +18,17 @@ import { } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { BarChart } from "@plane/propel/charts/bar-chart"; -import { IChartResponseV2 } from "@plane/types"; +import { IChartResponse } from "@plane/types"; import { TBarItem, TChart, TChartData, TChartDatum } from "@plane/types/src/charts"; // plane web components import { Button } from "@plane/ui"; import { generateExtendedColors, parseChartData } from "@/components/chart/utils"; // hooks import { useProjectState } from "@/hooks/store"; -import { useAnalyticsV2 } from "@/hooks/store/use-analytics-v2"; +import { useAnalytics } from "@/hooks/store/use-analytics"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; -import { AnalyticsV2Service } from "@/services/analytics-v2.service"; -import AnalyticsV2EmptyState from "../empty-state"; +import { AnalyticsService } from "@/services/analytics.service"; +import AnalyticsEmptyState from "../empty-state"; import { DataTable } from "../insight-table/data-table"; import { ChartLoader } from "../loaders"; import { generateBarColor } from "./utils"; @@ -40,13 +40,13 @@ interface Props { x_axis_date_grouping?: ChartXAxisDateGrouping; } -const analyticsV2Service = new AnalyticsV2Service(); +const analyticsService = new AnalyticsService(); const PriorityChart = observer((props: Props) => { const { x_axis, y_axis, group_by } = props; const { t } = useTranslation(); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics-v2/empty-chart-bar" }); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-chart-bar" }); // store hooks - const { selectedDuration, selectedProjects, selectedCycle, selectedModule, isPeekView } = useAnalyticsV2(); + const { selectedDuration, selectedProjects, selectedCycle, selectedModule, isPeekView, isEpic } = useAnalytics(); const { workspaceStates } = useProjectState(); const { resolvedTheme } = useTheme(); // router @@ -55,9 +55,9 @@ const PriorityChart = observer((props: Props) => { const { data: priorityChartData, isLoading: priorityChartLoading } = useSWR( `customized-insights-chart-${workspaceSlug}-${selectedDuration}- - ${selectedProjects}-${selectedCycle}-${selectedModule}-${props.x_axis}-${props.y_axis}-${props.group_by}-${isPeekView}`, + ${selectedProjects}-${selectedCycle}-${selectedModule}-${props.x_axis}-${props.y_axis}-${props.group_by}-${isPeekView}-${isEpic}`, () => - analyticsV2Service.getAdvanceAnalyticsCharts( + analyticsService.getAdvanceAnalyticsCharts( workspaceSlug, "custom-work-items", { @@ -65,6 +65,7 @@ const PriorityChart = observer((props: Props) => { ...(selectedProjects?.length > 0 && { project_ids: selectedProjects?.join(",") }), ...(selectedCycle ? { cycle_id: selectedCycle } : {}), ...(selectedModule ? { module_id: selectedModule } : {}), + ...(isEpic ? { epic: true } : {}), ...props, }, isPeekView @@ -132,11 +133,11 @@ const PriorityChart = observer((props: Props) => { }, [chart_model, group_by, parsedData, resolvedTheme, workspaceStates, x_axis, y_axis]); const yAxisLabel = useMemo( - () => ANALYTICS_V2_Y_AXIS_VALUES.find((item) => item.value === props.y_axis)?.label ?? props.y_axis, + () => ANALYTICS_Y_AXIS_VALUES.find((item) => item.value === props.y_axis)?.label ?? props.y_axis, [props.y_axis] ); const xAxisLabel = useMemo( - () => ANALYTICS_V2_X_AXIS_VALUES.find((item) => item.value === props.x_axis)?.label ?? props.x_axis, + () => ANALYTICS_X_AXIS_VALUES.find((item) => item.value === props.x_axis)?.label ?? props.x_axis, [props.x_axis] ); @@ -237,9 +238,9 @@ const PriorityChart = observer((props: Props) => { /> ) : ( - diff --git a/web/core/components/analytics-v2/work-items/root.tsx b/web/core/components/analytics/work-items/root.tsx similarity index 100% rename from web/core/components/analytics-v2/work-items/root.tsx rename to web/core/components/analytics/work-items/root.tsx diff --git a/web/core/components/analytics-v2/work-items/utils.ts b/web/core/components/analytics/work-items/utils.ts similarity index 100% rename from web/core/components/analytics-v2/work-items/utils.ts rename to web/core/components/analytics/work-items/utils.ts diff --git a/web/core/components/analytics-v2/work-items/workitems-insight-table.tsx b/web/core/components/analytics/work-items/workitems-insight-table.tsx similarity index 73% rename from web/core/components/analytics-v2/work-items/workitems-insight-table.tsx rename to web/core/components/analytics/work-items/workitems-insight-table.tsx index 6dca7d92931..45e12b1e3b7 100644 --- a/web/core/components/analytics-v2/work-items/workitems-insight-table.tsx +++ b/web/core/components/analytics/work-items/workitems-insight-table.tsx @@ -1,5 +1,6 @@ -import { useMemo } from "react"; +import { useMemo, useCallback } from "react"; import { ColumnDef, Row } from "@tanstack/react-table"; +import { download, generateCsv } from "export-to-csv"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; @@ -12,13 +13,14 @@ import { Avatar } from "@plane/ui"; import { getFileURL } from "@plane/utils"; import { Logo } from "@/components/common/logo"; // hooks -import { useAnalyticsV2 } from "@/hooks/store/use-analytics-v2"; +import { useAnalytics } from "@/hooks/store/use-analytics"; import { useProject } from "@/hooks/store/use-project"; -import { AnalyticsV2Service } from "@/services/analytics-v2.service"; +import { AnalyticsService } from "@/services/analytics.service"; // plane web components +import { csvConfig } from "../config"; import { InsightTable } from "../insight-table"; -const analyticsV2Service = new AnalyticsV2Service(); +const analyticsService = new AnalyticsService(); const WorkItemsInsightTable = observer(() => { // router @@ -27,11 +29,11 @@ const WorkItemsInsightTable = observer(() => { const { t } = useTranslation(); // store hooks const { getProjectById } = useProject(); - const { selectedDuration, selectedProjects, selectedCycle, selectedModule, isPeekView } = useAnalyticsV2(); + const { selectedDuration, selectedProjects, selectedCycle, selectedModule, isPeekView, isEpic } = useAnalytics(); const { data: workItemsData, isLoading } = useSWR( - `insights-table-work-items-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`, + `insights-table-work-items-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}-${isEpic}`, () => - analyticsV2Service.getAdvanceAnalyticsStats( + analyticsService.getAdvanceAnalyticsStats( workspaceSlug, "work-items", { @@ -39,23 +41,25 @@ const WorkItemsInsightTable = observer(() => { ...(selectedProjects?.length > 0 ? { project_ids: selectedProjects.join(",") } : {}), ...(selectedCycle ? { cycle_id: selectedCycle } : {}), ...(selectedModule ? { module_id: selectedModule } : {}), + ...(isEpic ? { epic: true } : {}), }, isPeekView ) ); // derived values - const columnsLabels = useMemo( - () => ({ - backlog_work_items: t("workspace_projects.state.backlog"), - started_work_items: t("workspace_projects.state.started"), - un_started_work_items: t("workspace_projects.state.unstarted"), - completed_work_items: t("workspace_projects.state.completed"), - cancelled_work_items: t("workspace_projects.state.cancelled"), - project__name: t("common.project"), - display_name: t("common.assignee"), - }), - [t] - ); + const columnsLabels: Record, string> = + useMemo( + () => ({ + backlog_work_items: t("workspace_projects.state.backlog"), + started_work_items: t("workspace_projects.state.started"), + un_started_work_items: t("workspace_projects.state.unstarted"), + completed_work_items: t("workspace_projects.state.completed"), + cancelled_work_items: t("workspace_projects.state.cancelled"), + project__name: t("common.project"), + display_name: t("common.assignee"), + }), + [t] + ); const columns = useMemo( () => [ @@ -135,6 +139,25 @@ const WorkItemsInsightTable = observer(() => { [columnsLabels, getProjectById, isPeekView, t] ); + const exportCSV = useCallback( + (rows: Row[]) => { + const rowData: any = rows.map((row) => { + const { project_id, avatar_url, assignee_id, ...exportableData } = row.original; + return Object.fromEntries( + Object.entries(exportableData).map(([key, value]) => { + if (columnsLabels?.[key as keyof typeof columnsLabels]) { + return [columnsLabels[key as keyof typeof columnsLabels], value]; + } + return [key, value]; + }) + ); + }); + const csv = generateCsv(csvConfig(workspaceSlug))(rowData); + download(csvConfig(workspaceSlug))(csv); + }, + [columnsLabels, workspaceSlug] + ); + return ( analyticsType="work-items" @@ -142,7 +165,8 @@ const WorkItemsInsightTable = observer(() => { isLoading={isLoading} columns={columns} columnsLabels={columnsLabels} - headerText={isPeekView ? columnsLabels["display_name"] : columnsLabels["project__name"]} + headerText={isPeekView ? t("common.assignee") : t("common.projects")} + onExport={exportCSV} /> ); }); diff --git a/web/core/components/core/sidebar/progress-chart.tsx b/web/core/components/core/sidebar/progress-chart.tsx index bb4eeb49351..e6317eeb9f0 100644 --- a/web/core/components/core/sidebar/progress-chart.tsx +++ b/web/core/components/core/sidebar/progress-chart.tsx @@ -1,155 +1,64 @@ import React from "react"; -import { eachDayOfInterval, isValid } from "date-fns"; -import { TModuleCompletionChartDistribution } from "@plane/types"; -// ui -import { LineGraph } from "@/components/ui"; -// helpers -import { getDate, renderFormattedDateWithoutYear } from "@/helpers/date-time.helper"; -//types +import { AreaChart } from "@plane/propel/charts/area-chart"; +import { TChartData, TModuleCompletionChartDistribution } from "@plane/types"; +import { renderFormattedDateWithoutYear } from "@/helpers/date-time.helper"; type Props = { distribution: TModuleCompletionChartDistribution; - startDate: string | Date; - endDate: string | Date; totalIssues: number; className?: string; plotTitle?: string; }; -const styleById = { - ideal: { - strokeDasharray: "6, 3", - strokeWidth: 1, - }, - default: { - strokeWidth: 1, - }, -}; - -const DashedLine = ({ series, lineGenerator, xScale, yScale }: any) => - series.map(({ id, data, color }: any) => ( - ({ - x: xScale(d.data.x), - y: yScale(d.data.y), - })) - )} - fill="none" - stroke={color ?? "#ddd"} - style={styleById[id as keyof typeof styleById] || styleById.default} - /> - )); - -const ProgressChart: React.FC = ({ - distribution, - startDate, - endDate, - totalIssues, - className = "", - plotTitle = "work items", -}) => { - const chartData = Object.keys(distribution ?? []).map((key) => ({ - currentDate: renderFormattedDateWithoutYear(key), - pending: distribution[key], +const ProgressChart: React.FC = ({ distribution, totalIssues, className = "", plotTitle = "work items" }) => { + const chartData: TChartData[] = Object.keys(distribution ?? []).map((key, index) => ({ + name: renderFormattedDateWithoutYear(key), + current: distribution[key] ?? 0, + ideal: totalIssues * (1 - index / (Object.keys(distribution ?? []).length - 1)), })); - const generateXAxisTickValues = () => { - const start = getDate(startDate); - const end = getDate(endDate); - - let dates: Date[] = []; - if (start && end && isValid(start) && isValid(end)) { - dates = eachDayOfInterval({ start, end }); - } - - if (dates.length === 0) return []; - - const formattedDates = dates.map((d) => renderFormattedDateWithoutYear(d)); - const firstDate = formattedDates[0]; - const lastDate = formattedDates[formattedDates.length - 1]; - - if (formattedDates.length <= 2) return [firstDate, lastDate]; - - const middleDateIndex = Math.floor(formattedDates.length / 2); - const middleDate = formattedDates[middleDateIndex]; - - return [firstDate, middleDate, lastDate]; - }; - return (
- 0 - ? chartData.map((item, index) => ({ - index, - x: item.currentDate, - y: item.pending, - color: "#3F76FF", - })) - : [], - enableArea: true, + key: "current", + label: `Current ${plotTitle}`, + strokeColor: "#3F76FF", + fill: "#3F76FF33", + fillOpacity: 1, + showDot: true, + smoothCurves: true, + strokeOpacity: 1, + stackId: "bar-one", }, { - id: "ideal", - color: "#a9bbd0", - fill: "transparent", - data: - chartData.length > 0 - ? [ - { - x: chartData[0].currentDate, - y: totalIssues, - }, - { - x: chartData[chartData.length - 1].currentDate, - y: 0, - }, - ] - : [], + key: "ideal", + label: `Ideal ${plotTitle}`, + strokeColor: "#A9BBD0", + fill: "#A9BBD0", + fillOpacity: 0, + showDot: true, + smoothCurves: true, + strokeOpacity: 1, + stackId: "bar-two", + style: { + strokeDasharray: "6, 3", + strokeWidth: 1, + }, }, ]} - layers={["grid", "markers", "areas", DashedLine, "slices", "points", "axes", "legends"]} - axisBottom={{ - tickValues: generateXAxisTickValues(), - }} - enablePoints={false} - enableArea - colors={(datum) => datum.color ?? "#3F76FF"} - customYAxisTickValues={[0, totalIssues]} - gridXValues={ - chartData.length > 0 ? chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : "")) : undefined - } - enableSlices="x" - sliceTooltip={(datum) => ( -
- {datum.slice.points?.[1]?.data?.yFormatted ?? datum.slice.points[0].data.yFormatted} - {plotTitle} pending on - {datum.slice.points[0].data.xFormatted} -
- )} - theme={{ - background: "transparent", - axis: { - domain: { - line: { - stroke: "rgb(var(--color-border))", - strokeWidth: 1, - }, - }, + xAxis={{ key: "name", label: "Date" }} + yAxis={{ key: "current", label: "Completion" }} + margin={{ bottom: 30 }} + className="h-[370px] w-full" + legend={{ + align: "center", + verticalAlign: "bottom", + layout: "horizontal", + wrapperStyles: { + marginTop: 20, }, }} /> diff --git a/web/core/components/cycles/active-cycle/productivity.tsx b/web/core/components/cycles/active-cycle/productivity.tsx index 613355c7eba..f53e2ef8760 100644 --- a/web/core/components/cycles/active-cycle/productivity.tsx +++ b/web/core/components/cycles/active-cycle/productivity.tsx @@ -54,17 +54,7 @@ export const ActiveCycleProductivity: FC = observe {cycle.total_issues > 0 ? ( <>
-
-
-
- - {t("project_cycles.active_cycle.ideal")} -
-
- - {t("project_cycles.active_cycle.current")} -
-
+
{estimateType === "points" ? ( {`Pending points - ${cycle.backlog_estimate_points + cycle.unstarted_estimate_points + cycle.started_estimate_points}`} ) : ( @@ -78,16 +68,12 @@ export const ActiveCycleProductivity: FC = observe {estimateType === "points" ? ( ) : ( diff --git a/web/core/components/dashboard/home-dashboard-widgets.tsx b/web/core/components/dashboard/home-dashboard-widgets.tsx deleted file mode 100644 index fe64278dc99..00000000000 --- a/web/core/components/dashboard/home-dashboard-widgets.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// types -import { TWidgetKeys } from "@plane/types"; -// components -import { - AssignedIssuesWidget, - CreatedIssuesWidget, - IssuesByPriorityWidget, - IssuesByStateGroupWidget, - OverviewStatsWidget, - RecentActivityWidget, - RecentCollaboratorsWidget, - RecentProjectsWidget, - WidgetProps, -} from "@/components/dashboard"; -// hooks -import { useDashboard } from "@/hooks/store"; - -const WIDGETS_LIST: { - [key in TWidgetKeys]: { component: React.FC; fullWidth: boolean }; -} = { - overview_stats: { component: OverviewStatsWidget, fullWidth: true }, - assigned_issues: { component: AssignedIssuesWidget, fullWidth: false }, - created_issues: { component: CreatedIssuesWidget, fullWidth: false }, - issues_by_state_groups: { component: IssuesByStateGroupWidget, fullWidth: false }, - issues_by_priority: { component: IssuesByPriorityWidget, fullWidth: false }, - recent_activity: { component: RecentActivityWidget, fullWidth: false }, - recent_projects: { component: RecentProjectsWidget, fullWidth: false }, - recent_collaborators: { component: RecentCollaboratorsWidget, fullWidth: true }, -}; - -export const DashboardWidgets = observer(() => { - // router - const { workspaceSlug } = useParams(); - // store hooks - const { homeDashboardId, homeDashboardWidgets } = useDashboard(); - - const doesWidgetExist = (widgetKey: TWidgetKeys) => - Boolean(homeDashboardWidgets?.find((widget) => widget.key === widgetKey)); - - if (!workspaceSlug || !homeDashboardId) return null; - - return ( -
- {Object.entries(WIDGETS_LIST).map(([key, widget]) => { - const WidgetComponent = widget.component; - // if the widget doesn't exist, return null - if (!doesWidgetExist(key as TWidgetKeys)) return null; - // if the widget is full width, return it in a 2 column grid - if (widget.fullWidth) - return ( -
- -
- ); - else - return ; - })} -
- ); -}); diff --git a/web/core/components/dashboard/index.ts b/web/core/components/dashboard/index.ts index 129cdb69ea3..e88b1c701d9 100644 --- a/web/core/components/dashboard/index.ts +++ b/web/core/components/dashboard/index.ts @@ -1,3 +1,2 @@ export * from "./widgets"; -export * from "./home-dashboard-widgets"; export * from "./project-empty-state"; diff --git a/web/core/components/dashboard/widgets/index.ts b/web/core/components/dashboard/widgets/index.ts index 31fc645d410..e622d708f71 100644 --- a/web/core/components/dashboard/widgets/index.ts +++ b/web/core/components/dashboard/widgets/index.ts @@ -5,8 +5,6 @@ export * from "./issue-panels"; export * from "./loaders"; export * from "./assigned-issues"; export * from "./created-issues"; -export * from "./issues-by-priority"; -export * from "./issues-by-state-group"; export * from "./overview-stats"; export * from "./recent-activity"; export * from "./recent-collaborators"; diff --git a/web/core/components/dashboard/widgets/issues-by-priority.tsx b/web/core/components/dashboard/widgets/issues-by-priority.tsx deleted file mode 100644 index ac678812057..00000000000 --- a/web/core/components/dashboard/widgets/issues-by-priority.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import Link from "next/link"; -// types -import { EDurationFilters } from "@plane/constants"; -import { TIssuesByPriorityWidgetFilters, TIssuesByPriorityWidgetResponse } from "@plane/types"; -// components -import { Card } from "@plane/ui"; -import { - DurationFilterDropdown, - IssuesByPriorityEmptyState, - WidgetLoader, - WidgetProps, -} from "@/components/dashboard/widgets"; -import { IssuesByPriorityGraph } from "@/components/graphs"; -// constants -// helpers -import { getCustomDates } from "@/helpers/dashboard.helper"; -// hooks -import { useDashboard } from "@/hooks/store"; -import { useAppRouter } from "@/hooks/use-app-router"; - -const WIDGET_KEY = "issues_by_priority"; - -export const IssuesByPriorityWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; - // router - const router = useAppRouter(); - // store hooks - const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard(); - // derived values - const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - const selectedDuration = widgetDetails?.widget_filters.duration ?? EDurationFilters.NONE; - const selectedCustomDates = widgetDetails?.widget_filters.custom_dates ?? []; - - const handleUpdateFilters = async (filters: Partial) => { - if (!widgetDetails) return; - - await updateDashboardWidgetFilters(workspaceSlug, dashboardId, widgetDetails.id, { - widgetKey: WIDGET_KEY, - filters, - }); - - const filterDates = getCustomDates( - filters.duration ?? selectedDuration, - filters.custom_dates ?? selectedCustomDates - ); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }); - }; - - useEffect(() => { - const filterDates = getCustomDates(selectedDuration, selectedCustomDates); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (!widgetDetails || !widgetStats) return ; - - const totalCount = widgetStats.reduce((acc, item) => acc + item?.count, 0); - const chartData = widgetStats.map((item) => ({ - priority: item?.priority, - priority_count: item?.count, - })); - - return ( - -
- - Assigned by priority - - - handleUpdateFilters({ - duration: val, - ...(val === "custom" ? { custom_dates: customDates } : {}), - }) - } - /> -
- {totalCount > 0 ? ( -
-
- { - router.push( - `/${workspaceSlug}/workspace-views/assigned?priority=${`${datum.data.priority}`.toLowerCase()}` - ); - }} - /> -
-
- ) : ( -
- -
- )} -
- ); -}); diff --git a/web/core/components/dashboard/widgets/issues-by-state-group.tsx b/web/core/components/dashboard/widgets/issues-by-state-group.tsx deleted file mode 100644 index fec4204cfc3..00000000000 --- a/web/core/components/dashboard/widgets/issues-by-state-group.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import { useEffect, useState } from "react"; -import { linearGradientDef } from "@nivo/core"; -import { observer } from "mobx-react"; -import Link from "next/link"; -// types -import { EDurationFilters, STATE_GROUPS } from "@plane/constants"; -import { TIssuesByStateGroupsWidgetFilters, TIssuesByStateGroupsWidgetResponse, TStateGroups } from "@plane/types"; -// components -import { Card } from "@plane/ui"; -import { - DurationFilterDropdown, - IssuesByStateGroupEmptyState, - WidgetLoader, - WidgetProps, -} from "@/components/dashboard/widgets"; -import { PieGraph } from "@/components/ui"; -// helpers -import { getCustomDates } from "@/helpers/dashboard.helper"; -// hooks -import { useDashboard } from "@/hooks/store"; -import { useAppRouter } from "@/hooks/use-app-router"; - -const WIDGET_KEY = "issues_by_state_groups"; - -export const STATE_GROUP_GRAPH_COLORS: Record = { - backlog: "#CDCED6", - unstarted: "#80838D", - started: "#FFC53D", - completed: "#3E9B4F", - cancelled: "#E5484D", -}; -// colors for work items by state group widget graph arcs -export const STATE_GROUP_GRAPH_GRADIENTS = [ - linearGradientDef("gradientBacklog", [ - { offset: 0, color: "#DEDEDE" }, - { offset: 100, color: "#BABABE" }, - ]), - linearGradientDef("gradientUnstarted", [ - { offset: 0, color: "#D4D4D4" }, - { offset: 100, color: "#878796" }, - ]), - linearGradientDef("gradientStarted", [ - { offset: 0, color: "#FFD300" }, - { offset: 100, color: "#FAE270" }, - ]), - linearGradientDef("gradientCompleted", [ - { offset: 0, color: "#0E8B1B" }, - { offset: 100, color: "#37CB46" }, - ]), - linearGradientDef("gradientCanceled", [ - { offset: 0, color: "#C90004" }, - { offset: 100, color: "#FF7679" }, - ]), -]; - -export const IssuesByStateGroupWidget: React.FC = observer((props) => { - const { dashboardId, workspaceSlug } = props; - // states - const [defaultStateGroup, setDefaultStateGroup] = useState(null); - const [activeStateGroup, setActiveStateGroup] = useState(null); - // router - const router = useAppRouter(); - // store hooks - const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard(); - // derived values - const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); - const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); - const selectedDuration = widgetDetails?.widget_filters.duration ?? EDurationFilters.NONE; - const selectedCustomDates = widgetDetails?.widget_filters.custom_dates ?? []; - - const handleUpdateFilters = async (filters: Partial) => { - if (!widgetDetails) return; - - await updateDashboardWidgetFilters(workspaceSlug, dashboardId, widgetDetails.id, { - widgetKey: WIDGET_KEY, - filters, - }); - - const filterDates = getCustomDates( - filters.duration ?? selectedDuration, - filters.custom_dates ?? selectedCustomDates - ); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }); - }; - - // fetch widget stats - useEffect(() => { - const filterDates = getCustomDates(selectedDuration, selectedCustomDates); - fetchWidgetStats(workspaceSlug, dashboardId, { - widget_key: WIDGET_KEY, - ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // set active group for center metric - useEffect(() => { - if (!widgetStats) return; - - const startedCount = widgetStats?.find((item) => item?.state === "started")?.count ?? 0; - const unStartedCount = widgetStats?.find((item) => item?.state === "unstarted")?.count ?? 0; - const backlogCount = widgetStats?.find((item) => item?.state === "backlog")?.count ?? 0; - const completedCount = widgetStats?.find((item) => item?.state === "completed")?.count ?? 0; - const canceledCount = widgetStats?.find((item) => item?.state === "cancelled")?.count ?? 0; - - const stateGroup = - startedCount > 0 - ? "started" - : unStartedCount > 0 - ? "unstarted" - : backlogCount > 0 - ? "backlog" - : completedCount > 0 - ? "completed" - : canceledCount > 0 - ? "cancelled" - : null; - - setActiveStateGroup(stateGroup); - setDefaultStateGroup(stateGroup); - }, [widgetStats]); - - if (!widgetDetails || !widgetStats) return ; - - const totalCount = widgetStats?.reduce((acc, item) => acc + item?.count, 0); - const chartData = widgetStats?.map((item) => ({ - color: STATE_GROUP_GRAPH_COLORS[item?.state as keyof typeof STATE_GROUP_GRAPH_COLORS], - id: item?.state, - label: item?.state, - value: (item?.count / totalCount) * 100, - })); - - const CenteredMetric = ({ dataWithArc, centerX, centerY }: any) => { - const data = dataWithArc?.find((datum: any) => datum?.id === activeStateGroup); - const percentage = chartData?.find((item) => item.id === activeStateGroup)?.value?.toFixed(0); - - return ( - - - {percentage}% - - - {data?.id} - - - ); - }; - - return ( - -
- - Assigned by state - - - handleUpdateFilters({ - duration: val, - ...(val === "custom" ? { custom_dates: customDates } : {}), - }) - } - /> -
- {totalCount > 0 ? ( -
-
-
- datum.data.color} - padAngle={1} - enableArcLinkLabels={false} - enableArcLabels={false} - activeOuterRadiusOffset={5} - tooltip={() => <>} - margin={{ - top: 0, - right: 5, - bottom: 0, - left: 5, - }} - defs={STATE_GROUP_GRAPH_GRADIENTS} - fill={Object.values(STATE_GROUPS).map((p) => ({ - match: { - id: p.key, - }, - id: `gradient${p.label}`, - }))} - onClick={(datum, e) => { - e.preventDefault(); - e.stopPropagation(); - router.push(`/${workspaceSlug}/workspace-views/assigned/?state_group=${datum.id}`); - }} - onMouseEnter={(datum) => setActiveStateGroup(datum.id as TStateGroups)} - onMouseLeave={() => setActiveStateGroup(defaultStateGroup)} - layers={["arcs", CenteredMetric]} - /> -
-
- {chartData.map((item) => ( -
-
-
- {item.label} -
- {item.value.toFixed(0)}% -
- ))} -
-
-
- ) : ( -
- -
- )} - - ); -}); diff --git a/web/core/components/graphs/index.ts b/web/core/components/graphs/index.ts deleted file mode 100644 index 305c3944ea4..00000000000 --- a/web/core/components/graphs/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./issues-by-priority"; diff --git a/web/core/components/graphs/issues-by-priority.tsx b/web/core/components/graphs/issues-by-priority.tsx deleted file mode 100644 index faed3709ec6..00000000000 --- a/web/core/components/graphs/issues-by-priority.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { ComputedDatum } from "@nivo/bar"; -import { Theme, linearGradientDef } from "@nivo/core"; -import { ISSUE_PRIORITIES } from "@plane/constants"; -// components -import { TIssuePriorities } from "@plane/types"; -import { BarGraph } from "@/components/ui"; -// helpers -import { capitalizeFirstLetter } from "@/helpers/string.helper"; - -// gradients for work items by priority widget graph bars -export const PRIORITY_GRAPH_GRADIENTS = [ - linearGradientDef( - "gradient_urgent", - [ - { offset: 0, color: "#A90408" }, - { offset: 100, color: "#DF4D51" }, - ], - { - x1: 1, - y1: 0, - x2: 0, - y2: 0, - } - ), - linearGradientDef( - "gradient_high", - [ - { offset: 0, color: "#FE6B00" }, - { offset: 100, color: "#FFAC88" }, - ], - { - x1: 1, - y1: 0, - x2: 0, - y2: 0, - } - ), - linearGradientDef( - "gradient_medium", - [ - { offset: 0, color: "#F5AC00" }, - { offset: 100, color: "#FFD675" }, - ], - { - x1: 1, - y1: 0, - x2: 0, - y2: 0, - } - ), - linearGradientDef( - "gradient_low", - [ - { offset: 0, color: "#1B46DE" }, - { offset: 100, color: "#4F9BF4" }, - ], - { - x1: 1, - y1: 0, - x2: 0, - y2: 0, - } - ), - linearGradientDef( - "gradient_none", - [ - { offset: 0, color: "#A0A1A9" }, - { offset: 100, color: "#B9BBC6" }, - ], - { - x1: 1, - y1: 0, - x2: 0, - y2: 0, - } - ), -]; - -type Props = { - borderRadius?: number; - data: { - priority: TIssuePriorities; - priority_count: number; - }[]; - height?: number; - onBarClick?: ( - datum: ComputedDatum & { - color: string; - } - ) => void; - padding?: number; - theme?: Theme; -}; - -const PRIORITY_TEXT_COLORS = { - urgent: "#CE2C31", - high: "#AB4800", - medium: "#AB6400", - low: "#1F2D5C", - none: "#60646C", -}; - -export const IssuesByPriorityGraph: React.FC = (props) => { - const { borderRadius = 8, data, height = 300, onBarClick, padding = 0.05, theme } = props; - - const chartData = data.map((priority) => ({ - priority: capitalizeFirstLetter(priority.priority), - value: priority.priority_count, - })); - - return ( - p.priority_count)} - axisBottom={{ - tickPadding: 8, - tickSize: 0, - }} - tooltip={(datum) => ( -
- - {datum.data.priority}: - {datum.value} -
- )} - colors={({ data }) => `url(#gradient${data.priority})`} - defs={PRIORITY_GRAPH_GRADIENTS} - fill={ISSUE_PRIORITIES.map((p) => ({ - match: { - id: p.key, - }, - id: `gradient_${p.key}`, - }))} - onClick={(datum) => { - if (onBarClick) onBarClick(datum); - }} - theme={{ - axis: { - domain: { - line: { - stroke: "transparent", - }, - }, - ticks: { - text: { - fontSize: 13, - }, - }, - }, - grid: { - line: { - stroke: "transparent", - }, - }, - ...theme, - }} - /> - ); -}; diff --git a/web/core/components/issues/filters.tsx b/web/core/components/issues/filters.tsx index a50afe50ebc..9ef8706eaed 100644 --- a/web/core/components/issues/filters.tsx +++ b/web/core/components/issues/filters.tsx @@ -17,8 +17,7 @@ import { isIssueFilterActive } from "@/helpers/filter.helper"; import { useLabel, useProjectState, useMember, useIssues } from "@/hooks/store"; // plane web types import { TProject } from "@/plane-web/types"; -import { ProjectAnalyticsModal } from "../analytics"; -import { WorkItemsModal } from "../analytics-v2/work-items/modal"; +import { WorkItemsModal } from "../analytics/work-items/modal"; type Props = { currentProjectDetails: TProject | undefined; @@ -102,6 +101,7 @@ const HeaderFilters = observer((props: Props) => { isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} projectDetails={currentProjectDetails ?? undefined} + isEpic={storeType === EIssuesStoreType.EPIC} /> = observer((p {/* progress burndown chart */}
-
-
- - {t("ideal")} -
-
- - {t("current")} -
-
{moduleStartDate && moduleEndDate && completionChartDistributionData && ( {plotType === "points" ? ( ) : ( diff --git a/web/core/components/page-views/index.ts b/web/core/components/page-views/index.ts deleted file mode 100644 index 0491e8caf53..00000000000 --- a/web/core/components/page-views/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./workspace-dashboard"; diff --git a/web/core/components/page-views/workspace-dashboard.tsx b/web/core/components/page-views/workspace-dashboard.tsx deleted file mode 100644 index 19d3590b07f..00000000000 --- a/web/core/components/page-views/workspace-dashboard.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// plane imports -import { EUserPermissionsLevel, PRODUCT_TOUR_COMPLETED, EUserPermissions } from "@plane/constants"; -import { useTranslation } from "@plane/i18n"; -import { ContentWrapper } from "@plane/ui"; -// components -import { DashboardWidgets } from "@/components/dashboard"; -import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state"; -import { IssuePeekOverview } from "@/components/issues"; -import { TourRoot } from "@/components/onboarding"; -import { UserGreetingsView } from "@/components/user"; -// constants -// helpers -import { cn } from "@/helpers/common.helper"; -// hooks -import { - useCommandPalette, - useUserProfile, - useEventTracker, - useDashboard, - useProject, - useUser, - useUserPermissions, -} from "@/hooks/store"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; -import useSize from "@/hooks/use-window-size"; - -export const WorkspaceDashboardView = observer(() => { - // plane hooks - const { t } = useTranslation(); - // store hooks - const { captureEvent, setTrackElement } = useEventTracker(); - const { toggleCreateProjectModal } = useCommandPalette(); - const { workspaceSlug } = useParams(); - const { data: currentUser } = useUser(); - const { data: currentUserProfile, updateTourCompleted } = useUserProfile(); - const { homeDashboardId, fetchHomeDashboardWidgets } = useDashboard(); - const { joinedProjectIds, loader } = useProject(); - const { allowPermissions } = useUserPermissions(); - - // helper hooks - const [windowWidth] = useSize(); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/dashboard" }); - - const handleTourCompleted = () => { - updateTourCompleted() - .then(() => { - captureEvent(PRODUCT_TOUR_COMPLETED, { - user_id: currentUser?.id, - state: "SUCCESS", - }); - }) - .catch((error) => { - console.error(error); - }); - }; - - // fetch home dashboard widgets on workspace change - useEffect(() => { - if (!workspaceSlug) return; - - fetchHomeDashboardWidgets(workspaceSlug?.toString()); - }, [fetchHomeDashboardWidgets, workspaceSlug]); - - const canPerformEmptyStateActions = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER], - EUserPermissionsLevel.WORKSPACE - ); - - // TODO: refactor loader implementation - return ( - <> - {currentUserProfile && !currentUserProfile.is_tour_completed && ( -
- -
- )} - {homeDashboardId && joinedProjectIds && ( - <> - {joinedProjectIds.length > 0 || loader === "init-loader" ? ( - <> - - = 768, - })} - > - {currentUser && } - - - - - ) : ( - { - setTrackElement("Dashboard empty state"); - toggleCreateProjectModal(true); - }} - disabled={!canPerformEmptyStateActions} - /> - } - /> - )} - - )} - - ); -}); diff --git a/web/core/components/profile/overview/priority-distribution/index.ts b/web/core/components/profile/overview/priority-distribution/index.ts deleted file mode 100644 index 64d81eb1244..00000000000 --- a/web/core/components/profile/overview/priority-distribution/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./priority-distribution"; diff --git a/web/core/components/profile/overview/priority-distribution/main-content.tsx b/web/core/components/profile/overview/priority-distribution/main-content.tsx deleted file mode 100644 index 5a4f64bcebd..00000000000 --- a/web/core/components/profile/overview/priority-distribution/main-content.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// components -import { IUserPriorityDistribution } from "@plane/types"; -import { IssuesByPriorityGraph } from "@/components/graphs"; -import { ProfileEmptyState } from "@/components/ui"; -// assets -import emptyBarGraph from "@/public/empty-state/empty_bar_graph.svg"; -// types - -type Props = { - priorityDistribution: IUserPriorityDistribution[]; -}; - -export const PriorityDistributionContent: React.FC = (props) => { - const { priorityDistribution } = props; - - return ( -
- {priorityDistribution.length > 0 ? ( - - ) : ( -
- -
- )} -
- ); -}; diff --git a/web/core/components/profile/overview/priority-distribution/priority-distribution.tsx b/web/core/components/profile/overview/priority-distribution/priority-distribution.tsx deleted file mode 100644 index a7bca192bcc..00000000000 --- a/web/core/components/profile/overview/priority-distribution/priority-distribution.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -// components -// ui -import { IUserPriorityDistribution } from "@plane/types"; -import { Loader } from "@plane/ui"; -// types -import { PriorityDistributionContent } from "./main-content"; - -type Props = { - priorityDistribution: IUserPriorityDistribution[] | undefined; -}; - -export const ProfilePriorityDistribution: React.FC = (props) => { - const { priorityDistribution } = props; - - return ( -
-

Work items by priority

- {priorityDistribution ? ( - - ) : ( -
- - - - - - - -
- )} -
- ); -}; diff --git a/web/core/components/ui/graphs/bar-graph.tsx b/web/core/components/ui/graphs/bar-graph.tsx deleted file mode 100644 index 1a724322e37..00000000000 --- a/web/core/components/ui/graphs/bar-graph.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// nivo -import { ResponsiveBar, BarSvgProps } from "@nivo/bar"; -import { CHARTS_THEME, CHART_DEFAULT_MARGIN } from "@plane/constants"; -// helpers -import { generateYAxisTickValues } from "@/helpers/graph.helper"; -// types -import { TGraph } from "./types"; -// constants - -type Props = { - indexBy: string; - keys: string[]; - customYAxisTickValues?: number[]; -}; - -export const BarGraph: React.FC, "height" | "width">> = ({ - indexBy, - keys, - customYAxisTickValues, - height = "400px", - width = "100%", - margin, - theme, - ...rest -}) => ( -
- 7) ? 0.8 : 0.9} - axisLeft={{ - tickSize: 0, - tickPadding: 10, - tickValues: customYAxisTickValues ? generateYAxisTickValues(customYAxisTickValues) : undefined, - }} - axisBottom={{ - tickSize: 0, - tickPadding: 10, - tickRotation: rest.data.length > 7 ? -45 : 0, - }} - labelTextColor={{ from: "color", modifiers: [["darker", 1.6]] }} - theme={{ ...CHARTS_THEME, ...(theme ?? {}) }} - animate - enableLabel={rest.enableLabel ?? false} - {...rest} - /> -
-); diff --git a/web/core/components/ui/graphs/calendar-graph.tsx b/web/core/components/ui/graphs/calendar-graph.tsx deleted file mode 100644 index 5a63dd640da..00000000000 --- a/web/core/components/ui/graphs/calendar-graph.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// nivo -import { ResponsiveCalendar, CalendarSvgProps } from "@nivo/calendar"; -// types -import { CHARTS_THEME, CHART_DEFAULT_MARGIN } from "@plane/constants"; -import { TGraph } from "./types"; -// constants - -export const CalendarGraph: React.FC> = ({ - height = "400px", - width = "100%", - margin, - theme, - ...rest -}) => ( -
- -
-); diff --git a/web/core/components/ui/graphs/index.ts b/web/core/components/ui/graphs/index.ts deleted file mode 100644 index 984bb642cf2..00000000000 --- a/web/core/components/ui/graphs/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./bar-graph"; -export * from "./calendar-graph"; -export * from "./line-graph"; -export * from "./pie-graph"; -export * from "./scatter-plot-graph"; diff --git a/web/core/components/ui/graphs/line-graph.tsx b/web/core/components/ui/graphs/line-graph.tsx deleted file mode 100644 index e04d7ea6c43..00000000000 --- a/web/core/components/ui/graphs/line-graph.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// nivo -import { ResponsiveLine, LineSvgProps } from "@nivo/line"; -// helpers -import { CHARTS_THEME, CHART_DEFAULT_MARGIN } from "@plane/constants"; -import { generateYAxisTickValues } from "@/helpers/graph.helper"; -// types -import { TGraph } from "./types"; -// constants - -type Props = { - customYAxisTickValues?: number[]; -}; - -export const LineGraph: React.FC = ({ - customYAxisTickValues, - height = "400px", - width = "100%", - margin, - theme, - ...rest -}) => ( -
- -
-); diff --git a/web/core/components/ui/graphs/pie-graph.tsx b/web/core/components/ui/graphs/pie-graph.tsx deleted file mode 100644 index 64585974164..00000000000 --- a/web/core/components/ui/graphs/pie-graph.tsx +++ /dev/null @@ -1,23 +0,0 @@ -// nivo -import { PieSvgProps, ResponsivePie } from "@nivo/pie"; -// types -import { CHARTS_THEME, CHART_DEFAULT_MARGIN } from "@plane/constants"; -import { TGraph } from "./types"; -// constants - -export const PieGraph: React.FC, "height" | "width">> = ({ - height = "400px", - width = "100%", - margin, - theme, - ...rest -}) => ( -
- -
-); diff --git a/web/core/components/ui/graphs/scatter-plot-graph.tsx b/web/core/components/ui/graphs/scatter-plot-graph.tsx deleted file mode 100644 index e4a7d7fcc61..00000000000 --- a/web/core/components/ui/graphs/scatter-plot-graph.tsx +++ /dev/null @@ -1,23 +0,0 @@ -// nivo -import { ResponsiveScatterPlot, ScatterPlotSvgProps } from "@nivo/scatterplot"; -// types -import { CHARTS_THEME, CHART_DEFAULT_MARGIN } from "@plane/constants"; -import { TGraph } from "./types"; -// constants - -export const ScatterPlotGraph: React.FC, "height" | "width">> = ({ - height = "400px", - width = "100%", - margin, - theme, - ...rest -}) => ( -
- -
-); diff --git a/web/core/components/ui/graphs/types.d.ts b/web/core/components/ui/graphs/types.d.ts deleted file mode 100644 index c8646405cf0..00000000000 --- a/web/core/components/ui/graphs/types.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Theme, Margin } from "@nivo/core"; - -export type TGraph = { - height?: string; - width?: string; - margin?: Partial; - theme?: Theme; -}; diff --git a/web/core/components/ui/index.ts b/web/core/components/ui/index.ts index e441b896f25..35367be7ece 100644 --- a/web/core/components/ui/index.ts +++ b/web/core/components/ui/index.ts @@ -1,4 +1,3 @@ -export * from "./graphs"; export * from "./empty-space"; export * from "./labels-list"; export * from "./markdown-to-component"; diff --git a/web/core/constants/fetch-keys.ts b/web/core/constants/fetch-keys.ts index ec5de760f6f..d5bbf3ed65c 100644 --- a/web/core/constants/fetch-keys.ts +++ b/web/core/constants/fetch-keys.ts @@ -1,4 +1,4 @@ -import { IAnalyticsParams, IJiraMetadata } from "@plane/types"; +import { IJiraMetadata } from "@plane/types"; const paramsToKey = (params: any) => { const { @@ -237,14 +237,6 @@ export const MY_PAGES_LIST = (pageId: string) => `MY_PAGE_LIST_${pageId}`; export const ESTIMATES_LIST = (projectId: string) => `ESTIMATES_LIST_${projectId.toUpperCase()}`; export const ESTIMATE_DETAILS = (estimateId: string) => `ESTIMATE_DETAILS_${estimateId.toUpperCase()}`; -// analytics -export const ANALYTICS = (workspaceSlug: string, params: IAnalyticsParams) => - `ANALYTICS${workspaceSlug.toUpperCase()}_${params.x_axis}_${params.y_axis}_${ - params.segment - }_${params.project?.toString()}`; -export const DEFAULT_ANALYTICS = (workspaceSlug: string, params?: Partial) => - `DEFAULT_ANALYTICS_${workspaceSlug.toUpperCase()}_${params?.project?.toString()}_${params?.cycle}_${params?.module}`; - // profile export const USER_PROFILE_DATA = (workspaceSlug: string, userId: string) => `USER_PROFILE_ACTIVITY_${workspaceSlug.toUpperCase()}_${userId.toUpperCase()}`; diff --git a/web/core/hooks/store/index.ts b/web/core/hooks/store/index.ts index 6a1b91e5d0f..dfc104542a1 100644 --- a/web/core/hooks/store/index.ts +++ b/web/core/hooks/store/index.ts @@ -31,4 +31,4 @@ export * from "./use-workspace"; export * from "./user"; export * from "./use-transient"; export * from "./workspace-draft"; -export * from "./use-analytics-v2"; +export * from "./use-analytics"; diff --git a/web/core/hooks/store/use-analytics-v2.ts b/web/core/hooks/store/use-analytics-v2.ts deleted file mode 100644 index c8c13ba6130..00000000000 --- a/web/core/hooks/store/use-analytics-v2.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useContext } from "react"; -// mobx store -import { StoreContext } from "@/lib/store-context"; -// types -import { IAnalyticsStoreV2 } from "@/store/analytics-v2.store"; - -export const useAnalyticsV2 = (): IAnalyticsStoreV2 => { - const context = useContext(StoreContext); - if (context === undefined) throw new Error("useAnalyticsV2 must be used within StoreProvider"); - return context.analyticsV2; -}; diff --git a/web/core/hooks/store/use-analytics.ts b/web/core/hooks/store/use-analytics.ts new file mode 100644 index 00000000000..a07af60ed7a --- /dev/null +++ b/web/core/hooks/store/use-analytics.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// mobx store +import { StoreContext } from "@/lib/store-context"; +// types +import { IAnalyticsStore } from "@/plane-web/store/analytics.store"; + +export const useAnalytics = (): IAnalyticsStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("useAnalytics must be used within StoreProvider"); + return context.analytics; +}; diff --git a/web/core/services/analytics-v2.service.ts b/web/core/services/analytics-v2.service.ts deleted file mode 100644 index 05e1b78b728..00000000000 --- a/web/core/services/analytics-v2.service.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { API_BASE_URL } from "@plane/constants"; -import { IAnalyticsResponseV2, TAnalyticsTabsV2Base, TAnalyticsGraphsV2Base } from "@plane/types"; -import { APIService } from "./api.service"; - -export class AnalyticsV2Service extends APIService { - constructor() { - super(API_BASE_URL); - } - - async getAdvanceAnalytics( - workspaceSlug: string, - tab: TAnalyticsTabsV2Base, - params?: Record, - isPeekView?: boolean - ): Promise { - return this.get( - this.processUrl("advance-analytics", workspaceSlug, tab, params, isPeekView), - { - params: { - tab, - ...params, - }, - } - ) - .then((res) => res?.data) - .catch((err) => { - throw err?.response?.data; - }); - } - - async getAdvanceAnalyticsStats( - workspaceSlug: string, - tab: Exclude, - params?: Record, - isPeekView?: boolean - ): Promise { - const processedUrl = this.processUrl>( - "advance-analytics-stats", - workspaceSlug, - tab, - params, - isPeekView - ); - return this.get(processedUrl, { - params: { - type: tab, - ...params, - }, - }) - .then((res) => res?.data) - .catch((err) => { - throw err?.response?.data; - }); - } - - async getAdvanceAnalyticsCharts( - workspaceSlug: string, - tab: TAnalyticsGraphsV2Base, - params?: Record, - isPeekView?: boolean - ): Promise { - const processedUrl = this.processUrl( - "advance-analytics-charts", - workspaceSlug, - tab, - params, - isPeekView - ); - return this.get(processedUrl, { - params: { - type: tab, - ...params, - }, - }) - .then((res) => res?.data) - .catch((err) => { - throw err?.response?.data; - }); - } - - processUrl( - endpoint: string, - workspaceSlug: string, - tab: T, - params?: Record, - isPeekView?: boolean - ) { - let processedUrl = `/api/workspaces/${workspaceSlug}`; - if (isPeekView && tab === "work-items") { - const projectId = params?.project_ids.split(",")[0]; - processedUrl += `/projects/${projectId}`; - } - return `${processedUrl}/${endpoint}`; - } -} diff --git a/web/core/services/analytics.service.ts b/web/core/services/analytics.service.ts index 991407d1656..de8e489d3cb 100644 --- a/web/core/services/analytics.service.ts +++ b/web/core/services/analytics.service.ts @@ -1,63 +1,99 @@ -// services -import { - IAnalyticsParams, - IAnalyticsResponse, - IDefaultAnalyticsResponse, - IExportAnalyticsFormData, - ISaveAnalyticsFormData, -} from "@plane/types"; -import { API_BASE_URL } from "@/helpers/common.helper"; -import { APIService } from "@/services/api.service"; -// types -// helpers +import { API_BASE_URL } from "@plane/constants"; +import { IAnalyticsResponse, TAnalyticsTabsBase, TAnalyticsGraphsBase, TAnalyticsFilterParams } from "@plane/types"; +import { APIService } from "./api.service"; export class AnalyticsService extends APIService { constructor() { super(API_BASE_URL); } - async getAnalytics(workspaceSlug: string, params: IAnalyticsParams): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/analytics/`, { + async getAdvanceAnalytics( + workspaceSlug: string, + tab: TAnalyticsTabsBase, + params?: TAnalyticsFilterParams, + isPeekView?: boolean + ): Promise { + return this.get(this.processUrl("advance-analytics", workspaceSlug, tab, params, isPeekView), { params: { + tab, ...params, - project: params?.project ? params.project.toString() : null, }, }) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; }); } - async getDefaultAnalytics( + async getAdvanceAnalyticsStats( workspaceSlug: string, - params?: Partial - ): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/default-analytics/`, { + tab: Exclude, + params?: TAnalyticsFilterParams, + isPeekView?: boolean + ): Promise { + const processedUrl = this.processUrl>( + "advance-analytics-stats", + workspaceSlug, + tab, + params, + isPeekView + ); + return this.get(processedUrl, { params: { + type: tab, ...params, - project: params?.project ? params.project.toString() : null, }, }) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; }); } - async saveAnalytics(workspaceSlug: string, data: ISaveAnalyticsFormData): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/analytic-view/`, data) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; + async getAdvanceAnalyticsCharts( + workspaceSlug: string, + tab: TAnalyticsGraphsBase, + params?: TAnalyticsFilterParams, + isPeekView?: boolean + ): Promise { + const processedUrl = this.processUrl( + "advance-analytics-charts", + workspaceSlug, + tab, + params, + isPeekView + ); + return this.get(processedUrl, { + params: { + type: tab, + ...params, + }, + }) + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; }); } - async exportAnalytics(workspaceSlug: string, data: IExportAnalyticsFormData): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/export-analytics/`, data) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); + processUrl( + endpoint: string, + workspaceSlug: string, + tab: TAnalyticsGraphsBase | TAnalyticsTabsBase, + params?: TAnalyticsFilterParams, + isPeekView?: boolean + ) { + let processedUrl = `/api/workspaces/${workspaceSlug}`; + if (isPeekView && (tab === "work-items" || tab === "custom-work-items")) { + const projectIds = params?.project_ids; + if (typeof projectIds !== "string" || !projectIds.trim()) { + throw new Error("project_ids parameter is required for peek view of work items"); + } + const projectId = projectIds.split(",")[0]; + if (!projectId) { + throw new Error("Invalid project_ids format - no project ID found"); + } + processedUrl += `/projects/${projectId}`; + } + return `${processedUrl}/${endpoint}`; } } diff --git a/web/core/store/analytics-v2.store.ts b/web/core/store/analytics.store.ts similarity index 73% rename from web/core/store/analytics-v2.store.ts rename to web/core/store/analytics.store.ts index 97582577ace..a78c4971089 100644 --- a/web/core/store/analytics-v2.store.ts +++ b/web/core/store/analytics.store.ts @@ -1,19 +1,19 @@ import { action, computed, makeObservable, observable, runInAction } from "mobx"; -import { ANALYTICS_V2_DURATION_FILTER_OPTIONS } from "@plane/constants"; -import { TAnalyticsTabsV2Base } from "@plane/types"; +import { ANALYTICS_DURATION_FILTER_OPTIONS, EIssuesStoreType } from "@plane/constants"; +import { TAnalyticsTabsBase } from "@plane/types"; import { CoreRootStore } from "./root.store"; -type DurationType = (typeof ANALYTICS_V2_DURATION_FILTER_OPTIONS)[number]["value"]; +type DurationType = (typeof ANALYTICS_DURATION_FILTER_OPTIONS)[number]["value"]; -export interface IAnalyticsStoreV2 { +export interface IBaseAnalyticsStore { //observables - currentTab: TAnalyticsTabsV2Base; + currentTab: TAnalyticsTabsBase; selectedProjects: string[]; selectedDuration: DurationType; selectedCycle: string; selectedModule: string; isPeekView?: boolean; - + isEpic?: boolean; //computed selectedDurationLabel: DurationType | null; @@ -23,25 +23,28 @@ export interface IAnalyticsStoreV2 { updateSelectedCycle: (cycle: string) => void; updateSelectedModule: (module: string) => void; updateIsPeekView: (isPeekView: boolean) => void; + updateIsEpic: (isEpic: boolean) => void; } -export class AnalyticsStoreV2 implements IAnalyticsStoreV2 { +export abstract class BaseAnalyticsStore implements IBaseAnalyticsStore { //observables - currentTab: TAnalyticsTabsV2Base = "overview"; + currentTab: TAnalyticsTabsBase = "overview"; selectedProjects: string[] = []; selectedDuration: DurationType = "last_30_days"; selectedCycle: string = ""; selectedModule: string = ""; isPeekView: boolean = false; + isEpic: boolean = false; constructor() { makeObservable(this, { // observables currentTab: observable.ref, selectedDuration: observable.ref, - selectedProjects: observable.ref, + selectedProjects: observable, selectedCycle: observable.ref, selectedModule: observable.ref, isPeekView: observable.ref, + isEpic: observable.ref, // computed selectedDurationLabel: computed, // actions @@ -50,11 +53,12 @@ export class AnalyticsStoreV2 implements IAnalyticsStoreV2 { updateSelectedCycle: action, updateSelectedModule: action, updateIsPeekView: action, + updateIsEpic: action, }); } get selectedDurationLabel() { - return ANALYTICS_V2_DURATION_FILTER_OPTIONS.find((item) => item.value === this.selectedDuration)?.name ?? null; + return ANALYTICS_DURATION_FILTER_OPTIONS.find((item) => item.value === this.selectedDuration)?.name ?? null; } updateSelectedProjects = (projects: string[]) => { @@ -96,4 +100,10 @@ export class AnalyticsStoreV2 implements IAnalyticsStoreV2 { this.isPeekView = isPeekView; }); }; + + updateIsEpic = (isEpic: boolean) => { + runInAction(() => { + this.isEpic = isEpic; + }); + }; } diff --git a/web/core/store/root.store.ts b/web/core/store/root.store.ts index b3e93afc9a6..e210754cc9b 100644 --- a/web/core/store/root.store.ts +++ b/web/core/store/root.store.ts @@ -2,11 +2,11 @@ import { enableStaticRendering } from "mobx-react"; // plane imports import { FALLBACK_LANGUAGE, LANGUAGE_STORAGE_KEY } from "@plane/i18n"; // plane web store +import { AnalyticsStore, IAnalyticsStore } from "@/plane-web/store/analytics.store"; import { CommandPaletteStore, ICommandPaletteStore } from "@/plane-web/store/command-palette.store"; import { RootStore } from "@/plane-web/store/root.store"; import { IStateStore, StateStore } from "@/plane-web/store/state.store"; // stores -import { IAnalyticsStoreV2, AnalyticsStoreV2 } from "./analytics-v2.store"; import { CycleStore, ICycleStore } from "./cycle.store"; import { CycleFilterStore, ICycleFilterStore } from "./cycle_filter.store"; import { DashboardStore, IDashboardStore } from "./dashboard.store"; @@ -50,7 +50,7 @@ export class CoreRootStore { state: IStateStore; label: ILabelStore; dashboard: IDashboardStore; - analyticsV2: IAnalyticsStoreV2; + analytics: IAnalyticsStore; projectPages: IProjectPageStore; router: IRouterStore; commandPalette: ICommandPaletteStore; @@ -96,7 +96,7 @@ export class CoreRootStore { this.transient = new TransientStore(); this.stickyStore = new StickyStore(); this.editorAssetStore = new EditorAssetStore(); - this.analyticsV2 = new AnalyticsStoreV2(); + this.analytics = new AnalyticsStore(); } resetOnSignOut() { diff --git a/web/ee/store/analytics.store.ts b/web/ee/store/analytics.store.ts new file mode 100644 index 00000000000..ef866f65a78 --- /dev/null +++ b/web/ee/store/analytics.store.ts @@ -0,0 +1,8 @@ +import { BaseAnalyticsStore, IBaseAnalyticsStore } from "@/store/analytics.store"; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface IAnalyticsStore extends IBaseAnalyticsStore { + //observables +} + +export class AnalyticsStore extends BaseAnalyticsStore {} diff --git a/web/helpers/analytics.helper.ts b/web/helpers/analytics.helper.ts deleted file mode 100644 index cee4b40f352..00000000000 --- a/web/helpers/analytics.helper.ts +++ /dev/null @@ -1,152 +0,0 @@ -// nivo -import { BarDatum } from "@nivo/bar"; -// plane imports -import { ANALYTICS_DATE_KEYS, STATE_GROUPS } from "@plane/constants"; -import { IAnalyticsData, IAnalyticsParams, IAnalyticsResponse, TStateGroups } from "@plane/types"; -// constants -import { MONTHS_LIST } from "@/constants/calendar"; -// helpers -import { addSpaceIfCamelCase, capitalizeFirstLetter, generateRandomColor } from "@/helpers/string.helper"; - -export const convertResponseToBarGraphData = ( - response: IAnalyticsData | undefined, - params: IAnalyticsParams -): { data: BarDatum[]; xAxisKeys: string[] } => { - if (!response || !(typeof response === "object") || Object.keys(response).length === 0) - return { data: [], xAxisKeys: [] }; - - const data: BarDatum[] = []; - - let xAxisKeys: string[] = []; - const yAxisKey = params.y_axis === "issue_count" ? "count" : "estimate"; - - Object.keys(response).forEach((key) => { - const segments: { [key: string]: number } = {}; - - if (params.segment) { - response[key].map((item: any) => { - segments[item.segment ?? "None"] = item[yAxisKey] ?? 0; - - // store the segment in the xAxisKeys array - if (!xAxisKeys.includes(item.segment ?? "None")) xAxisKeys.push(item.segment ?? "None"); - }); - - data.push({ - name: ANALYTICS_DATE_KEYS.includes(params.x_axis) - ? renderMonthAndYear(key) - : params.x_axis === "priority" || params.x_axis === "state__group" - ? capitalizeFirstLetter(key) - : key, - ...segments, - }); - } else { - xAxisKeys = [yAxisKey]; - - const item = response[key][0]; - - data.push({ - name: ANALYTICS_DATE_KEYS.includes(params.x_axis) - ? renderMonthAndYear(item.dimension) - : params.x_axis === "priority" || params.x_axis === "state__group" - ? capitalizeFirstLetter(item.dimension ?? "None") - : (item.dimension ?? "None"), - [yAxisKey]: item[yAxisKey] ?? 0, - }); - } - }); - - return { data, xAxisKeys }; -}; - -export const generateBarColor = ( - value: string, - analytics: IAnalyticsResponse, - params: IAnalyticsParams, - type: "x_axis" | "segment" -): string => { - let color: string | undefined = generateRandomColor(value); - - if (!analytics) return color; - - if (params[type] === "state_id") - color = analytics?.extras.state_details.find((s) => s.state_id === value)?.state__color; - - if (params[type] === "labels__id") - color = analytics?.extras.label_details.find((l) => l.labels__id === value)?.labels__color ?? undefined; - - if (params[type] === "state__group") color = STATE_GROUPS[value.toLowerCase() as TStateGroups]?.color ?? undefined; - - if (params[type] === "priority") { - const priority = value.toLowerCase(); - - color = - priority === "urgent" - ? "#ef4444" - : priority === "high" - ? "#f97316" - : priority === "medium" - ? "#eab308" - : priority === "low" - ? "#22c55e" - : "#ced4da"; - } - - return color ?? generateRandomColor(value); -}; - -export const generateDisplayName = ( - value: string, - analytics: IAnalyticsResponse, - params: IAnalyticsParams, - type: "x_axis" | "segment" -): string => { - let displayName = addSpaceIfCamelCase(value); - - if (!analytics) return displayName; - - if (params[type] === "assignees__id") - displayName = - analytics?.extras.assignee_details.find((a) => a.assignees__id === value)?.assignees__display_name ?? - "No assignee"; - - if (params[type] === "issue_cycle__cycle_id") - displayName = - analytics?.extras.cycle_details.find((c) => c.issue_cycle__cycle_id === value)?.issue_cycle__cycle__name ?? - "None"; - - if (params[type] === "issue_module__module_id") - displayName = - analytics?.extras.module_details.find((m) => m.issue_module__module_id === value)?.issue_module__module__name ?? - "None"; - - if (params[type] === "labels__id") - displayName = analytics?.extras.label_details.find((l) => l.labels__id === value)?.labels__name ?? "None"; - - if (params[type] === "state_id") - displayName = analytics?.extras.state_details.find((s) => s.state_id === value)?.state__name ?? "None"; - - if (ANALYTICS_DATE_KEYS.includes(params.segment ?? "")) displayName = renderMonthAndYear(value); - - return displayName; -}; - -export const renderMonthAndYear = (date: string | number | null): string => { - if (!date || date === "") return ""; - - const monthNumber = parseInt(`${date}`.split("-")[1], 10); - const year = `${date}`.split("-")[0]; - - return (MONTHS_LIST[monthNumber]?.shortTitle || "None") + ` ${year ? year : ""}`; -}; - -export const MAX_CHART_LABEL_LENGTH = 15; -export const renderChartDynamicLabel = ( - label: string, - length: number = MAX_CHART_LABEL_LENGTH -): { label: string; length: number } => { - const currentLabel = label.substring(0, length); - return { - label: `${label.length > MAX_CHART_LABEL_LENGTH ? `${currentLabel.substring(0, MAX_CHART_LABEL_LENGTH - 3)}...` : currentLabel}`, - length: currentLabel.length, - }; -}; diff --git a/web/next.config.js b/web/next.config.js index b7195885463..41ed049c558 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -24,12 +24,6 @@ const nextConfig = { "lucide-react", "date-fns", "@headlessui/react", - "@nivo/core", - "@nivo/bar", - "@nivo/line", - "@nivo/pie", - "@nivo/calendar", - "@nivo/scatterplot", "react-color", "react-day-picker", "react-dropzone", diff --git a/web/package.json b/web/package.json index 7eb12c8ac87..6312f9f903b 100644 --- a/web/package.json +++ b/web/package.json @@ -22,13 +22,6 @@ "@blueprintjs/popover2": "^1.13.3", "@headlessui/react": "^1.7.3", "@intercom/messenger-js-sdk": "^0.0.12", - "@nivo/bar": "^0.88.0", - "@nivo/calendar": "^0.88.0", - "@nivo/core": "^0.88.0", - "@nivo/legends": "^0.88.0", - "@nivo/line": "^0.88.0", - "@nivo/pie": "^0.88.0", - "@nivo/scatterplot": "^0.88.0", "@plane/constants": "*", "@plane/editor": "*", "@plane/hooks": "*", diff --git a/web/public/empty-state/analytics-v2/empty-chart-area-dark.webp b/web/public/empty-state/analytics/empty-chart-area-dark.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-chart-area-dark.webp rename to web/public/empty-state/analytics/empty-chart-area-dark.webp diff --git a/web/public/empty-state/analytics-v2/empty-chart-area-light.webp b/web/public/empty-state/analytics/empty-chart-area-light.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-chart-area-light.webp rename to web/public/empty-state/analytics/empty-chart-area-light.webp diff --git a/web/public/empty-state/analytics-v2/empty-chart-bar-dark.webp b/web/public/empty-state/analytics/empty-chart-bar-dark.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-chart-bar-dark.webp rename to web/public/empty-state/analytics/empty-chart-bar-dark.webp diff --git a/web/public/empty-state/analytics-v2/empty-chart-bar-light.webp b/web/public/empty-state/analytics/empty-chart-bar-light.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-chart-bar-light.webp rename to web/public/empty-state/analytics/empty-chart-bar-light.webp diff --git a/web/public/empty-state/analytics-v2/empty-chart-radar-dark.webp b/web/public/empty-state/analytics/empty-chart-radar-dark.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-chart-radar-dark.webp rename to web/public/empty-state/analytics/empty-chart-radar-dark.webp diff --git a/web/public/empty-state/analytics-v2/empty-chart-radar-light.webp b/web/public/empty-state/analytics/empty-chart-radar-light.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-chart-radar-light.webp rename to web/public/empty-state/analytics/empty-chart-radar-light.webp diff --git a/web/public/empty-state/analytics-v2/empty-grid-background-dark.webp b/web/public/empty-state/analytics/empty-grid-background-dark.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-grid-background-dark.webp rename to web/public/empty-state/analytics/empty-grid-background-dark.webp diff --git a/web/public/empty-state/analytics-v2/empty-grid-background-light.webp b/web/public/empty-state/analytics/empty-grid-background-light.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-grid-background-light.webp rename to web/public/empty-state/analytics/empty-grid-background-light.webp diff --git a/web/public/empty-state/analytics-v2/empty-table-dark.webp b/web/public/empty-state/analytics/empty-table-dark.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-table-dark.webp rename to web/public/empty-state/analytics/empty-table-dark.webp diff --git a/web/public/empty-state/analytics-v2/empty-table-light.webp b/web/public/empty-state/analytics/empty-table-light.webp similarity index 100% rename from web/public/empty-state/analytics-v2/empty-table-light.webp rename to web/public/empty-state/analytics/empty-table-light.webp diff --git a/yarn.lock b/yarn.lock index ea131bb2b9b..29dd63adac8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1675,201 +1675,6 @@ resolved "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ== -"@nivo/annotations@0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.88.0.tgz#96830d735331dea2b60e66ce3971ef43b45cca8c" - integrity sha512-NXE+1oIUn+EGWMQpnpeRMLgi2wyuzhGDoJQY4OUHissCUiNotid2oNQ/PXJwN0toiu+/j9SyhzI32xr70OPi7Q== - dependencies: - "@nivo/colors" "0.88.0" - "@nivo/core" "0.88.0" - "@react-spring/web" "9.4.5 || ^9.7.2" - lodash "^4.17.21" - -"@nivo/arcs@0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/arcs/-/arcs-0.88.0.tgz#0004aa612fd12eee5fd415ed257812b722bf5a10" - integrity sha512-q7MHxT71s/KKlDDtSJS4L9+/JIa5HPZZrDr3ZFECLnvp0TC1qzyFMtVevN2CsXopSTj8poN4uFXPWxYVXOq8vg== - dependencies: - "@nivo/colors" "0.88.0" - "@nivo/core" "0.88.0" - "@react-spring/web" "9.4.5 || ^9.7.2" - "@types/d3-shape" "^3.1.6" - d3-shape "^3.2.0" - -"@nivo/axes@0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/axes/-/axes-0.88.0.tgz#b773efb217fc0faedbb5f8bc458f4b78820e2761" - integrity sha512-jF7aIxzTNayV5cI1J/b9Q1FfpMBxTXGk3OwSigXMSfYWlliskDn2u0qGRLiYhuXFdQAWIp4oXsO1GcAQ0eRVdg== - dependencies: - "@nivo/core" "0.88.0" - "@nivo/scales" "0.88.0" - "@react-spring/web" "9.4.5 || ^9.7.2" - "@types/d3-format" "^1.4.1" - "@types/d3-time-format" "^2.3.1" - d3-format "^1.4.4" - d3-time-format "^3.0.0" - -"@nivo/bar@^0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/bar/-/bar-0.88.0.tgz#646f12233c0dc9f2507823bfe30a2f1d76ddae71" - integrity sha512-wckwuHWeCikxGvvdRfGL+dVFsUD9uHk1r9s7bWUfOD+p8BWhxtYqfXpHolEfgGg3UyPaHtpGA7P4zgE5vgo7gQ== - dependencies: - "@nivo/annotations" "0.88.0" - "@nivo/axes" "0.88.0" - "@nivo/colors" "0.88.0" - "@nivo/core" "0.88.0" - "@nivo/legends" "0.88.0" - "@nivo/scales" "0.88.0" - "@nivo/tooltip" "0.88.0" - "@react-spring/web" "9.4.5 || ^9.7.2" - "@types/d3-scale" "^4.0.8" - "@types/d3-shape" "^3.1.6" - d3-scale "^4.0.2" - d3-shape "^3.2.0" - lodash "^4.17.21" - -"@nivo/calendar@^0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/calendar/-/calendar-0.88.0.tgz#f09e8ab5332d8f9b18de87e95ca6a48013ac0606" - integrity sha512-sTpoaN5bNRwywRIVKAv7oo+/ZZjX0cjBcpbyFQZqXnEmFX8tEO55Rn/469zWDG776Gk7wHcuwmQfEIqwWM9PfQ== - dependencies: - "@nivo/core" "0.88.0" - "@nivo/legends" "0.88.0" - "@nivo/tooltip" "0.88.0" - "@types/d3-scale" "^4.0.8" - "@types/d3-time" "^1.0.10" - "@types/d3-time-format" "^3.0.0" - d3-scale "^4.0.2" - d3-time "^1.0.10" - d3-time-format "^3.0.0" - lodash "^4.17.21" - -"@nivo/colors@0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/colors/-/colors-0.88.0.tgz#2790ac0607381800270f2902e4d957e65e2cad70" - integrity sha512-IZ+leYIqAlo7dyLHmsQwujanfRgXyoQ5H7PU3RWLEn1PP0zxDKLgEjFEDADpDauuslh2Tx0L81GNkWR6QSP0Mw== - dependencies: - "@nivo/core" "0.88.0" - "@types/d3-color" "^3.0.0" - "@types/d3-scale" "^4.0.8" - "@types/d3-scale-chromatic" "^3.0.0" - "@types/prop-types" "^15.7.2" - d3-color "^3.1.0" - d3-scale "^4.0.2" - d3-scale-chromatic "^3.0.0" - lodash "^4.17.21" - prop-types "^15.7.2" - -"@nivo/core@0.88.0", "@nivo/core@^0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/core/-/core-0.88.0.tgz#ec79c7d63311473f15a463fd78274d9971a52848" - integrity sha512-XjUkA5MmwjLP38bdrJwn36Gj7T5SYMKD55LYQp/1nIJPdxqJ38dUfE4XyBDfIEgfP6yrHOihw3C63cUdnUBoiw== - dependencies: - "@nivo/tooltip" "0.88.0" - "@react-spring/web" "9.4.5 || ^9.7.2" - "@types/d3-shape" "^3.1.6" - d3-color "^3.1.0" - d3-format "^1.4.4" - d3-interpolate "^3.0.1" - d3-scale "^4.0.2" - d3-scale-chromatic "^3.0.0" - d3-shape "^3.2.0" - d3-time-format "^3.0.0" - lodash "^4.17.21" - prop-types "^15.7.2" - -"@nivo/legends@0.88.0", "@nivo/legends@^0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/legends/-/legends-0.88.0.tgz#a6007492048358a14bd061472a117ed34e7b0c81" - integrity sha512-d4DF9pHbD8LmGJlp/Gp1cF4e8y2wfQTcw3jVhbZj9zkb7ZWB7JfeF60VHRfbXNux9bjQ9U78/SssQqueVDPEmg== - dependencies: - "@nivo/colors" "0.88.0" - "@nivo/core" "0.88.0" - "@types/d3-scale" "^4.0.8" - d3-scale "^4.0.2" - -"@nivo/line@^0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/line/-/line-0.88.0.tgz#145b194f2c1bff2dd6071ab46fe114f9c4237811" - integrity sha512-hFTyZ3BdAZvq2HwdwMj2SJGUeodjEW+7DLtFMIIoVIxmjZlAs3z533HcJ9cJd3it928fDm8SF/rgHs0TztYf9Q== - dependencies: - "@nivo/annotations" "0.88.0" - "@nivo/axes" "0.88.0" - "@nivo/colors" "0.88.0" - "@nivo/core" "0.88.0" - "@nivo/legends" "0.88.0" - "@nivo/scales" "0.88.0" - "@nivo/tooltip" "0.88.0" - "@nivo/voronoi" "0.88.0" - "@react-spring/web" "9.4.5 || ^9.7.2" - d3-shape "^3.2.0" - -"@nivo/pie@^0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/pie/-/pie-0.88.0.tgz#628a608d077d9cfe850ffef1b16181482479e7c7" - integrity sha512-BE6dFWlGne1SnaEkFHNbg0sZBiwtcIqBFwmMRJ0F11SiKOzVeJyq3KiyY1I2ySSCx5VR1V8/MNBXzXFu3vJMAQ== - dependencies: - "@nivo/arcs" "0.88.0" - "@nivo/colors" "0.88.0" - "@nivo/core" "0.88.0" - "@nivo/legends" "0.88.0" - "@nivo/tooltip" "0.88.0" - "@types/d3-shape" "^3.1.6" - d3-shape "^3.2.0" - -"@nivo/scales@0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/scales/-/scales-0.88.0.tgz#c69110b5ab504debb80a5a01a7824780af241ffb" - integrity sha512-HbpxkQp6tHCltZ1yDGeqdLcaJl5ze54NPjurfGtx/Uq+H5IQoBd4Tln49bUar5CsFAMsXw8yF1HQvASr7I1SIA== - dependencies: - "@types/d3-scale" "^4.0.8" - "@types/d3-time" "^1.1.1" - "@types/d3-time-format" "^3.0.0" - d3-scale "^4.0.2" - d3-time "^1.0.11" - d3-time-format "^3.0.0" - lodash "^4.17.21" - -"@nivo/scatterplot@^0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/scatterplot/-/scatterplot-0.88.0.tgz#d6572986eee49e2da942c01be37f6fe189bf6506" - integrity sha512-kVUnBknVnX/+kAnOVl27wSrVrDAnYt7hyttss5jydBMzGidxK7vFBwRUFl5yrPGmEwpDSO8POvXjSF4M3XHzZA== - dependencies: - "@nivo/annotations" "0.88.0" - "@nivo/axes" "0.88.0" - "@nivo/colors" "0.88.0" - "@nivo/core" "0.88.0" - "@nivo/legends" "0.88.0" - "@nivo/scales" "0.88.0" - "@nivo/tooltip" "0.88.0" - "@nivo/voronoi" "0.88.0" - "@react-spring/web" "9.4.5 || ^9.7.2" - "@types/d3-scale" "^4.0.8" - "@types/d3-shape" "^3.1.6" - d3-scale "^4.0.2" - d3-shape "^3.2.0" - lodash "^4.17.21" - -"@nivo/tooltip@0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.88.0.tgz#b98348c0d617fea09c1bbddccc443c12ca697620" - integrity sha512-iEjVfQA8gumAzg/yUinjTwswygCkE5Iwuo8opwnrbpNIqMrleBV+EAKIgB0PrzepIoW8CFG/SJhoiRfbU8jhOw== - dependencies: - "@nivo/core" "0.88.0" - "@react-spring/web" "9.4.5 || ^9.7.2" - -"@nivo/voronoi@0.88.0": - version "0.88.0" - resolved "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.88.0.tgz#029f984ccb6e72b415f398e1bd74c572960197ae" - integrity sha512-MyiNLvODthFoMjQ7Wjp693nogbTmVEx8Yn/7QkJhyPQbFyyA37TF/D1a/ox4h2OslXtP6K9QFN+42gB/zu7ixw== - dependencies: - "@nivo/core" "0.88.0" - "@nivo/tooltip" "0.88.0" - "@types/d3-delaunay" "^6.0.4" - "@types/d3-scale" "^4.0.8" - d3-delaunay "^6.0.4" - d3-scale "^4.0.2" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2186,51 +1991,6 @@ resolved "https://registry.npmjs.org/@react-pdf/types/-/types-2.7.1.tgz#eb9f70be66b42c47f60c5afcc0af044ac48b98bf" integrity sha512-MyjR1u+6SclQ/Tx6NP3/yoYZw7reXgC4OHFOrdMh/zeZ+ezfdGyovB+jdmVQuMe7Fsh64v7PUkO5tnsXHyCFWQ== -"@react-spring/animated@~9.7.5": - version "9.7.5" - resolved "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz#eb0373aaf99b879736b380c2829312dae3b05f28" - integrity sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg== - dependencies: - "@react-spring/shared" "~9.7.5" - "@react-spring/types" "~9.7.5" - -"@react-spring/core@~9.7.5": - version "9.7.5" - resolved "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz#72159079f52c1c12813d78b52d4f17c0bf6411f7" - integrity sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w== - dependencies: - "@react-spring/animated" "~9.7.5" - "@react-spring/shared" "~9.7.5" - "@react-spring/types" "~9.7.5" - -"@react-spring/rafz@~9.7.5": - version "9.7.5" - resolved "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz#ee7959676e7b5d6a3813e8c17d5e50df98b95df9" - integrity sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw== - -"@react-spring/shared@~9.7.5": - version "9.7.5" - resolved "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz#6d513622df6ad750bbbd4dedb4ca0a653ec92073" - integrity sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw== - dependencies: - "@react-spring/rafz" "~9.7.5" - "@react-spring/types" "~9.7.5" - -"@react-spring/types@~9.7.5": - version "9.7.5" - resolved "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz#e5dd180f3ed985b44fd2cd2f32aa9203752ef3e8" - integrity sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g== - -"@react-spring/web@9.4.5 || ^9.7.2": - version "9.7.5" - resolved "https://registry.npmjs.org/@react-spring/web/-/web-9.7.5.tgz#7d7782560b3a6fb9066b52824690da738605de80" - integrity sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ== - dependencies: - "@react-spring/animated" "~9.7.5" - "@react-spring/core" "~9.7.5" - "@react-spring/shared" "~9.7.5" - "@react-spring/types" "~9.7.5" - "@remirror/core-constants@3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" @@ -3210,26 +2970,16 @@ resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== -"@types/d3-color@*", "@types/d3-color@^3.0.0": +"@types/d3-color@*": version "3.1.3" resolved "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== -"@types/d3-delaunay@^6.0.4": - version "6.0.4" - resolved "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1" - integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== - "@types/d3-ease@^3.0.0": version "3.0.2" resolved "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== -"@types/d3-format@^1.4.1": - version "1.4.5" - resolved "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.5.tgz#6392303c2ca3c287c3a1a2046455cd0a0bd50bbe" - integrity sha512-mLxrC1MSWupOSncXN/HOlWUAAIffAEBaI4+PKy2uMPsKe4FNZlk7qrbTjmzJXITQQqBHivaks4Td18azgqnotA== - "@types/d3-interpolate@^3.0.1": version "3.0.4" resolved "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" @@ -3242,45 +2992,25 @@ resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a" integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== -"@types/d3-scale-chromatic@^3.0.0": - version "3.1.0" - resolved "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#dc6d4f9a98376f18ea50bad6c39537f1b5463c39" - integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ== - -"@types/d3-scale@^4.0.2", "@types/d3-scale@^4.0.8": +"@types/d3-scale@^4.0.2": version "4.0.9" resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb" integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== dependencies: "@types/d3-time" "*" -"@types/d3-shape@^3.1.0", "@types/d3-shape@^3.1.6": +"@types/d3-shape@^3.1.0": version "3.1.7" resolved "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz#2b7b423dc2dfe69c8c93596e673e37443348c555" integrity sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg== dependencies: "@types/d3-path" "*" -"@types/d3-time-format@^2.3.1": - version "2.3.4" - resolved "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.4.tgz#544af5184df8b3fc4d9b42b14058789acee2905e" - integrity sha512-xdDXbpVO74EvadI3UDxjxTdR6QIxm1FKzEA/+F8tL4GWWUg/hgvBqf6chql64U5A9ZUGWo7pEu4eNlyLwbKdhg== - -"@types/d3-time-format@^3.0.0": - version "3.0.4" - resolved "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-3.0.4.tgz#f972bdd7be1048184577cf235a44721a78c6bb4b" - integrity sha512-or9DiDnYI1h38J9hxKEsw513+KVuFbEVhl7qdxcaudoiqWWepapUen+2vAriFGexr6W5+P4l9+HJrB39GG+oRg== - "@types/d3-time@*", "@types/d3-time@^3.0.0": version "3.0.4" resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f" integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== -"@types/d3-time@^1.0.10", "@types/d3-time@^1.1.1": - version "1.1.4" - resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.4.tgz#20da4b75c537a940e7319b75717c67a2e499515a" - integrity sha512-JIvy2HjRInE+TXOmIGN5LCmeO0hkFZx5f9FZ7kiN+D+YTcc8pptsiLiuHsvwxwC7VVKmJ2ExHUgNlAiV7vQM9g== - "@types/d3-timer@^3.0.0": version "3.0.2" resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" @@ -3533,7 +3263,7 @@ resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== -"@types/prop-types@*", "@types/prop-types@^15.0.0", "@types/prop-types@^15.7.12", "@types/prop-types@^15.7.2": +"@types/prop-types@*", "@types/prop-types@^15.0.0", "@types/prop-types@^15.7.12": version "15.7.14" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2" integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ== @@ -5193,13 +4923,6 @@ csstype@^3.0.2, csstype@^3.1.3: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -d3-array@2: - version "2.12.1" - resolved "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81" - integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== - dependencies: - internmap "^1.0.0" - "d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6: version "3.2.4" resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" @@ -5207,18 +4930,11 @@ d3-array@2: dependencies: internmap "1 - 2" -"d3-color@1 - 3", d3-color@^3.1.0: +"d3-color@1 - 3": version "3.1.0" resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== -d3-delaunay@^6.0.4: - version "6.0.4" - resolved "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" - integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== - dependencies: - delaunator "5" - d3-ease@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" @@ -5229,12 +4945,7 @@ d3-ease@^3.0.1: resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== -d3-format@^1.4.4: - version "1.4.5" - resolved "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" - integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== - -"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: +"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== @@ -5246,14 +4957,6 @@ d3-path@^3.1.0: resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== -d3-scale-chromatic@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" - integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== - dependencies: - d3-color "1 - 3" - d3-interpolate "1 - 3" - d3-scale@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" @@ -5265,7 +4968,7 @@ d3-scale@^4.0.2: d3-time "2.1.1 - 3" d3-time-format "2 - 4" -d3-shape@^3.1.0, d3-shape@^3.2.0: +d3-shape@^3.1.0: version "3.2.0" resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== @@ -5279,20 +4982,6 @@ d3-shape@^3.1.0, d3-shape@^3.2.0: dependencies: d3-time "1 - 3" -d3-time-format@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6" - integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag== - dependencies: - d3-time "1 - 2" - -"d3-time@1 - 2": - version "2.1.1" - resolved "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682" - integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ== - dependencies: - d3-array "2" - "d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" @@ -5300,11 +4989,6 @@ d3-time-format@^3.0.0: dependencies: d3-array "2 - 3" -d3-time@^1.0.10, d3-time@^1.0.11: - version "1.1.0" - resolved "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" - integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== - d3-timer@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" @@ -5477,13 +5161,6 @@ define-properties@^1.1.3, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -delaunator@5: - version "5.0.1" - resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" - integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== - dependencies: - robust-predicates "^3.0.2" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -7185,11 +6862,6 @@ internal-slot@^1.1.0: resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== -internmap@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" - integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== - intl-messageformat@^10.7.11: version "10.7.15" resolved "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.15.tgz#5cdc62139ef39ece1b083db32dae4d1c9fa5b627" @@ -9277,7 +8949,7 @@ process@^0.11.10: resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -10077,11 +9749,6 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -robust-predicates@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" - integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== - rollup@^4.34.8: version "4.41.1" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.41.1.tgz#46ddc1b33cf1b0baa99320d3b0b4973dc2253b6a" @@ -10572,7 +10239,16 @@ streamx@^2.15.0, streamx@^2.21.0: optionalDependencies: bare-events "^2.2.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10665,7 +10341,14 @@ string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11883,7 +11566,16 @@ word-wrap@^1.2.5: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==