diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ca022d8..0c303d46c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ You can also check the [release page](https://github.com/visualize-admin/visuali - Features - Added a new group of charts – Combo charts – that includes multi-measure line, dual-axis line and column-line charts + - Added a way to edit and remove charts for logged in users + - Improved user profile page # [3.22.9] - 2023-10-06 diff --git a/app/browse/datatable.tsx b/app/browse/datatable.tsx index 133e31c72..2f73b8950 100644 --- a/app/browse/datatable.tsx +++ b/app/browse/datatable.tsx @@ -15,7 +15,7 @@ import { ascending, descending } from "d3"; import { useMemo, useRef, useState } from "react"; import { - extractComponentIris, + extractChartConfigComponentIris, useQueryFilters, } from "@/charts/shared/chart-helpers"; import { Loading } from "@/components/hint"; @@ -246,7 +246,7 @@ export const DataSetTable = ({ sx?: SxProps; }) => { const locale = useLocale(); - const componentIris = extractComponentIris(chartConfig); + const componentIris = extractChartConfigComponentIris(chartConfig); const filters = useQueryFilters({ chartConfig }); const commonQueryVariables = { iri: dataSetIri, diff --git a/app/browser/dataset-browse.tsx b/app/browser/dataset-browse.tsx index b8f8f1df5..ded0dee85 100644 --- a/app/browser/dataset-browse.tsx +++ b/app/browser/dataset-browse.tsx @@ -592,7 +592,7 @@ const NavSection = ({ color="inherit" onClick={open} > - Show all + Show all )} diff --git a/app/charts/area/chart-area.tsx b/app/charts/area/chart-area.tsx index 2fb42778c..ed602d6e9 100644 --- a/app/charts/area/chart-area.tsx +++ b/app/charts/area/chart-area.tsx @@ -6,7 +6,6 @@ import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; import { AxisHeightLinear } from "@/charts/shared/axis-height-linear"; import { AxisTime, AxisTimeDomain } from "@/charts/shared/axis-width-time"; import { BrushTime } from "@/charts/shared/brush"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartControlsContainer, @@ -31,18 +30,15 @@ export const ChartAreasVisualization = ({ dataSource, chartConfig, queryFilters, - published, + componentIris, }: { dataSetIri: string; dataSource: DataSource; chartConfig: AreaConfig; queryFilters: QueryFilters; - published: boolean; + componentIris: string[] | undefined; }) => { const locale = useLocale(); - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const commonQueryVariables = { iri: dataSetIri, sourceType: dataSource.type, diff --git a/app/charts/column/chart-column.tsx b/app/charts/column/chart-column.tsx index 0c6b06e95..d815dfab4 100644 --- a/app/charts/column/chart-column.tsx +++ b/app/charts/column/chart-column.tsx @@ -17,7 +17,6 @@ import { AxisWidthBandDomain, } from "@/charts/shared/axis-width-band"; import { BrushTime } from "@/charts/shared/brush"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartControlsContainer, @@ -39,20 +38,17 @@ import { ChartProps } from "../shared/ChartProps"; export const ChartColumnsVisualization = ({ dataSetIri, dataSource, + componentIris, chartConfig, queryFilters, - published, }: { dataSetIri: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: ColumnConfig; queryFilters: QueryFilters; - published: boolean; }) => { const locale = useLocale(); - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const commonQueryVariables = { iri: dataSetIri, sourceType: dataSource.type, diff --git a/app/charts/combo/chart-combo-line-column.tsx b/app/charts/combo/chart-combo-line-column.tsx index 15349658c..4c24c7c96 100644 --- a/app/charts/combo/chart-combo-line-column.tsx +++ b/app/charts/combo/chart-combo-line-column.tsx @@ -7,7 +7,6 @@ import { ComboLineColumn } from "@/charts/combo/combo-line-column"; import { ComboLineColumnChart } from "@/charts/combo/combo-line-column-state"; import { AxisWidthBand } from "@/charts/shared/axis-width-band"; import { BrushTime } from "@/charts/shared/brush"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartSvg } from "@/charts/shared/containers"; import { HoverDotMultiple } from "@/charts/shared/interaction/hover-dots-multiple"; import { Ruler } from "@/charts/shared/interaction/ruler"; @@ -29,20 +28,17 @@ import { ChartProps } from "../shared/ChartProps"; type ChartComboLineColumnVisualizationProps = { dataSetIri: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: ComboLineColumnConfig; queryFilters: QueryFilters; - published: boolean; }; export const ChartComboLineColumnVisualization = ( props: ChartComboLineColumnVisualizationProps ) => { - const { dataSetIri, dataSource, chartConfig, queryFilters, published } = + const { dataSetIri, dataSource, componentIris, chartConfig, queryFilters } = props; const locale = useLocale(); - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const commonQueryVariables = { iri: dataSetIri, sourceType: dataSource.type, diff --git a/app/charts/combo/chart-combo-line-dual.tsx b/app/charts/combo/chart-combo-line-dual.tsx index d24eceab6..7023c41c2 100644 --- a/app/charts/combo/chart-combo-line-dual.tsx +++ b/app/charts/combo/chart-combo-line-dual.tsx @@ -6,7 +6,6 @@ import { ComboLineDual } from "@/charts/combo/combo-line-dual"; import { ComboLineDualChart } from "@/charts/combo/combo-line-dual-state"; import { AxisTime, AxisTimeDomain } from "@/charts/shared/axis-width-time"; import { BrushTime } from "@/charts/shared/brush"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartSvg } from "@/charts/shared/containers"; import { HoverDotMultiple } from "@/charts/shared/interaction/hover-dots-multiple"; import { Ruler } from "@/charts/shared/interaction/ruler"; @@ -25,20 +24,17 @@ import { ChartProps } from "../shared/ChartProps"; type ChartComboLineDualVisualizationProps = { dataSetIri: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: ComboLineDualConfig; queryFilters: QueryFilters; - published: boolean; }; export const ChartComboLineDualVisualization = ( props: ChartComboLineDualVisualizationProps ) => { - const { dataSetIri, dataSource, chartConfig, queryFilters, published } = + const { dataSetIri, dataSource, componentIris, chartConfig, queryFilters } = props; const locale = useLocale(); - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const commonQueryVariables = { iri: dataSetIri, sourceType: dataSource.type, diff --git a/app/charts/combo/chart-combo-line-single.tsx b/app/charts/combo/chart-combo-line-single.tsx index 4697b63c4..af763b7d0 100644 --- a/app/charts/combo/chart-combo-line-single.tsx +++ b/app/charts/combo/chart-combo-line-single.tsx @@ -6,7 +6,6 @@ import { ComboLineSingleChart } from "@/charts/combo/combo-line-single-state"; import { AxisHeightLinear } from "@/charts/shared/axis-height-linear"; import { AxisTime, AxisTimeDomain } from "@/charts/shared/axis-width-time"; import { BrushTime } from "@/charts/shared/brush"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartControlsContainer, @@ -34,20 +33,17 @@ import { ChartProps } from "../shared/ChartProps"; type ChartComboLineSingleVisualizationProps = { dataSetIri: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: ComboLineSingleConfig; queryFilters: QueryFilters; - published: boolean; }; export const ChartComboLineSingleVisualization = ( props: ChartComboLineSingleVisualizationProps ) => { - const { dataSetIri, dataSource, chartConfig, queryFilters, published } = + const { dataSetIri, dataSource, componentIris, chartConfig, queryFilters } = props; const locale = useLocale(); - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const commonQueryVariables = { iri: dataSetIri, sourceType: dataSource.type, diff --git a/app/charts/line/chart-lines.tsx b/app/charts/line/chart-lines.tsx index 4bed41549..65d7d3531 100644 --- a/app/charts/line/chart-lines.tsx +++ b/app/charts/line/chart-lines.tsx @@ -6,7 +6,6 @@ import { LineChart } from "@/charts/line/lines-state"; import { AxisHeightLinear } from "@/charts/shared/axis-height-linear"; import { AxisTime, AxisTimeDomain } from "@/charts/shared/axis-width-time"; import { BrushTime } from "@/charts/shared/brush"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartControlsContainer, @@ -30,20 +29,17 @@ import { ChartProps } from "../shared/ChartProps"; export const ChartLinesVisualization = ({ dataSetIri, dataSource, + componentIris, chartConfig, queryFilters, - published, }: { dataSetIri: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: LineConfig; queryFilters: QueryFilters; - published: boolean; }) => { const locale = useLocale(); - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const commonQueryVariables = { iri: dataSetIri, sourceType: dataSource.type, diff --git a/app/charts/map/chart-map.tsx b/app/charts/map/chart-map.tsx index bd1a17b6c..db0e2cd46 100644 --- a/app/charts/map/chart-map.tsx +++ b/app/charts/map/chart-map.tsx @@ -5,7 +5,6 @@ import { MapComponent } from "@/charts/map/map"; import { MapLegend } from "@/charts/map/map-legend"; import { MapChart } from "@/charts/map/map-state"; import { MapTooltip } from "@/charts/map/map-tooltip"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartControlsContainer, @@ -29,15 +28,15 @@ import { ChartProps } from "../shared/ChartProps"; export const ChartMapVisualization = ({ dataSetIri, dataSource, + componentIris, chartConfig, queryFilters, - published, }: { dataSetIri: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: MapConfig; queryFilters: QueryFilters; - published: boolean; }) => { const locale = useLocale(); const areaDimensionIri = chartConfig.fields.areaLayer?.componentIri || ""; @@ -48,9 +47,6 @@ export const ChartMapVisualization = ({ sourceUrl: dataSource.url, locale, }; - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const [metadataQuery] = useDataCubeMetadataQuery({ variables: commonQueryVariables, }); diff --git a/app/charts/pie/chart-pie.tsx b/app/charts/pie/chart-pie.tsx index 7d0b62a24..0b58e857f 100644 --- a/app/charts/pie/chart-pie.tsx +++ b/app/charts/pie/chart-pie.tsx @@ -3,7 +3,6 @@ import { memo } from "react"; import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; import { Pie } from "@/charts/pie/pie"; import { PieChart } from "@/charts/pie/pie-state"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartControlsContainer, @@ -26,20 +25,17 @@ import { ChartProps } from "../shared/ChartProps"; export const ChartPieVisualization = ({ dataSetIri, dataSource, + componentIris, chartConfig, queryFilters, - published, }: { dataSetIri: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: PieConfig; queryFilters: QueryFilters; - published: boolean; }) => { const locale = useLocale(); - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const commonQueryVariables = { iri: dataSetIri, sourceType: dataSource.type, @@ -70,9 +66,6 @@ export const ChartPieVisualization = ({ observationsQuery={observationsQuery} chartConfig={chartConfig} Component={ChartPie} - ComponentProps={{ - published, - }} /> ); }; diff --git a/app/charts/scatterplot/chart-scatterplot.tsx b/app/charts/scatterplot/chart-scatterplot.tsx index 222d66881..603d646a4 100644 --- a/app/charts/scatterplot/chart-scatterplot.tsx +++ b/app/charts/scatterplot/chart-scatterplot.tsx @@ -11,7 +11,6 @@ import { AxisWidthLinear, AxisWidthLinearDomain, } from "@/charts/shared/axis-width-linear"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartControlsContainer, @@ -34,20 +33,17 @@ import { ChartProps } from "../shared/ChartProps"; export const ChartScatterplotVisualization = ({ dataSetIri, dataSource, + componentIris, chartConfig, queryFilters, - published, }: { dataSetIri: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: ScatterPlotConfig; queryFilters: QueryFilters; - published: boolean; }) => { const locale = useLocale(); - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const commonQueryVariables = { iri: dataSetIri, sourceType: dataSource.type, @@ -77,9 +73,6 @@ export const ChartScatterplotVisualization = ({ componentsQuery={componentsQuery} observationsQuery={observationsQuery} Component={ChartScatterplot} - ComponentProps={{ - published, - }} chartConfig={chartConfig} /> ); diff --git a/app/charts/shared/chart-helpers.spec.tsx b/app/charts/shared/chart-helpers.spec.tsx index 2e69f4a37..6e5887a1f 100644 --- a/app/charts/shared/chart-helpers.spec.tsx +++ b/app/charts/shared/chart-helpers.spec.tsx @@ -2,7 +2,7 @@ import { InternMap } from "d3"; import merge from "lodash/merge"; import { - extractComponentIris, + extractChartConfigComponentIris, getWideData, prepareQueryFilters, } from "@/charts/shared/chart-helpers"; @@ -156,7 +156,7 @@ describe("getChartConfigComponentIris", () => { const mapConfig = map1Fixture.data.chartConfig as unknown as MapConfig; it("should return correct componentIris for line chart", () => { - const componentsIris = extractComponentIris(lineConfig); + const componentsIris = extractChartConfigComponentIris(lineConfig); expect(componentsIris).toEqual([ "http://environment.ld.admin.ch/foen/px/0703010000_105/dimension/0", "http://environment.ld.admin.ch/foen/px/0703010000_105/measure/0", @@ -168,7 +168,7 @@ describe("getChartConfigComponentIris", () => { }); it("should return correct componentIris for map chart", () => { - const componentsIris = extractComponentIris(mapConfig); + const componentsIris = extractChartConfigComponentIris(mapConfig); expect(componentsIris).toEqual([ "https://environment.ld.admin.ch/foen/nfi/unitOfReference", "https://environment.ld.admin.ch/foen/nfi/Topic/3r", diff --git a/app/charts/shared/chart-helpers.tsx b/app/charts/shared/chart-helpers.tsx index 5a39cc3c4..18f5fe1d0 100644 --- a/app/charts/shared/chart-helpers.tsx +++ b/app/charts/shared/chart-helpers.tsx @@ -23,8 +23,10 @@ import { } from "@/config-types"; import { CategoricalColorField, + ComboChartConfig, GenericField, NumericalColorField, + isComboChartConfig, } from "@/configurator"; import { parseDate } from "@/configurator/components/ui-helpers"; import { FIELD_VALUE_NONE } from "@/configurator/constants"; @@ -118,12 +120,40 @@ const getMapChartConfigAdditionalFields = ({ fields }: MapConfig) => { return additionalFields; }; -export const extractComponentIris = (chartConfig: ChartConfig) => { +const getComboChartConfigAdditionalFields = (chartConfig: ComboChartConfig) => { + switch (chartConfig.chartType) { + case "comboLineSingle": + return chartConfig.fields.y.componentIris; + case "comboLineDual": + return [ + chartConfig.fields.y.leftAxisComponentIri, + chartConfig.fields.y.rightAxisComponentIri, + ]; + case "comboLineColumn": + return [ + chartConfig.fields.y.columnComponentIri, + chartConfig.fields.y.lineComponentIri, + ]; + default: + const _exhaustiveCheck: never = chartConfig; + return _exhaustiveCheck; + } +}; + +export const extractChartConfigsComponentIris = ( + chartConfigs: ChartConfig[] +) => { + return uniq(chartConfigs.map(extractChartConfigComponentIris).flat()); +}; + +export const extractChartConfigComponentIris = (chartConfig: ChartConfig) => { const { fields, interactiveFiltersConfig: IFConfig } = chartConfig; const fieldIris = Object.values(fields).map((d) => d.componentIri); const additionalFieldIris = chartConfig.chartType === "map" ? getMapChartConfigAdditionalFields(chartConfig) + : isComboChartConfig(chartConfig) + ? getComboChartConfigAdditionalFields(chartConfig) : []; const filterIris = getChartConfigFilterComponentIris(chartConfig); const IFKeys = IFConfig ? (Object.keys(IFConfig) as IFKey[]) : []; diff --git a/app/charts/table/chart-table.tsx b/app/charts/table/chart-table.tsx index 7570cb8b6..691ae05ae 100644 --- a/app/charts/table/chart-table.tsx +++ b/app/charts/table/chart-table.tsx @@ -1,7 +1,6 @@ import { memo } from "react"; import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; import { Table } from "@/charts/table/table"; import { TableChart } from "@/charts/table/table-state"; import { DataSource, TableConfig } from "@/configurator"; @@ -17,18 +16,15 @@ import { ChartProps } from "../shared/ChartProps"; export const ChartTableVisualization = ({ dataSetIri, dataSource, + componentIris, chartConfig, - published, }: { dataSetIri: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: TableConfig; - published: boolean; }) => { const locale = useLocale(); - const componentIris = published - ? extractComponentIris(chartConfig) - : undefined; const commonQueryVariables = { iri: dataSetIri, sourceType: dataSource.type, diff --git a/app/components/chart-footnotes.tsx b/app/components/chart-footnotes.tsx index 6cc0cfd2c..bc71369a4 100644 --- a/app/components/chart-footnotes.tsx +++ b/app/components/chart-footnotes.tsx @@ -4,7 +4,7 @@ import { makeStyles } from "@mui/styles"; import { PropsWithChildren, useEffect, useMemo, useState } from "react"; import { - extractComponentIris, + extractChartConfigComponentIris, useQueryFilters, } from "@/charts/shared/chart-helpers"; import { useChartTablePreview } from "@/components/chart-table-preview"; @@ -81,7 +81,7 @@ export const ChartFootnotes = ({ // Data for data download const filters = useQueryFilters({ chartConfig }); - const componentIris = extractComponentIris(chartConfig); + const componentIris = extractChartConfigComponentIris(chartConfig); const [{ data: visibleData }] = useDataCubeObservationsQuery({ variables: { ...commonQueryVariables, diff --git a/app/components/chart-preview.tsx b/app/components/chart-preview.tsx index 1a9e6656d..27d3837c4 100644 --- a/app/components/chart-preview.tsx +++ b/app/components/chart-preview.tsx @@ -214,8 +214,8 @@ export const ChartPreviewInner = (props: ChartPreviewProps) => { )} diff --git a/app/components/chart-published.tsx b/app/components/chart-published.tsx index 2d16ccb81..c448d27ab 100644 --- a/app/components/chart-published.tsx +++ b/app/components/chart-published.tsx @@ -5,7 +5,7 @@ import { useEffect, useMemo, useRef } from "react"; import { useStore } from "zustand"; import { DataSetTable } from "@/browse/datatable"; -import { extractComponentIris } from "@/charts/shared/chart-helpers"; +import { extractChartConfigsComponentIris } from "@/charts/shared/chart-helpers"; import { isUsingImputation } from "@/charts/shared/imputation"; import { ChartErrorBoundary } from "@/components/chart-error-boundary"; import { ChartFootnotes } from "@/components/chart-footnotes"; @@ -23,6 +23,7 @@ import { } from "@/components/metadata-panel"; import { ChartConfig, + ConfiguratorStatePublished, DataSource, getChartConfig, isPublished, @@ -58,6 +59,7 @@ export const ChartPublished = (props: ChartPublishedProps) => { @@ -83,6 +85,7 @@ const useStyles = makeStyles((theme) => ({ type ChartPublishInnerProps = { dataSet: string; dataSource: DataSource | undefined; + state: ConfiguratorStatePublished; chartConfig: ChartConfig; configKey: string | undefined; }; @@ -91,6 +94,7 @@ const ChartPublishedInner = (props: ChartPublishInnerProps) => { const { dataSet, dataSource = DEFAULT_DATA_SOURCE, + state, chartConfig, configKey, } = props; @@ -141,10 +145,11 @@ const ChartPublishedInner = (props: ChartPublishInnerProps) => { const [{ data: metadata }] = useDataCubeMetadataQuery({ variables: commonQueryVariables, }); + const componentIris = extractChartConfigsComponentIris(state.chartConfigs); const [{ data: components }] = useComponentsQuery({ variables: { ...commonQueryVariables, - componentIris: extractComponentIris(chartConfig), + componentIris, }, }); const handleToggleTableView = useEvent(() => setIsTablePreview((c) => !c)); @@ -250,8 +255,8 @@ const ChartPublishedInner = (props: ChartPublishInnerProps) => { )} diff --git a/app/components/chart-with-filters.tsx b/app/components/chart-with-filters.tsx index 6fb5571e6..3bd9a3665 100644 --- a/app/components/chart-with-filters.tsx +++ b/app/components/chart-with-filters.tsx @@ -73,18 +73,18 @@ const ChartTableVisualization = dynamic( type GenericChartProps = { dataSet: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: ChartConfig; - published: boolean; }; const GenericChart = (props: GenericChartProps) => { - const { dataSet, dataSource, chartConfig, published } = props; + const { dataSet, dataSource, componentIris, chartConfig } = props; const queryFilters = useQueryFilters({ chartConfig }); const commonProps = { dataSetIri: dataSet, dataSource, queryFilters, - published, + componentIris, }; switch (chartConfig.chartType) { @@ -150,8 +150,8 @@ const GenericChart = (props: GenericChartProps) => { type ChartWithFiltersProps = { dataSet: string; dataSource: DataSource; + componentIris: string[] | undefined; chartConfig: ChartConfig; - published: boolean; }; export const ChartWithFilters = React.forwardRef< diff --git a/app/configurator/configurator-state.spec.tsx b/app/configurator/configurator-state.spec.tsx index d50b0c4c5..f97bb71a6 100644 --- a/app/configurator/configurator-state.spec.tsx +++ b/app/configurator/configurator-state.spec.tsx @@ -21,7 +21,7 @@ import { getLocalStorageKey, handleChartFieldChanged, handleChartOptionChanged, - initChartStateFromChart, + initChartStateFromChartCopy, initChartStateFromCube, initChartStateFromLocalStorage, moveFilterField, @@ -106,7 +106,7 @@ describe("initChartStateFromChart", () => { }); // @ts-ignore const { key, activeChartKey, chartConfigs, ...rest } = - await initChartStateFromChart("abcde"); + await initChartStateFromChartCopy("abcde"); const { key: chartConfigKey, ...chartConfig } = chartConfigs[0]; const { key: migratedKey, @@ -132,7 +132,7 @@ describe("initChartStateFromChart", () => { isBadState: true, }, }); - const state = await initChartStateFromChart("abcde"); + const state = await initChartStateFromChartCopy("abcde"); expect(state).toEqual(undefined); }); }); diff --git a/app/configurator/configurator-state.tsx b/app/configurator/configurator-state.tsx index ce5aa49e0..7bf66cb1e 100644 --- a/app/configurator/configurator-state.tsx +++ b/app/configurator/configurator-state.tsx @@ -1318,7 +1318,7 @@ const ConfiguratorStateContext = createContext< type ChartId = string; type DatasetIri = string; -export const initChartStateFromChart = async ( +export const initChartStateFromChartCopy = async ( from: ChartId ): Promise => { const config = await fetchChartConfig(from); @@ -1443,8 +1443,8 @@ const ConfiguratorStateProviderInternal = ( let newChartState; if (chartId === "new") { - if (query.from && typeof query.from === "string") { - newChartState = await initChartStateFromChart(query.from); + if (query.copy && typeof query.copy === "string") { + newChartState = await initChartStateFromChartCopy(query.copy); } else if (query.edit && typeof query.edit === "string") { newChartState = await initChartStateFromChartEdit(query.edit); } else if (query.cube && typeof query.cube === "string") { diff --git a/app/db/config.ts b/app/db/config.ts index a41a7c585..659619c47 100644 --- a/app/db/config.ts +++ b/app/db/config.ts @@ -62,6 +62,24 @@ export const updateConfig = async ({ }); }; +/** + * Remove config from the DB. + * Only valid for logged in users. + * + * @param key Key of the config to be updated + */ +export const removeConfig = async ({ + key, +}: { + key: string; +}): Promise<{ key: string }> => { + return await prisma.config.delete({ + where: { + key, + }, + }); +}; + const migrateDataSet = (dataSet: string): string => { if (dataSet.includes("https://environment.ld.admin.ch/foen/nfi")) { return dataSet.replace(/None-None-/, ""); @@ -143,7 +161,11 @@ export const getUserConfigs = async (userId: number) => { where: { user_id: userId, }, + orderBy: { + created_at: "desc", + }, }); + return configs.map((c) => parseDbConfig(c)); }; diff --git a/app/locales/de/messages.po b/app/locales/de/messages.po index 1512f3325..22d74d0b8 100644 --- a/app/locales/de/messages.po +++ b/app/locales/de/messages.po @@ -17,6 +17,10 @@ msgstr "" msgid "Add filter" msgstr "Filter hinzufügen" +#: app/configurator/components/chart-options-selector.tsx +msgid "Axis orientation" +msgstr "Achsenausrichtung" + #: app/configurator/components/chart-configurator.tsx msgid "Drag filters to reorganize" msgstr "Ziehen Sie die Filter per Drag & Drop, um sie neu zu organisieren" @@ -50,6 +54,10 @@ msgstr "Organisationen" msgid "browse-panel.themes" msgstr "Kategorien" +#: app/login/components/profile-header.tsx +msgid "browse.dataset.all" +msgstr "Alle Datensätze durchsuchen" + #: app/browser/dataset-preview.tsx msgid "browse.dataset.create-visualization" msgstr "Visualisierung von diesem Datensatz erstellen" @@ -239,6 +247,18 @@ msgstr "Balken" msgid "controls.chart.type.column" msgstr "Säulen" +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineColumn" +msgstr "Column-line" + +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineDual" +msgstr "Dual-axis line" + +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineSingle" +msgstr "Multi-line" + #: app/configurator/components/field-i18n.ts msgid "controls.chart.type.line" msgstr "Linien" @@ -481,6 +501,7 @@ msgstr "Zurück zur Übersicht" msgid "controls.nav.back-to-preview" msgstr "Zurück zur Vorschau" +#: app/configurator/components/chart-options-selector.tsx #: app/configurator/components/field.tsx #: app/configurator/components/field.tsx msgid "controls.none" @@ -735,6 +756,7 @@ msgstr "Filter anwenden" msgid "controls.size" msgstr "Grösse" +#: app/configurator/components/chart-options-selector.tsx #: app/configurator/table/table-chart-sorting-options.tsx msgid "controls.sorting.addDimension" msgstr "Dimension hinzufügen" @@ -1068,6 +1090,69 @@ msgstr "Filter ausblenden" msgid "interactive.data.filters.show" msgstr "Filter anzeigen" +#: app/login/components/profile-tables.tsx +msgid "login.chart.copy" +msgstr "Kopieren" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.delete" +msgstr "Löschen" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.delete.confirmation" +msgstr "Sind Sie sicher, dass Sie diese Grafik löschen wollen?" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.edit" +msgstr "Bearbeiten" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.share" +msgstr "Teilen" + +#: app/login/components/profile-tables.tsx +msgid "login.create-chart" +msgstr "erstellen Sie einen" + +#: app/login/components/profile-tables.tsx +msgid "login.no-charts" +msgstr "Noch keine Charts" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.chart.confirmation.default" +msgstr "Sind Sie sicher, dass Sie diese Aktion durchführen wollen?" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.chart.delete.warning" +msgstr "Denken Sie daran, dass das Entfernen dieser Visualisierung sich auf alle Stellen auswirkt, an denen sie möglicherweise bereits eingebettet ist!" + +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations" +msgstr "Meine Visualisierungen" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-actions" +msgstr "Aktionen" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-name" +msgstr "Name" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-published-date" +msgstr "Veröffentlicht" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-type" +msgstr "Typ" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.dataset-name" +msgstr "Datensatz" + #: app/components/header.tsx #: app/components/header.tsx msgid "logo.swiss.confederation" @@ -1093,6 +1178,10 @@ msgstr "Zur Diagrammansicht wechseln" msgid "metadata.switch.table" msgstr "Zur Tabellenansicht wechseln" +#: app/login/components/profile-tables.tsx +msgid "no" +msgstr "Nein" + #: app/components/publish-actions.tsx msgid "publication.embed.AEM" msgstr "Einbett-Code für AEM «Externe Applikation»" @@ -1162,6 +1251,11 @@ msgstr "Suche" msgid "select.controls.metadata.search" msgstr "Springen zu..." +#: app/browser/dataset-browse.tsx +#: app/login/components/profile-tables.tsx +msgid "show.all" +msgstr "Alle anzeigen" + #: app/configurator/components/chart-controls/drag-and-drop-tab.tsx msgid "table.column.no" msgstr "Spalte {0}" @@ -1171,3 +1265,7 @@ msgstr "Spalte {0}" #: app/components/chart-footnotes.tsx msgid "typography.colon" msgstr ": " + +#: app/login/components/profile-tables.tsx +msgid "yes" +msgstr "Ja" diff --git a/app/locales/en/messages.po b/app/locales/en/messages.po index d1eb04374..05630f96c 100644 --- a/app/locales/en/messages.po +++ b/app/locales/en/messages.po @@ -17,6 +17,10 @@ msgstr "" msgid "Add filter" msgstr "Add filter" +#: app/configurator/components/chart-options-selector.tsx +msgid "Axis orientation" +msgstr "Axis orientation" + #: app/configurator/components/chart-configurator.tsx msgid "Drag filters to reorganize" msgstr "Drag filters to reorganize" @@ -50,6 +54,10 @@ msgstr "Organizations" msgid "browse-panel.themes" msgstr "Categories" +#: app/login/components/profile-header.tsx +msgid "browse.dataset.all" +msgstr "Browse all datasets" + #: app/browser/dataset-preview.tsx msgid "browse.dataset.create-visualization" msgstr "Start a visualization" @@ -239,6 +247,18 @@ msgstr "Bars" msgid "controls.chart.type.column" msgstr "Columns" +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineColumn" +msgstr "Column-line" + +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineDual" +msgstr "Dual-axis line" + +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineSingle" +msgstr "Multi-line" + #: app/configurator/components/field-i18n.ts msgid "controls.chart.type.line" msgstr "Lines" @@ -481,6 +501,7 @@ msgstr "Back to main" msgid "controls.nav.back-to-preview" msgstr "Back to preview" +#: app/configurator/components/chart-options-selector.tsx #: app/configurator/components/field.tsx #: app/configurator/components/field.tsx msgid "controls.none" @@ -735,6 +756,7 @@ msgstr "Apply filters" msgid "controls.size" msgstr "Size" +#: app/configurator/components/chart-options-selector.tsx #: app/configurator/table/table-chart-sorting-options.tsx msgid "controls.sorting.addDimension" msgstr "Add dimension" @@ -1068,6 +1090,69 @@ msgstr "Hide Filters" msgid "interactive.data.filters.show" msgstr "Show Filters" +#: app/login/components/profile-tables.tsx +msgid "login.chart.copy" +msgstr "Copy" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.delete" +msgstr "Delete" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.delete.confirmation" +msgstr "Are you sure you want to delete this chart?" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.edit" +msgstr "Edit" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.share" +msgstr "Share" + +#: app/login/components/profile-tables.tsx +msgid "login.create-chart" +msgstr "create one" + +#: app/login/components/profile-tables.tsx +msgid "login.no-charts" +msgstr "No charts yet" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.chart.confirmation.default" +msgstr "Are you sure you want to perform this action?" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.chart.delete.warning" +msgstr "Keep in mind that removing this visualization will affect all the places where it might be already embedded!" + +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations" +msgstr "My visualizations" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-actions" +msgstr "Actions" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-name" +msgstr "Name" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-published-date" +msgstr "Published" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-type" +msgstr "Type" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.dataset-name" +msgstr "Dataset" + #: app/components/header.tsx #: app/components/header.tsx msgid "logo.swiss.confederation" @@ -1093,6 +1178,10 @@ msgstr "Switch to chart view" msgid "metadata.switch.table" msgstr "Switch to table view" +#: app/login/components/profile-tables.tsx +msgid "no" +msgstr "No" + #: app/components/publish-actions.tsx msgid "publication.embed.AEM" msgstr "Embed Code for AEM \"External Application\"" @@ -1162,6 +1251,11 @@ msgstr "Search" msgid "select.controls.metadata.search" msgstr "Jump to..." +#: app/browser/dataset-browse.tsx +#: app/login/components/profile-tables.tsx +msgid "show.all" +msgstr "Show all" + #: app/configurator/components/chart-controls/drag-and-drop-tab.tsx msgid "table.column.no" msgstr "Column {0}" @@ -1171,3 +1265,7 @@ msgstr "Column {0}" #: app/components/chart-footnotes.tsx msgid "typography.colon" msgstr ": " + +#: app/login/components/profile-tables.tsx +msgid "yes" +msgstr "Yes" diff --git a/app/locales/fr/messages.po b/app/locales/fr/messages.po index c358b0150..b125e868f 100644 --- a/app/locales/fr/messages.po +++ b/app/locales/fr/messages.po @@ -17,6 +17,10 @@ msgstr "" msgid "Add filter" msgstr "Ajouter un filtre" +#: app/configurator/components/chart-options-selector.tsx +msgid "Axis orientation" +msgstr "Orientation de l'axe" + #: app/configurator/components/chart-configurator.tsx msgid "Drag filters to reorganize" msgstr "Faites glisser les filtres pour les réorganiser" @@ -50,6 +54,10 @@ msgstr "Organisations" msgid "browse-panel.themes" msgstr "Catégories" +#: app/login/components/profile-header.tsx +msgid "browse.dataset.all" +msgstr "Parcourir tous les ensembles de données" + #: app/browser/dataset-preview.tsx msgid "browse.dataset.create-visualization" msgstr "Démarrer une visualisation" @@ -239,6 +247,18 @@ msgstr "Barres" msgid "controls.chart.type.column" msgstr "Colonnes" +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineColumn" +msgstr "Column-line" + +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineDual" +msgstr "Dual-axis line" + +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineSingle" +msgstr "Multi-line" + #: app/configurator/components/field-i18n.ts msgid "controls.chart.type.line" msgstr "Lignes" @@ -481,6 +501,7 @@ msgstr "Retour aux paramètres généraux" msgid "controls.nav.back-to-preview" msgstr "Retour à l'aperçu" +#: app/configurator/components/chart-options-selector.tsx #: app/configurator/components/field.tsx #: app/configurator/components/field.tsx msgid "controls.none" @@ -735,6 +756,7 @@ msgstr "Appliquer les filtres" msgid "controls.size" msgstr "Taille" +#: app/configurator/components/chart-options-selector.tsx #: app/configurator/table/table-chart-sorting-options.tsx msgid "controls.sorting.addDimension" msgstr "Ajouter une dimension" @@ -1068,6 +1090,69 @@ msgstr "Masquer les filtres" msgid "interactive.data.filters.show" msgstr "Afficher les filtres" +#: app/login/components/profile-tables.tsx +msgid "login.chart.copy" +msgstr "Copie" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.delete" +msgstr "Supprimer" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.delete.confirmation" +msgstr "Êtes-vous sûr de vouloir supprimer cette carte ?" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.edit" +msgstr "Editer" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.share" +msgstr "Partager" + +#: app/login/components/profile-tables.tsx +msgid "login.create-chart" +msgstr "créez-en un" + +#: app/login/components/profile-tables.tsx +msgid "login.no-charts" +msgstr "Pas encore de graphiques" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.chart.confirmation.default" +msgstr "Êtes-vous sûr de vouloir effectuer cette action ?" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.chart.delete.warning" +msgstr "Gardez à l'esprit que la suppression de cette visualisation affectera tous les endroits où elle est déjà intégrée !" + +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations" +msgstr "Mes visualisations" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-actions" +msgstr "Actions" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-name" +msgstr "Nom" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-published-date" +msgstr "Publié" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-type" +msgstr "Type" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.dataset-name" +msgstr "Ensemble de données" + #: app/components/header.tsx #: app/components/header.tsx msgid "logo.swiss.confederation" @@ -1093,6 +1178,10 @@ msgstr "Passer à la vue graphique" msgid "metadata.switch.table" msgstr "Passer à la vue en tableau" +#: app/login/components/profile-tables.tsx +msgid "no" +msgstr "Non" + #: app/components/publish-actions.tsx msgid "publication.embed.AEM" msgstr "Code pour intégrer sur AEM comme \"External Application\"" @@ -1162,6 +1251,11 @@ msgstr "Chercher" msgid "select.controls.metadata.search" msgstr "Sauter à..." +#: app/browser/dataset-browse.tsx +#: app/login/components/profile-tables.tsx +msgid "show.all" +msgstr "Tout afficher" + #: app/configurator/components/chart-controls/drag-and-drop-tab.tsx msgid "table.column.no" msgstr "Colonne {0}" @@ -1171,3 +1265,7 @@ msgstr "Colonne {0}" #: app/components/chart-footnotes.tsx msgid "typography.colon" msgstr " : " + +#: app/login/components/profile-tables.tsx +msgid "yes" +msgstr "Oui" diff --git a/app/locales/it/messages.po b/app/locales/it/messages.po index 9cc5a8572..117c2ac54 100644 --- a/app/locales/it/messages.po +++ b/app/locales/it/messages.po @@ -17,6 +17,10 @@ msgstr "" msgid "Add filter" msgstr "Aggiungi filtro" +#: app/configurator/components/chart-options-selector.tsx +msgid "Axis orientation" +msgstr "Orientamento dell'asse" + #: app/configurator/components/chart-configurator.tsx msgid "Drag filters to reorganize" msgstr "Trascina i filtri per riorganizzarli" @@ -50,6 +54,10 @@ msgstr "Organizzazioni" msgid "browse-panel.themes" msgstr "Categorie" +#: app/login/components/profile-header.tsx +msgid "browse.dataset.all" +msgstr "Sfoglia tutti i set di dati" + #: app/browser/dataset-preview.tsx msgid "browse.dataset.create-visualization" msgstr "Iniziare una visualizzazione" @@ -239,6 +247,18 @@ msgstr "Barre" msgid "controls.chart.type.column" msgstr "Colonne" +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineColumn" +msgstr "Column-line" + +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineDual" +msgstr "Dual-axis line" + +#: app/configurator/components/field-i18n.ts +msgid "controls.chart.type.comboLineSingle" +msgstr "Multi-line" + #: app/configurator/components/field-i18n.ts msgid "controls.chart.type.line" msgstr "Linee" @@ -481,6 +501,7 @@ msgstr "Torna alle impostazioni generali" msgid "controls.nav.back-to-preview" msgstr "Torna all'anteprima" +#: app/configurator/components/chart-options-selector.tsx #: app/configurator/components/field.tsx #: app/configurator/components/field.tsx msgid "controls.none" @@ -735,6 +756,7 @@ msgstr "Appliquer les filtres" msgid "controls.size" msgstr "Grandezza" +#: app/configurator/components/chart-options-selector.tsx #: app/configurator/table/table-chart-sorting-options.tsx msgid "controls.sorting.addDimension" msgstr "Aggiungi una dimensione" @@ -1068,6 +1090,69 @@ msgstr "Nascondi i filtri" msgid "interactive.data.filters.show" msgstr "Mostra i filtri" +#: app/login/components/profile-tables.tsx +msgid "login.chart.copy" +msgstr "Copia" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.delete" +msgstr "Cancellare" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.delete.confirmation" +msgstr "Sei sicuro di voler cancellare questo grafico?" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.edit" +msgstr "Modifica" + +#: app/login/components/profile-tables.tsx +msgid "login.chart.share" +msgstr "Condividi" + +#: app/login/components/profile-tables.tsx +msgid "login.create-chart" +msgstr "creane uno" + +#: app/login/components/profile-tables.tsx +msgid "login.no-charts" +msgstr "Non ci sono ancora grafici" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.chart.confirmation.default" +msgstr "Siete sicuri di voler eseguire questa azione?" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.chart.delete.warning" +msgstr "Tenete presente che la rimozione di questa visualizzazione avrà effetto su tutti i luoghi in cui potrebbe essere già incorporata!" + +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-content-tabs.tsx +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations" +msgstr "Le mie visualizzazioni" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-actions" +msgstr "Azioni" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-name" +msgstr "Nome" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-published-date" +msgstr "Pubblicato" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.chart-type" +msgstr "Tipo" + +#: app/login/components/profile-tables.tsx +msgid "login.profile.my-visualizations.dataset-name" +msgstr "Set di dati" + #: app/components/header.tsx #: app/components/header.tsx msgid "logo.swiss.confederation" @@ -1093,6 +1178,10 @@ msgstr "Passare alla visualizzazione del grafico" msgid "metadata.switch.table" msgstr "Passare alla vista tabella" +#: app/login/components/profile-tables.tsx +msgid "no" +msgstr "No" + #: app/components/publish-actions.tsx msgid "publication.embed.AEM" msgstr "Codice da incorporare su AEM come \"External Application\"" @@ -1162,6 +1251,11 @@ msgstr "Cerca" msgid "select.controls.metadata.search" msgstr "Salta a..." +#: app/browser/dataset-browse.tsx +#: app/login/components/profile-tables.tsx +msgid "show.all" +msgstr "Mostra tutti" + #: app/configurator/components/chart-controls/drag-and-drop-tab.tsx msgid "table.column.no" msgstr "Colonna {0}" @@ -1171,3 +1265,7 @@ msgstr "Colonna {0}" #: app/components/chart-footnotes.tsx msgid "typography.colon" msgstr ": " + +#: app/login/components/profile-tables.tsx +msgid "yes" +msgstr "Sì" diff --git a/app/login/components/login-menu.tsx b/app/login/components/login-menu.tsx index 84a4b0537..28b883f2a 100644 --- a/app/login/components/login-menu.tsx +++ b/app/login/components/login-menu.tsx @@ -1,6 +1,6 @@ -import { Box, Button, Typography } from "@mui/material"; +import { Box, Button, Link, Typography } from "@mui/material"; import { signIn } from "next-auth/react"; -import Link from "next/link"; +import NextLink from "next/link"; import { useUser } from "@/login/utils"; @@ -11,9 +11,11 @@ export const LoginMenu = () => { {user ? ( - - {user.name} - {" "} + + + {user.name} + + ) : ( + + ); +}; diff --git a/app/login/components/profile-tables.tsx b/app/login/components/profile-tables.tsx index 3f2dcb96f..f0f4e239a 100644 --- a/app/login/components/profile-tables.tsx +++ b/app/login/components/profile-tables.tsx @@ -1,106 +1,457 @@ +import { Trans, t } from "@lingui/macro"; import { Box, + Button, + CircularProgress, + ClickAwayListener, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + IconButton, Link, + Skeleton, Table, TableBody, TableCell, TableHead, TableRow, + Tooltip, Typography, } from "@mui/material"; import NextLink from "next/link"; +import React from "react"; +import useDisclosure from "@/components/use-disclosure"; import { ParsedConfig } from "@/db/config"; +import { sourceToLabel } from "@/domain/datasource"; import { useDataCubeMetadataQuery } from "@/graphql/query-hooks"; -import { Icon } from "@/icons"; +import { Icon, IconName } from "@/icons"; import { useRootStyles } from "@/login/utils"; import { useLocale } from "@/src"; +import { removeConfig } from "@/utils/chart-config/api"; + +const PREVIEW_LIMIT = 3; + +type ProfileTableProps = React.PropsWithChildren<{ + title: string; + preview?: boolean; + onShowAll?: () => void; +}>; + +const ProfileTable = (props: ProfileTableProps) => { + const { title, preview, onShowAll, children } = props; + const rootClasses = useRootStyles(); + + return ( + + + {title} + + {children}
+ {preview && ( + + )} +
+ ); +}; type ProfileVisualizationsTableProps = { + userId: number; userConfigs: ParsedConfig[]; + setUserConfigs: React.Dispatch>; + preview?: boolean; + onShowAll?: () => void; }; export const ProfileVisualizationsTable = ( props: ProfileVisualizationsTableProps ) => { - const { userConfigs } = props; - const rootClasses = useRootStyles(); + const { userId, userConfigs, setUserConfigs, preview, onShowAll } = props; + const onRemoveSuccess = React.useCallback( + (key: string) => { + setUserConfigs((prev) => prev.filter((d) => d.key !== key)); + }, + [setUserConfigs] + ); return ( - - My visualizations + PREVIEW_LIMIT} + onShowAll={onShowAll} + > {userConfigs.length > 0 ? ( - - - Chart type - Name - Dataset - Actions + <> + .MuiTableCell-root": { + borderBottomColor: "divider", + color: "secondary.main", + }, + }} + > + + + Type + + + + + Name + + + + + Dataset + + + + + Published + + + + + Actions + + - {userConfigs.map((d) => ( - - ))} + {userConfigs + .slice(0, preview ? PREVIEW_LIMIT : undefined) + .map((config) => ( + + ))} -
+ ) : ( - No charts yet,{" "} + No charts yet,{" "} - create one + create one . )} -
+ ); }; -type RowProps = { +type ProfileVisualizationsRowProps = { + userId: number; config: ParsedConfig; + onRemoveSuccess: (key: string) => void; }; -const Row = (props: RowProps) => { - const { config } = props; +const ProfileVisualizationsRow = (props: ProfileVisualizationsRowProps) => { + const { userId, config, onRemoveSuccess } = props; + const { dataSet, dataSource } = config.data; const locale = useLocale(); - const [{ data }] = useDataCubeMetadataQuery({ + const [{ data, fetching }] = useDataCubeMetadataQuery({ variables: { - iri: config.data.dataSet, - sourceType: config.data.dataSource.type, - sourceUrl: config.data.dataSource.url, + iri: dataSet, + sourceType: dataSource.type, + sourceUrl: dataSource.url, locale, }, }); + const actions = React.useMemo(() => { + const actions: ActionProps[] = [ + { + type: "link", + href: `/create/new?copy=${config.key}`, + label: t({ id: "login.chart.copy", message: "Copy" }), + iconName: "copy", + }, + { + type: "link", + href: `/create/new?edit=${config.key}`, + label: t({ id: "login.chart.edit", message: "Edit" }), + iconName: "edit", + }, + { + type: "link", + href: `/v/${config.key}`, + label: t({ id: "login.chart.share", message: "Share" }), + iconName: "linkExternal", + }, + { + type: "button", + label: t({ id: "login.chart.delete", message: "Delete" }), + iconName: "trash", + requireConfirmation: true, + confirmationTitle: t({ + id: "login.chart.delete.confirmation", + message: "Are you sure you want to delete this chart?", + }), + confirmationText: t({ + id: "login.profile.chart.delete.warning", + message: + "Keep in mind that removing this visualization will affect all the places where it might be already embedded!", + }), + onClick: async () => { + await removeConfig({ key: config.key, userId }); + }, + onSuccess: () => { + onRemoveSuccess(config.key); + }, + }, + ]; - console.log(config); + return actions; + }, [config.key, onRemoveSuccess, userId]); return ( - - - {config.data.chartConfigs.length > 1 - ? "multi" - : config.data.chartConfigs[0].chartType} + .MuiTableCell-root": { + borderBottomColor: "divider", + }, + }} + > + + + {config.data.chartConfigs.length > 1 ? "multi" : "single"} + - {config.data.meta.title[locale]} - {data?.dataCubeByIri?.title ?? ""} - - - - - - - + + + {config.data.meta.title[locale]} + + + + {fetching ? ( + + ) : ( - - + + + {data?.dataCubeByIri?.title ?? ""} + - + )} + + + + {config.created_at.toLocaleDateString("de")} + + + + ); }; + +type ActionsProps = { + actions: ActionProps[]; +}; + +const Actions = (props: ActionsProps) => { + const { actions } = props; + const buttonRef = React.useRef(null); + const { isOpen, open, close } = useDisclosure(); + + return ( + + + {actions.map((props, i) => ( + + ))} + + } + sx={{ p: 2 }} + componentsProps={{ tooltip: { sx: { p: 3, pb: 2 } } }} + > + + + + + + ); +}; + +type ActionProps = ActionLinkProps | ActionButtonProps; + +const Action = (props: ActionProps) => { + switch (props.type) { + case "link": + return ; + case "button": + return ; + default: + const _exhaustiveCheck: never = props; + return _exhaustiveCheck; + } +}; + +type ActionLinkProps = { + type: "link"; + href: string; + label: string; + iconName: IconName; +}; + +const ActionLink = (props: ActionLinkProps) => { + const { href, label, iconName } = props; + + return ( + + + + {label} + + + ); +}; + +type ActionButtonProps = { + type: "button"; + label: string; + iconName: IconName; + requireConfirmation?: boolean; + confirmationTitle?: string; + confirmationText?: string; + onClick: () => Promise; + onDialogClose?: () => void; + onSuccess?: () => void; +}; + +const ActionButton = (props: ActionButtonProps) => { + const { + label, + iconName, + requireConfirmation, + confirmationTitle, + confirmationText, + onClick, + onDialogClose, + onSuccess, + } = props; + const { isOpen, open, close } = useDisclosure(); + const [loading, setLoading] = React.useState(false); + + return ( + <> + { + // To prevent the click away listener from closing the dialog. + e.stopPropagation(); + + if (requireConfirmation) { + open(); + } else { + onClick(); + } + }} + sx={{ + display: "flex", + alignItems: "center", + gap: 1, + color: "primary.main", + cursor: "pointer", + }} + > + + {label} + + {requireConfirmation && ( + e.stopPropagation()} + onClose={close} + maxWidth="xs" + > + + + {confirmationTitle ?? + t({ + id: "login.profile.chart.confirmation.default", + message: "Are you sure you want to perform this action?", + })} + + + {confirmationText && ( + + {confirmationText} + + )} + .MuiButton-root": { + justifyContent: "center", + pointerEvents: loading ? "none" : "auto", + }, + }} + > + + + + + )} + + ); +}; diff --git a/app/pages/api/config-remove.ts b/app/pages/api/config-remove.ts new file mode 100644 index 000000000..398b5a002 --- /dev/null +++ b/app/pages/api/config-remove.ts @@ -0,0 +1,23 @@ +import { getServerSession } from "next-auth"; + +import { removeConfig } from "@/db/config"; + +import { api } from "../../server/nextkit"; + +import { nextAuthOptions } from "./auth/[...nextauth]"; + +const route = api({ + POST: async ({ req, res }) => { + const session = await getServerSession(req, res, nextAuthOptions); + const serverUserId = session?.user?.id; + const { key, userId } = req.body; + + if (serverUserId !== userId) { + throw new Error("Unauthorized!"); + } + + return await removeConfig({ key }); + }, +}); + +export default route; diff --git a/app/pages/profile.tsx b/app/pages/profile.tsx index 73566df6a..e9909209c 100644 --- a/app/pages/profile.tsx +++ b/app/pages/profile.tsx @@ -53,7 +53,7 @@ const ProfilePage = (props: Serialized) => { - + ); diff --git a/app/pages/v/[chartId].tsx b/app/pages/v/[chartId].tsx index 6391be31a..4682a80bd 100644 --- a/app/pages/v/[chartId].tsx +++ b/app/pages/v/[chartId].tsx @@ -190,7 +190,7 @@ const VisualizationPage = (props: Serialized) => { diff --git a/app/utils/chart-config/api.ts b/app/utils/chart-config/api.ts index dececca95..cd15ded75 100644 --- a/app/utils/chart-config/api.ts +++ b/app/utils/chart-config/api.ts @@ -62,6 +62,21 @@ export const updateConfig = async ( ); }; +export const removeConfig = async (options: UpdateConfigOptions) => { + const { key, userId } = options; + + return apiFetch>( + "/api/config-remove", + { + method: "POST", + data: { + key, + userId, + }, + } + ); +}; + export const fetchChartConfig = async (id: string) => { return await apiFetch>( `/api/config/${id}`