diff --git a/apps/api/plane/ee/models/dashboard.py b/apps/api/plane/ee/models/dashboard.py index fba7d06330..262d9a3348 100644 --- a/apps/api/plane/ee/models/dashboard.py +++ b/apps/api/plane/ee/models/dashboard.py @@ -90,6 +90,7 @@ class Widget(BaseModel): PIE_CHART = "PIE_CHART", "Pie Chart" DONUT_CHART = "DONUT_CHART", "Donut Chart" NUMBER = "NUMBER", "Number Chart" + TABLE_CHART = "TABLE_CHART", "Table Chart" class ChartModelEnum(models.TextChoices): BASIC = "BASIC", "Basic" diff --git a/apps/api/plane/ee/serializers/app/dashboard.py b/apps/api/plane/ee/serializers/app/dashboard.py index 948b64cdf5..4f77390dab 100644 --- a/apps/api/plane/ee/serializers/app/dashboard.py +++ b/apps/api/plane/ee/serializers/app/dashboard.py @@ -104,6 +104,14 @@ class WidgetSerializer(BaseSerializer): height = serializers.IntegerField(required=False) width = serializers.IntegerField(required=False) filters = serializers.JSONField(required=False) + chart_type = serializers.ChoiceField(choices=Widget.ChartTypeEnum.values) + chart_model = serializers.ChoiceField(choices=Widget.ChartModelEnum.values) + x_axis_property = serializers.ChoiceField(choices=Widget.PropertyEnum.values, allow_null=True, required=False) + y_axis_metric = serializers.ChoiceField(choices=Widget.YAxisMetricEnum.values, allow_null=True, required=False) + x_axis_date_grouping = serializers.ChoiceField( + choices=Widget.XAxisDateGroupingEnum.values, allow_null=True, required=False + ) + group_by = serializers.ChoiceField(choices=Widget.PropertyEnum.values, allow_null=True, required=False) def create(self, validated_data): workspace_id = self.context["workspace_id"] diff --git a/apps/web/app/assets/empty-state/dashboards/widgets/charts/table_chart-dark.svg b/apps/web/app/assets/empty-state/dashboards/widgets/charts/table_chart-dark.svg new file mode 100644 index 0000000000..9b7825ac20 --- /dev/null +++ b/apps/web/app/assets/empty-state/dashboards/widgets/charts/table_chart-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/web/app/assets/empty-state/dashboards/widgets/charts/table_chart-light.svg b/apps/web/app/assets/empty-state/dashboards/widgets/charts/table_chart-light.svg new file mode 100644 index 0000000000..bb1e7f1e82 --- /dev/null +++ b/apps/web/app/assets/empty-state/dashboards/widgets/charts/table_chart-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/web/core/components/dashboards/sidebar/axis-config/x-axis-config.tsx b/apps/web/core/components/dashboards/sidebar/axis-config/x-axis-config.tsx index aea6a51fe1..f5afa9a730 100644 --- a/apps/web/core/components/dashboards/sidebar/axis-config/x-axis-config.tsx +++ b/apps/web/core/components/dashboards/sidebar/axis-config/x-axis-config.tsx @@ -11,11 +11,13 @@ * NOTICE: Proprietary and confidential. Unauthorized use or distribution is prohibited. */ +import { useCallback } from "react"; import { Controller, useFormContext } from "react-hook-form"; // plane ui import { WIDGET_X_AXIS_DATE_PROPERTIES } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -import type { TDashboardWidget } from "@plane/types"; +import type { EWidgetXAxisProperty, TDashboardWidget } from "@plane/types"; +import { EWidgetChartTypes } from "@plane/types"; // local components import { WidgetDateGroupSelect } from "./date-group-select"; import { WidgetPropertySelect } from "./property-select"; @@ -24,17 +26,61 @@ type Props = { handleSubmit: (data: Partial) => Promise; }; +/** When updating one axis property, avoid duplicate with the other; returns payload and whether to clear the other field. */ +function getAxisMutualExclusionPayload( + field: "x_axis_property" | "group_by", + newValue: EWidgetXAxisProperty | null, + otherFieldValue: EWidgetXAxisProperty | null +): { payload: Partial; clearOther: boolean } { + const clearOther = newValue != null && newValue === otherFieldValue; + if (field === "x_axis_property") { + return { + payload: { x_axis_property: newValue, ...(clearOther && { group_by: null }) }, + clearOther, + }; + } + return { + payload: { group_by: newValue, ...(clearOther && { x_axis_property: null }) }, + clearOther, + }; +} + export function WidgetConfigSidebarXAxisConfig(props: Props) { const { handleSubmit } = props; - // translation const { t } = useTranslation(); - // form info const { control, watch, setValue } = useFormContext(); - // derived values + + const selectedChartType = watch("chart_type"); const selectedXAxisProperty = watch("x_axis_property"); const selectedGroupByProperty = watch("group_by"); + + const isTableChart = selectedChartType === EWidgetChartTypes.TABLE_CHART; const isDateGroupingEnabled = - !!selectedXAxisProperty && WIDGET_X_AXIS_DATE_PROPERTIES.includes(selectedXAxisProperty); + selectedXAxisProperty != null && WIDGET_X_AXIS_DATE_PROPERTIES.includes(selectedXAxisProperty); + + const handleXAxisPropertyChange = useCallback( + (val: EWidgetXAxisProperty | null, onChange: (val: EWidgetXAxisProperty | null) => void) => { + const { payload, clearOther } = getAxisMutualExclusionPayload( + "x_axis_property", + val, + selectedGroupByProperty ?? null + ); + onChange(val); + if (clearOther) setValue("group_by", null); + void handleSubmit(payload); + }, + [selectedGroupByProperty, setValue, handleSubmit] + ); + + const handleGroupByChange = useCallback( + (val: EWidgetXAxisProperty | null, onChange: (val: EWidgetXAxisProperty | null) => void) => { + const { payload, clearOther } = getAxisMutualExclusionPayload("group_by", val, selectedXAxisProperty ?? null); + onChange(val); + if (clearOther) setValue("x_axis_property", null); + void handleSubmit(payload); + }, + [selectedXAxisProperty, setValue, handleSubmit] + ); return (
@@ -43,16 +89,9 @@ export function WidgetConfigSidebarXAxisConfig(props: Props) { name="x_axis_property" render={({ field: { value, onChange } }) => ( { - const isGroupBySame = selectedGroupByProperty === val; - onChange(val); - if (isGroupBySame) { - setValue("group_by", null); - } - handleSubmit({ x_axis_property: val, ...(isGroupBySame ? { group_by: null } : {}) }); - }} + onChange={(val) => handleXAxisPropertyChange(val, onChange)} placeholder={t("dashboards.widget.common.add_property")} - title={t("chart.x_axis")} + title={isTableChart ? t("dashboards.widget.chart_types.table_chart.columns") : t("chart.x_axis")} value={value} /> )} @@ -65,7 +104,7 @@ export function WidgetConfigSidebarXAxisConfig(props: Props) { { onChange(val); - handleSubmit({ x_axis_date_grouping: val }); + void handleSubmit({ x_axis_date_grouping: val }); }} placeholder={t("dashboards.widget.common.date_group.placeholder")} title={t("dashboards.widget.common.date_group.label")} @@ -74,6 +113,20 @@ export function WidgetConfigSidebarXAxisConfig(props: Props) { )} /> )} + {isTableChart && ( + ( + handleGroupByChange(val, onChange)} + placeholder={t("dashboards.widget.chart_types.table_chart.rows_placeholder")} + title={t("dashboards.widget.chart_types.table_chart.rows")} + value={value} + /> + )} + /> + )}
); } diff --git a/apps/web/core/components/dashboards/sidebar/axis-config/y-axis-config.tsx b/apps/web/core/components/dashboards/sidebar/axis-config/y-axis-config.tsx index 25ff35d7b5..351e4b46c7 100644 --- a/apps/web/core/components/dashboards/sidebar/axis-config/y-axis-config.tsx +++ b/apps/web/core/components/dashboards/sidebar/axis-config/y-axis-config.tsx @@ -16,6 +16,7 @@ import { Controller, useFormContext } from "react-hook-form"; import { CHART_WIDGETS_Y_AXIS_METRICS_LIST } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import type { EWidgetYAxisMetric, TDashboardWidget } from "@plane/types"; +import { EWidgetChartTypes } from "@plane/types"; // local components import { WidgetMetricSelect } from "./metric-select"; @@ -28,7 +29,11 @@ export function WidgetConfigSidebarYAxisConfig(props: Props) { // translation const { t } = useTranslation(); // form info - const { control } = useFormContext(); + const { control, watch } = useFormContext(); + // derived values + const selectedChartType = watch("chart_type"); + + if (selectedChartType === EWidgetChartTypes.TABLE_CHART) return null; return (
diff --git a/apps/web/core/components/dashboards/sidebar/basic-config/root.tsx b/apps/web/core/components/dashboards/sidebar/basic-config/root.tsx index aba4a4f579..5ca9843515 100644 --- a/apps/web/core/components/dashboards/sidebar/basic-config/root.tsx +++ b/apps/web/core/components/dashboards/sidebar/basic-config/root.tsx @@ -22,7 +22,7 @@ import { } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import type { TDashboardWidget, TDashboardWidgetConfig } from "@plane/types"; -import { EWidgetChartModels, EWidgetChartTypes, EWidgetXAxisProperty } from "@plane/types"; +import { EWidgetChartModels, EWidgetChartTypes, EWidgetXAxisProperty, EWidgetYAxisMetric } from "@plane/types"; // local components import { WidgetChartTypeIcon } from "../../widgets"; import { DashboardWidgetChartTypesDropdown } from "../../widgets/dropdown"; @@ -72,38 +72,46 @@ export function WidgetConfigSidebarBasicConfig(props: Props) { [getValues] ); + const getYAxisMetricForChartType = useCallback( + (chartType: EWidgetChartTypes): EWidgetYAxisMetric | undefined => { + if (chartType === EWidgetChartTypes.TABLE_CHART) { + return EWidgetYAxisMetric.WORK_ITEM_COUNT; + } + const current = getValues("y_axis_metric"); + if (current == null) return undefined; + if (chartType === EWidgetChartTypes.NUMBER) { + return NUMBER_WIDGET_Y_AXIS_METRICS_LIST.includes(current) ? current : NUMBER_WIDGET_Y_AXIS_METRICS_LIST[0]; + } + return CHART_WIDGETS_Y_AXIS_METRICS_LIST.includes(current) ? current : CHART_WIDGETS_Y_AXIS_METRICS_LIST[0]; + }, + [getValues] + ); + const handleChartTypeChange = useCallback( async (chartType: EWidgetChartTypes, chartModel: EWidgetChartModels) => { const updatedConfig = getUpdatedConfig(chartType, chartModel); - // update form values const payload: Partial = { chart_type: chartType, chart_model: chartModel, config: updatedConfig, }; - // update y-axis metric - const yAxisMetric = getValues("y_axis_metric"); - let newYAxisMetric = yAxisMetric; - if (yAxisMetric) { - if (chartType === EWidgetChartTypes.NUMBER && !NUMBER_WIDGET_Y_AXIS_METRICS_LIST.includes(yAxisMetric)) { - newYAxisMetric = NUMBER_WIDGET_Y_AXIS_METRICS_LIST[0]; - } - if (chartType !== EWidgetChartTypes.NUMBER && !CHART_WIDGETS_Y_AXIS_METRICS_LIST.includes(yAxisMetric)) { - newYAxisMetric = CHART_WIDGETS_Y_AXIS_METRICS_LIST[0]; - } - payload.y_axis_metric = newYAxisMetric; - } - if (chartModel === EWidgetChartModels.BASIC) { + + const yAxisMetric = getYAxisMetricForChartType(chartType); + if (yAxisMetric != null) payload.y_axis_metric = yAxisMetric; + + if (chartModel === EWidgetChartModels.BASIC && chartType !== EWidgetChartTypes.TABLE_CHART) { payload.group_by = null; } if (chartType === EWidgetChartTypes.DONUT_CHART && chartModel === EWidgetChartModels.PROGRESS) { payload.x_axis_property = EWidgetXAxisProperty.STATE_GROUPS; } - Object.keys(payload).forEach((key) => { - const payloadKey = key as keyof typeof payload; - setValue(payloadKey, payloadKey); - }); - // make api call + if (chartType === EWidgetChartTypes.TABLE_CHART && !getValues("x_axis_property")) { + payload.x_axis_property = EWidgetXAxisProperty.STATES; + } + + for (const [key, value] of Object.entries(payload)) { + if (value !== undefined) setValue(key as keyof TDashboardWidget, value); + } await handleSubmit(payload); }, [getUpdatedConfig, handleSubmit] diff --git a/apps/web/core/components/dashboards/sidebar/root.tsx b/apps/web/core/components/dashboards/sidebar/root.tsx index e05cfeb651..c0cc4ffd6a 100644 --- a/apps/web/core/components/dashboards/sidebar/root.tsx +++ b/apps/web/core/components/dashboards/sidebar/root.tsx @@ -178,7 +178,6 @@ export const DashboardsWidgetConfigSidebarRoot = observer(function DashboardsWid
-
- - -
-
{t("dashboards.widget.common.style")}
-
- + <> +
+
+ + +
+
{t("dashboards.widget.common.style")}
+
+ +
-
- - -
- - {selectedChartType !== EWidgetChartTypes.NUMBER && ( - - )} -
-
- -
+ + +
+ + {selectedChartType !== EWidgetChartTypes.NUMBER && ( + + )} +
+
+ +
+ ); }); diff --git a/apps/web/core/components/dashboards/widgets/chart-types/root.tsx b/apps/web/core/components/dashboards/widgets/chart-types/root.tsx index 9d3c390c89..61b5046dc4 100644 --- a/apps/web/core/components/dashboards/widgets/chart-types/root.tsx +++ b/apps/web/core/components/dashboards/widgets/chart-types/root.tsx @@ -30,6 +30,7 @@ import { DashboardWidgetHeader } from "./header"; import { DashboardLineChartWidget } from "./line-chart"; import { DashboardNumberWidget } from "./number"; import { DashboardPieChartWidget } from "./pie-chart"; +import { DashboardTableChartWidget } from "./table-chart"; import type { TWidgetComponentProps } from "./"; import { commonWidgetClassName, parseWidgetData } from "./"; @@ -94,6 +95,9 @@ export const DashboardWidgetRoot = observer(function DashboardWidgetRoot(props: case EWidgetChartTypes.PIE_CHART: WidgetComponent = DashboardPieChartWidget; break; + case EWidgetChartTypes.TABLE_CHART: + WidgetComponent = DashboardTableChartWidget; + break; default: WidgetComponent = null; } diff --git a/apps/web/core/components/dashboards/widgets/chart-types/table-chart.tsx b/apps/web/core/components/dashboards/widgets/chart-types/table-chart.tsx new file mode 100644 index 0000000000..4e61d1f7b1 --- /dev/null +++ b/apps/web/core/components/dashboards/widgets/chart-types/table-chart.tsx @@ -0,0 +1,99 @@ +/** + * SPDX-FileCopyrightText: 2023-present Plane Software, Inc. + * SPDX-License-Identifier: LicenseRef-Plane-Commercial + * + * Licensed under the Plane Commercial License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://plane.so/legals/eula + * + * DO NOT remove or modify this notice. + * NOTICE: Proprietary and confidential. Unauthorized use or distribution is prohibited. + */ + +import { observer } from "mobx-react"; +// plane imports +import { WIDGET_X_AXIS_PROPERTIES_LIST } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; +// local imports +import type { TWidgetComponentProps } from "."; + +export const DashboardTableChartWidget = observer(function DashboardTableChartWidget(props: TWidgetComponentProps) { + const { parsedData, widget } = props; + // translation + const { t } = useTranslation(); + // derived values + const { group_by } = widget ?? {}; + // columns = x-axis items (each datum) + const columns = parsedData.data; + // rows = schema keys (group values) + const groupKeys = Object.keys(parsedData.schema); + const hasGroupKeys = groupKeys.length > 0; + // first column header label — the group_by property name + const groupByLabel = group_by ? t(WIDGET_X_AXIS_PROPERTIES_LIST[group_by].i18n_label) : ""; + + // If there are no columns at all, there is nothing to render. + if (!columns.length) return null; + + return ( +
+ {hasGroupKeys ? ( + <> + + + + + {columns.map((col) => ( + + ))} + + +
+ {groupByLabel} + + {col.name} +
+
+ + + {groupKeys.map((groupKey) => { + const groupLabel = parsedData.schema[groupKey]; + return ( + + + {columns.map((col) => { + const cellValue = col[groupKey] ?? 0; + return ( + + ); + })} + + ); + })} + +
+ {groupLabel} + + {cellValue} +
+
+ + ) : ( +
+ {t("dashboards.widget.chart_types.table_chart.configure_rows_hint")} +
+ )} +
+ ); +}); diff --git a/apps/web/core/components/dashboards/widgets/empty-states/helper.ts b/apps/web/core/components/dashboards/widgets/empty-states/helper.ts index 9d5bad018c..7749e6770e 100644 --- a/apps/web/core/components/dashboards/widgets/empty-states/helper.ts +++ b/apps/web/core/components/dashboards/widgets/empty-states/helper.ts @@ -26,6 +26,8 @@ import numberDark from "@/app/assets/empty-state/dashboards/widgets/charts/numbe import numberLight from "@/app/assets/empty-state/dashboards/widgets/charts/number-light.webp?url"; import pieChartDark from "@/app/assets/empty-state/dashboards/widgets/charts/pie_chart-dark.webp?url"; import pieChartLight from "@/app/assets/empty-state/dashboards/widgets/charts/pie_chart-light.webp?url"; +import tableChartDark from "@/app/assets/empty-state/dashboards/widgets/charts/table_chart-dark.svg?url"; +import tableChartLight from "@/app/assets/empty-state/dashboards/widgets/charts/table_chart-light.svg?url"; export const CHART_ASSET_MAP: Record = { AREA_CHART: { dark: areaChartDark, light: areaChartLight }, @@ -34,4 +36,5 @@ export const CHART_ASSET_MAP: Record : null; diff --git a/apps/web/core/store/dashboards/widget.ts b/apps/web/core/store/dashboards/widget.ts index d6886d9121..0f4c8231b0 100644 --- a/apps/web/core/store/dashboards/widget.ts +++ b/apps/web/core/store/dashboards/widget.ts @@ -193,6 +193,11 @@ export class DashboardWidgetInstance implements IDashboardWidgetInstance { if (chartType === EWidgetChartTypes.NUMBER) { if (!this.y_axis_metric) return "y_axis_metric"; } + if (chartType === EWidgetChartTypes.TABLE_CHART) { + if (!this.x_axis_property) return "x_axis_property"; + if (!this.y_axis_metric) return "y_axis_metric"; + if (!this.group_by) return "group_by"; + } return null; } diff --git a/packages/constants/src/dashboards/dashboards.ts b/packages/constants/src/dashboards/dashboards.ts index 4a5b1d0167..387d282bbd 100644 --- a/packages/constants/src/dashboards/dashboards.ts +++ b/packages/constants/src/dashboards/dashboards.ts @@ -69,6 +69,11 @@ export const WIDGET_CHART_TYPES_LIST: { i18n_short_label: "dashboards.widget.chart_types.number.short_label", i18n_long_label: "dashboards.widget.chart_types.number.long_label", }, + { + key: EWidgetChartTypes.TABLE_CHART, + i18n_short_label: "dashboards.widget.chart_types.table_chart.short_label", + i18n_long_label: "dashboards.widget.chart_types.table_chart.long_label", + }, ]; export const DEFAULT_WIDGET_CHART_TYPE_PAYLOAD: { @@ -184,6 +189,21 @@ export const DEFAULT_WIDGET_CHART_TYPE_PAYLOAD: { }, }, }, + [EWidgetChartTypes.TABLE_CHART]: { + // For table charts, y_axis_metric is static because it cannot be changed. + y_axis_metric: EWidgetYAxisMetric.WORK_ITEM_COUNT, + config: { + orientation: "vertical", + show_legends: true, + show_tooltip: true, + }, + [EWidgetChartModels.BASIC]: { + y_axis_metric: EWidgetYAxisMetric.WORK_ITEM_COUNT, + config: { + bar_color: DEFAULT_WIDGET_COLOR, + }, + }, + }, }; export const WIDGET_CHART_MODELS_LIST: Record< @@ -279,6 +299,14 @@ export const WIDGET_CHART_MODELS_LIST: Record< flags: [E_FEATURE_FLAGS.DASHBOARDS, E_FEATURE_FLAGS.DASHBOARDS_ADVANCED], }, ], + [EWidgetChartTypes.TABLE_CHART]: [ + { + value: EWidgetChartModels.BASIC, + i18n_short_label: "dashboards.widget.chart_types.table_chart.chart_models.basic.short_label", + i18n_long_label: "dashboards.widget.chart_types.table_chart.chart_models.basic.long_label", + flags: [E_FEATURE_FLAGS.DASHBOARDS, E_FEATURE_FLAGS.DASHBOARDS_ADVANCED], + }, + ], }; export const WIDGET_X_AXIS_PROPERTIES_LIST: Record< @@ -470,6 +498,11 @@ export const WIDGET_DROPDOWN_SECTIONS: { i18n_label: "dashboards.widget.chart_types.number.short_label", models: WIDGET_CHART_MODELS_LIST[EWidgetChartTypes.NUMBER], }, + { + key: EWidgetChartTypes.TABLE_CHART, + i18n_label: "dashboards.widget.chart_types.table_chart.short_label", + models: WIDGET_CHART_MODELS_LIST[EWidgetChartTypes.TABLE_CHART], + }, ], }, ]; diff --git a/packages/i18n/src/locales/cs/translations.ts b/packages/i18n/src/locales/cs/translations.ts index f84f063aee..501c7492de 100644 --- a/packages/i18n/src/locales/cs/translations.ts +++ b/packages/i18n/src/locales/cs/translations.ts @@ -4844,6 +4844,20 @@ Vytvořte nový.`, }, text_color: "Barva textu", }, + table_chart: { + short_label: "Tabulka", + long_label: "Tabulkový graf", + chart_models: { + basic: { + short_label: "Základní", + long_label: "Tabulka", + }, + }, + columns: "Sloupce", + rows: "Řádky", + rows_placeholder: "Přidat řádky", + configure_rows_hint: "Vyberte vlastnost pro řádky pro zobrazení této tabulky.", + }, }, color_palettes: { modern_tech: "Moderní technologie", @@ -4964,6 +4978,12 @@ Vytvořte nový.`, y_axis_metric: "Metrika chybí.", }, }, + table_chart: { + basic: { + x_axis_property: "Sloupcům chybí hodnota.", + group_by: "Řádkům chybí hodnota.", + }, + }, ask_admin: "Požádejte svého správce, aby konfiguroval tento widget.", }, }, diff --git a/packages/i18n/src/locales/de/translations.ts b/packages/i18n/src/locales/de/translations.ts index bf19433669..e4ac15436f 100644 --- a/packages/i18n/src/locales/de/translations.ts +++ b/packages/i18n/src/locales/de/translations.ts @@ -4907,6 +4907,20 @@ Erstellen Sie ein neues.`, }, text_color: "Textfarbe", }, + table_chart: { + short_label: "Tabelle", + long_label: "Tabellendiagramm", + chart_models: { + basic: { + short_label: "Standard", + long_label: "Tabelle", + }, + }, + columns: "Spalten", + rows: "Zeilen", + rows_placeholder: "Zeilen hinzufügen", + configure_rows_hint: "Wählen Sie eine Eigenschaft für Zeilen aus, um diese Tabelle anzuzeigen.", + }, }, color_palettes: { modern: "Modern", @@ -5027,6 +5041,12 @@ Erstellen Sie ein neues.`, y_axis_metric: "Der Metrik fehlt ein Wert.", }, }, + table_chart: { + basic: { + x_axis_property: "Spalten fehlt ein Wert.", + group_by: "Zeilen fehlt ein Wert.", + }, + }, ask_admin: "Bitten Sie Ihren Administrator, dieses Widget zu konfigurieren.", }, }, diff --git a/packages/i18n/src/locales/en/translations.ts b/packages/i18n/src/locales/en/translations.ts index 186031548b..865b2876df 100644 --- a/packages/i18n/src/locales/en/translations.ts +++ b/packages/i18n/src/locales/en/translations.ts @@ -5369,6 +5369,20 @@ if you are sure your search is right. `, }, text_color: "Text color", }, + table_chart: { + short_label: "Table", + long_label: "Table chart", + chart_models: { + basic: { + short_label: "Basic", + long_label: "Table", + }, + }, + columns: "Columns", + rows: "Rows", + rows_placeholder: "Add rows", + configure_rows_hint: "Select a property for rows to view this table.", + }, }, sections: { charts: "Charts", @@ -5489,6 +5503,12 @@ if you are sure your search is right. `, y_axis_metric: "Metric is missing a value.", }, }, + table_chart: { + basic: { + x_axis_property: "Columns is missing a value.", + group_by: "Rows is missing a value.", + }, + }, ask_admin: "Ask your admin to configure this widget.", }, upgrade_required: { diff --git a/packages/i18n/src/locales/es/translations.ts b/packages/i18n/src/locales/es/translations.ts index 1ddcbc2dc4..914abb1f7a 100644 --- a/packages/i18n/src/locales/es/translations.ts +++ b/packages/i18n/src/locales/es/translations.ts @@ -4945,6 +4945,20 @@ si estás seguro de que tu búsqueda es correcta.`, }, text_color: "Color de texto", }, + table_chart: { + short_label: "Tabla", + long_label: "Gráfico de tabla", + chart_models: { + basic: { + short_label: "Básico", + long_label: "Tabla", + }, + }, + columns: "Columnas", + rows: "Filas", + rows_placeholder: "Añadir filas", + configure_rows_hint: "Seleccione una propiedad para las filas para ver esta tabla.", + }, }, color_palettes: { modern: "Moderno", @@ -5065,6 +5079,12 @@ si estás seguro de que tu búsqueda es correcta.`, y_axis_metric: "A la métrica le falta un valor.", }, }, + table_chart: { + basic: { + x_axis_property: "A las columnas les falta un valor.", + group_by: "A las filas les falta un valor.", + }, + }, ask_admin: "Solicite a su administrador que configure este widget.", }, }, diff --git a/packages/i18n/src/locales/fr/translations.ts b/packages/i18n/src/locales/fr/translations.ts index 3c4a07a659..4d9fe67356 100644 --- a/packages/i18n/src/locales/fr/translations.ts +++ b/packages/i18n/src/locales/fr/translations.ts @@ -4951,6 +4951,20 @@ si vous êtes sûr que votre recherche est correcte.`, }, text_color: "Couleur du texte", }, + table_chart: { + short_label: "Tableau", + long_label: "Graphique en tableau", + chart_models: { + basic: { + short_label: "Basique", + long_label: "Tableau", + }, + }, + columns: "Colonnes", + rows: "Lignes", + rows_placeholder: "Ajouter des lignes", + configure_rows_hint: "Sélectionnez une propriété pour les lignes pour afficher ce tableau.", + }, }, color_palettes: { modern: "Moderne", @@ -5071,6 +5085,12 @@ si vous êtes sûr que votre recherche est correcte.`, y_axis_metric: "La métrique manque d'une valeur.", }, }, + table_chart: { + basic: { + x_axis_property: "Il manque une valeur aux colonnes.", + group_by: "Il manque une valeur aux lignes.", + }, + }, ask_admin: "Demandez à votre administrateur de configurer ce widget.", }, }, diff --git a/packages/i18n/src/locales/id/translations.ts b/packages/i18n/src/locales/id/translations.ts index 6f8902c880..1e5be78757 100644 --- a/packages/i18n/src/locales/id/translations.ts +++ b/packages/i18n/src/locales/id/translations.ts @@ -4885,6 +4885,20 @@ jika Anda yakin pencarian Anda benar. `, }, text_color: "Warna teks", }, + table_chart: { + short_label: "Tabel", + long_label: "Grafik tabel", + chart_models: { + basic: { + short_label: "Dasar", + long_label: "Tabel", + }, + }, + columns: "Kolom", + rows: "Baris", + rows_placeholder: "Tambah baris", + configure_rows_hint: "Pilih properti untuk baris untuk melihat tabel ini.", + }, }, color_palettes: { modern: "Modern", @@ -5005,6 +5019,12 @@ jika Anda yakin pencarian Anda benar. `, y_axis_metric: "Metrik tidak memiliki nilai.", }, }, + table_chart: { + basic: { + x_axis_property: "Kolom tidak memiliki nilai.", + group_by: "Baris tidak memiliki nilai.", + }, + }, ask_admin: "Tanyakan admin Anda untuk mengonfigurasi widget ini.", }, }, diff --git a/packages/i18n/src/locales/it/translations.ts b/packages/i18n/src/locales/it/translations.ts index 1e90d93e92..9c2e24e2b9 100644 --- a/packages/i18n/src/locales/it/translations.ts +++ b/packages/i18n/src/locales/it/translations.ts @@ -4911,6 +4911,20 @@ Crea un nuovo progetto invece`, }, text_color: "Colore testo", }, + table_chart: { + short_label: "Tabella", + long_label: "Grafico a tabella", + chart_models: { + basic: { + short_label: "Base", + long_label: "Tabella", + }, + }, + columns: "Colonne", + rows: "Righe", + rows_placeholder: "Aggiungi righe", + configure_rows_hint: "Seleziona una proprietà per le righe per visualizzare questa tabella.", + }, }, color_palettes: { modern: "Moderno", @@ -5031,6 +5045,12 @@ Crea un nuovo progetto invece`, y_axis_metric: "La metrica manca di un valore.", }, }, + table_chart: { + basic: { + x_axis_property: "Alle colonne manca un valore.", + group_by: "Alle righe manca un valore.", + }, + }, ask_admin: "Chiedi al tuo amministratore di configurare questo widget.", }, }, diff --git a/packages/i18n/src/locales/ja/translations.ts b/packages/i18n/src/locales/ja/translations.ts index fe59c69a1d..4197350493 100644 --- a/packages/i18n/src/locales/ja/translations.ts +++ b/packages/i18n/src/locales/ja/translations.ts @@ -4865,6 +4865,20 @@ export default { }, text_color: "テキスト カラー", }, + table_chart: { + short_label: "表", + long_label: "表グラフ", + chart_models: { + basic: { + short_label: "基本", + long_label: "表", + }, + }, + columns: "列", + rows: "行", + rows_placeholder: "行を追加", + configure_rows_hint: "この表を表示するには行のプロパティを選択してください。", + }, }, color_palettes: { modern: "モダン", @@ -4985,6 +4999,12 @@ export default { y_axis_metric: "メトリック イズ ミッシング ア バリュー", }, }, + table_chart: { + basic: { + x_axis_property: "列に値がありません。", + group_by: "行に値がありません。", + }, + }, ask_admin: "アスク ユア アドミン トゥ コンフィギュア ディス ウィジェット", }, }, diff --git a/packages/i18n/src/locales/ko/translations.ts b/packages/i18n/src/locales/ko/translations.ts index 0682196d3e..d797e4604f 100644 --- a/packages/i18n/src/locales/ko/translations.ts +++ b/packages/i18n/src/locales/ko/translations.ts @@ -4824,6 +4824,20 @@ export default { }, text_color: "텍스트 컬러", }, + table_chart: { + short_label: "테이블", + long_label: "테이블 차트", + chart_models: { + basic: { + short_label: "기본", + long_label: "테이블", + }, + }, + columns: "열", + rows: "행", + rows_placeholder: "행 추가", + configure_rows_hint: "이 테이블을 보려면 행에 대한 속성을 선택하세요.", + }, }, color_palettes: { modern: "모던", @@ -4944,6 +4958,12 @@ export default { y_axis_metric: "메트릭에 값이 없습니다.", }, }, + table_chart: { + basic: { + x_axis_property: "열에 값이 없습니다.", + group_by: "행에 값이 없습니다.", + }, + }, ask_admin: "이 위젯을 구성하려면 관리자에게 문의하세요.", }, }, diff --git a/packages/i18n/src/locales/pl/translations.ts b/packages/i18n/src/locales/pl/translations.ts index 8214f3d967..5ac81e95b3 100644 --- a/packages/i18n/src/locales/pl/translations.ts +++ b/packages/i18n/src/locales/pl/translations.ts @@ -4853,6 +4853,20 @@ Utwórz nowy.`, }, text_color: "Kolor tekstu", }, + table_chart: { + short_label: "Tabela", + long_label: "Wykres tabelaryczny", + chart_models: { + basic: { + short_label: "Podstawowy", + long_label: "Tabela", + }, + }, + columns: "Kolumny", + rows: "Wiersze", + rows_placeholder: "Dodaj wiersze", + configure_rows_hint: "Wybierz właściwość dla wierszy, aby wyświetlić tę tabelę.", + }, }, color_palettes: { modern: "Nowoczesna", @@ -4973,6 +4987,12 @@ Utwórz nowy.`, y_axis_metric: "Metryka nie ma wartości.", }, }, + table_chart: { + basic: { + x_axis_property: "Kolumny nie mają wartości.", + group_by: "Wiersze nie mają wartości.", + }, + }, ask_admin: "Poproś swojego administratora o skonfigurowanie tego widżetu.", }, }, diff --git a/packages/i18n/src/locales/pt-BR/translations.ts b/packages/i18n/src/locales/pt-BR/translations.ts index ee29179998..e35e5a6e52 100644 --- a/packages/i18n/src/locales/pt-BR/translations.ts +++ b/packages/i18n/src/locales/pt-BR/translations.ts @@ -4924,6 +4924,20 @@ se você tem certeza de que sua pesquisa está correta.`, }, text_color: "Cor do texto", }, + table_chart: { + short_label: "Tabela", + long_label: "Gráfico de tabela", + chart_models: { + basic: { + short_label: "Básico", + long_label: "Tabela", + }, + }, + columns: "Colunas", + rows: "Linhas", + rows_placeholder: "Adicionar linhas", + configure_rows_hint: "Selecione uma propriedade para as linhas para visualizar esta tabela.", + }, }, color_palettes: { modern: "Moderno", @@ -5046,6 +5060,12 @@ se você tem certeza de que sua pesquisa está correta.`, y_axis_metric: "A métrica está sem um valor.", }, }, + table_chart: { + basic: { + x_axis_property: "As colunas estão sem um valor.", + group_by: "As linhas estão sem um valor.", + }, + }, ask_admin: "Peça ao seu administrador para configurar este widget.", }, }, diff --git a/packages/i18n/src/locales/ro/translations.ts b/packages/i18n/src/locales/ro/translations.ts index fe9f45217a..9ef3759039 100644 --- a/packages/i18n/src/locales/ro/translations.ts +++ b/packages/i18n/src/locales/ro/translations.ts @@ -4917,6 +4917,20 @@ văzute aici`, }, text_color: "Culoare text", }, + table_chart: { + short_label: "Tabel", + long_label: "Grafic tabel", + chart_models: { + basic: { + short_label: "De bază", + long_label: "Tabel", + }, + }, + columns: "Coloane", + rows: "Rânduri", + rows_placeholder: "Adaugă rânduri", + configure_rows_hint: "Selectați o proprietate pentru rânduri pentru a vizualiza acest tabel.", + }, }, color_palettes: { modern: "Modern", @@ -5037,6 +5051,12 @@ văzute aici`, y_axis_metric: "Metrica lipsește o valoare.", }, }, + table_chart: { + basic: { + x_axis_property: "Coloanelor lipsește o valoare.", + group_by: "Rândurilor lipsește o valoare.", + }, + }, ask_admin: "Întreabă administratorul tău pentru a configura acest widget.", }, }, diff --git a/packages/i18n/src/locales/ru/translations.ts b/packages/i18n/src/locales/ru/translations.ts index 343d42331d..f2765b5b06 100644 --- a/packages/i18n/src/locales/ru/translations.ts +++ b/packages/i18n/src/locales/ru/translations.ts @@ -4918,6 +4918,20 @@ export default { }, text_color: "Цвет текста", }, + table_chart: { + short_label: "Таблица", + long_label: "Табличная диаграмма", + chart_models: { + basic: { + short_label: "Основная", + long_label: "Таблица", + }, + }, + columns: "Столбцы", + rows: "Строки", + rows_placeholder: "Добавить строки", + configure_rows_hint: "Выберите свойство для строк, чтобы просмотреть эту таблицу.", + }, }, color_palettes: { modern: "Современная", @@ -5038,6 +5052,12 @@ export default { y_axis_metric: "Отсутствует значение для метрики.", }, }, + table_chart: { + basic: { + x_axis_property: "Столбцам не хватает значения.", + group_by: "Строкам не хватает значения.", + }, + }, ask_admin: "Попросите администратора настроить этот виджет.", }, }, diff --git a/packages/i18n/src/locales/sk/translations.ts b/packages/i18n/src/locales/sk/translations.ts index 8ad7db083c..6df5b0110b 100644 --- a/packages/i18n/src/locales/sk/translations.ts +++ b/packages/i18n/src/locales/sk/translations.ts @@ -4760,6 +4760,20 @@ Vytvorte nový.`, }, text_color: "Farba textu", }, + table_chart: { + short_label: "Tabuľka", + long_label: "Tabuľkový graf", + chart_models: { + basic: { + short_label: "Základný", + long_label: "Tabuľka", + }, + }, + columns: "Stĺpce", + rows: "Riadky", + rows_placeholder: "Pridať riadky", + configure_rows_hint: "Vyberte vlastnosť pre riadky na zobrazenie tejto tabuľky.", + }, }, color_palettes: { modern: "Moderná", @@ -4880,6 +4894,12 @@ Vytvorte nový.`, y_axis_metric: "Metrike chýba hodnota.", }, }, + table_chart: { + basic: { + x_axis_property: "Stĺpcom chýba hodnota.", + group_by: "Riadkom chýba hodnota.", + }, + }, ask_admin: "Požiadajte svojho administrátora o konfiguráciu tohto vidžetu.", }, }, diff --git a/packages/i18n/src/locales/tr-TR/translations.ts b/packages/i18n/src/locales/tr-TR/translations.ts index 1b97f9adf0..d27da28da5 100644 --- a/packages/i18n/src/locales/tr-TR/translations.ts +++ b/packages/i18n/src/locales/tr-TR/translations.ts @@ -4906,6 +4906,20 @@ modüller arşivlenebilir.`, }, text_color: "Tekst rengi", }, + table_chart: { + short_label: "Tablo", + long_label: "Tablo grafiği", + chart_models: { + basic: { + short_label: "Temel", + long_label: "Tablo", + }, + }, + columns: "Sütunlar", + rows: "Satırlar", + rows_placeholder: "Satır ekle", + configure_rows_hint: "Bu tabloyu görüntülemek için satırlar için bir özellik seçin.", + }, }, sections: { charts: "Çarts", @@ -5026,6 +5040,12 @@ modüller arşivlenebilir.`, y_axis_metric: "Metrik için değer eksik.", }, }, + table_chart: { + basic: { + x_axis_property: "Sütunlar için değer eksik.", + group_by: "Satırlar için değer eksik.", + }, + }, ask_admin: "Bu vicıtı konfigüre etmesi için admininize sorun.", }, upgrade_required: { diff --git a/packages/i18n/src/locales/ua/translations.ts b/packages/i18n/src/locales/ua/translations.ts index 9398ba7050..fccc5134dd 100644 --- a/packages/i18n/src/locales/ua/translations.ts +++ b/packages/i18n/src/locales/ua/translations.ts @@ -4852,6 +4852,20 @@ export default { }, text_color: "Колір тексту", }, + table_chart: { + short_label: "Таблиця", + long_label: "Таблична діаграма", + chart_models: { + basic: { + short_label: "Базова", + long_label: "Таблиця", + }, + }, + columns: "Стовпці", + rows: "Рядки", + rows_placeholder: "Додати рядки", + configure_rows_hint: "Виберіть властивість для рядків, щоб переглянути цю таблицю.", + }, }, color_palettes: { modern: "Модерн", @@ -4972,6 +4986,12 @@ export default { y_axis_metric: "Відсутнє значення метрики.", }, }, + table_chart: { + basic: { + x_axis_property: "Стовпцям не вистачає значення.", + group_by: "Рядкам не вистачає значення.", + }, + }, ask_admin: "Попросіть адміністратора налаштувати цей віджет.", }, }, diff --git a/packages/i18n/src/locales/vi-VN/translations.ts b/packages/i18n/src/locales/vi-VN/translations.ts index a8e3d0ad8f..85e432de04 100644 --- a/packages/i18n/src/locales/vi-VN/translations.ts +++ b/packages/i18n/src/locales/vi-VN/translations.ts @@ -4861,6 +4861,20 @@ tìm kiếm là chính xác, hãy cho chúng tôi biết.`, }, text_color: "Màu văn bản", }, + table_chart: { + short_label: "Bảng", + long_label: "Biểu đồ bảng", + chart_models: { + basic: { + short_label: "Cơ bản", + long_label: "Bảng", + }, + }, + columns: "Cột", + rows: "Hàng", + rows_placeholder: "Thêm hàng", + configure_rows_hint: "Chọn một thuộc tính cho hàng để xem bảng này.", + }, }, color_palettes: { modern: "Hiện đại", @@ -4983,6 +4997,12 @@ tìm kiếm là chính xác, hãy cho chúng tôi biết.`, y_axis_metric: "Chỉ số đang thiếu giá trị.", }, }, + table_chart: { + basic: { + x_axis_property: "Cột đang thiếu giá trị.", + group_by: "Hàng đang thiếu giá trị.", + }, + }, ask_admin: "Yêu cầu quản trị viên của bạn cấu hình widget này.", }, }, diff --git a/packages/i18n/src/locales/zh-CN/translations.ts b/packages/i18n/src/locales/zh-CN/translations.ts index 60e6905750..f0ea51936a 100644 --- a/packages/i18n/src/locales/zh-CN/translations.ts +++ b/packages/i18n/src/locales/zh-CN/translations.ts @@ -4731,6 +4731,20 @@ export default { }, text_color: "文本颜色", }, + table_chart: { + short_label: "表格", + long_label: "表格图表", + chart_models: { + basic: { + short_label: "基础", + long_label: "表格", + }, + }, + columns: "列", + rows: "行", + rows_placeholder: "添加行", + configure_rows_hint: "选择行的属性以查看此表格。", + }, }, color_palettes: { modern: "现代", @@ -4851,6 +4865,12 @@ export default { y_axis_metric: "指标缺少值。", }, }, + table_chart: { + basic: { + x_axis_property: "列缺少值。", + group_by: "行缺少值。", + }, + }, ask_admin: "请联系管理员配置此组件。", }, }, diff --git a/packages/i18n/src/locales/zh-TW/translations.ts b/packages/i18n/src/locales/zh-TW/translations.ts index 87e2903285..703b416c23 100644 --- a/packages/i18n/src/locales/zh-TW/translations.ts +++ b/packages/i18n/src/locales/zh-TW/translations.ts @@ -4752,6 +4752,20 @@ export default { }, text_color: "文字顏色", }, + table_chart: { + short_label: "表格", + long_label: "表格圖表", + chart_models: { + basic: { + short_label: "基本", + long_label: "表格", + }, + }, + columns: "列", + rows: "行", + rows_placeholder: "添加行", + configure_rows_hint: "選擇行的屬性以查看此表格。", + }, }, color_palettes: { modern: "現代", @@ -4872,6 +4886,12 @@ export default { y_axis_metric: "指標缺少值。", }, }, + table_chart: { + basic: { + x_axis_property: "列缺少值。", + group_by: "行缺少值。", + }, + }, ask_admin: "請管理員配置此小工具。", }, }, diff --git a/packages/propel/src/icons/widget/index.ts b/packages/propel/src/icons/widget/index.ts index 9a66eb8c63..9c5a8147d0 100644 --- a/packages/propel/src/icons/widget/index.ts +++ b/packages/propel/src/icons/widget/index.ts @@ -17,3 +17,4 @@ export * from "./donut-chart"; export * from "./line-chart"; export * from "./number"; export * from "./pie-chart"; +export * from "./table-chart"; diff --git a/packages/propel/src/icons/widget/table-chart.tsx b/packages/propel/src/icons/widget/table-chart.tsx new file mode 100644 index 0000000000..c9d5048a88 --- /dev/null +++ b/packages/propel/src/icons/widget/table-chart.tsx @@ -0,0 +1,43 @@ +/** + * SPDX-FileCopyrightText: 2023-present Plane Software, Inc. + * SPDX-License-Identifier: LicenseRef-Plane-Commercial + * + * Licensed under the Plane Commercial License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://plane.so/legals/eula + * + * DO NOT remove or modify this notice. + * NOTICE: Proprietary and confidential. Unauthorized use or distribution is prohibited. + */ + +import * as React from "react"; +// types +import type { ISvgIcons } from "../type"; + +export function BasicTableChartIcon({ height = "27", width = "27", className = "", ...rest }: ISvgIcons) { + return ( + + + + + + ); +} diff --git a/packages/types/src/dashboards/widget.ts b/packages/types/src/dashboards/widget.ts index 2087843946..b3b0fc82eb 100644 --- a/packages/types/src/dashboards/widget.ts +++ b/packages/types/src/dashboards/widget.ts @@ -29,6 +29,7 @@ export enum EWidgetChartTypes { PIE_CHART = "PIE_CHART", DONUT_CHART = "DONUT_CHART", NUMBER = "NUMBER", + TABLE_CHART = "TABLE_CHART", } export enum EWidgetXAxisDateGrouping { @@ -123,13 +124,18 @@ export type TNumberWidgetConfig = { text_color?: string; }; // combined + +// table chart +export type TTableChartWidgetConfig = TBarChartWidgetConfig; + export type TDashboardWidgetConfig = | TBarChartWidgetConfig | TLineChartWidgetConfig | TAreaChartWidgetConfig | TDonutChartWidgetConfig | TPieChartWidgetConfig - | TNumberWidgetConfig; + | TNumberWidgetConfig + | TTableChartWidgetConfig; export type TDashboardWidget = { chart_model: EWidgetChartModels | undefined;