Skip to content

Commit

Permalink
feat: add tema section to dataset form
Browse files Browse the repository at this point in the history
  • Loading branch information
hegeaal committed Oct 4, 2024
1 parent 26fa906 commit 0c27fca
Show file tree
Hide file tree
Showing 17 changed files with 383 additions and 18 deletions.
50 changes: 47 additions & 3 deletions apps/dataset-catalog/app/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
getById,
postDataset,
updateDataset as update,
getLosThemes as losThemes,
getDataThemes as dataThemes,
} from '@catalog-frontend/data-access';
import { Dataset, DatasetToBeCreated } from '@catalog-frontend/types';
import { getValidSession, localization, removeEmptyValues } from '@catalog-frontend/utils';
Expand Down Expand Up @@ -36,13 +38,25 @@ export async function getDatasetById(catalogId: string, datasetId: string): Prom
return jsonResponse;
}

const convertListToObjectStructure = (uriList: string[]) => {
return uriList.map((uri) => ({ uri }));
};

export async function createDataset(values: DatasetToBeCreated, catalogId: string) {
const newDataset = removeEmptyValues(values);
const newDataset = {
...values,
theme: [
...(values.losThemeList ? convertListToObjectStructure(values.losThemeList) : []),
...(values.euThemeList ? convertListToObjectStructure(values.euThemeList) : []),
],
};
const datasetNoEmptyValues = removeEmptyValues(newDataset);

const session = await getValidSession();
let success = false;
let datasetId = undefined;
try {
const response = await postDataset(newDataset, catalogId, `${session?.accessToken}`);
const response = await postDataset(datasetNoEmptyValues, catalogId, `${session?.accessToken}`);
if (response.status !== 201) {
throw new Error();
}
Expand Down Expand Up @@ -79,7 +93,15 @@ export async function deleteDataset(catalogId: string, datasetId: string) {
}

export async function updateDataset(catalogId: string, initialDataset: Dataset, values: Dataset) {
const diff = compare(initialDataset, removeEmptyValues(values));
const updatedDataset = removeEmptyValues({
...values,
theme: [
...(values.losThemeList ? convertListToObjectStructure(values.losThemeList) : []),
...(values.euThemeList ? convertListToObjectStructure(values.euThemeList) : []),
],
});

const diff = compare(initialDataset, updatedDataset);

if (diff.length === 0) {
throw new Error(localization.alert.noChanges);
Expand All @@ -105,3 +127,25 @@ export async function updateDataset(catalogId: string, initialDataset: Dataset,
redirect(`/catalogs/${catalogId}/datasets/${initialDataset.id}`);
}
}

export async function getLosThemes() {
const session = await getValidSession();

const response = await losThemes(`${session?.accessToken}`);
if (response.status !== 200) {
throw new Error('getLosThemes failed with response code ' + response.status);
}
const jsonResponse = await response.json();
return jsonResponse.losNodes;
}

export async function getDataThemes() {
const session = await getValidSession();

const response = await dataThemes(`${session?.accessToken}`);
if (response.status !== 200) {
throw new Error('getDataThemes failed with response code ' + response.status);
}
const jsonResponse = await response.json();
return jsonResponse.dataThemes;
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ const DatasetsPageClient = ({ datasets, catalogId }: Props) => {
<SearchHitsPageLayout.LeftColumn>
<div className={styles.leftColumn}>
<div>
<Paragraph>Legg til...</Paragraph>
<Paragraph>{`${localization.add}...`}</Paragraph>
<Select onChange={handleSelectChange}>
<option value='blank'>{`${localization.choose}...`}</option>
<option value=''>{`${localization.choose}...`}</option>
<option value='dataset'>{localization.resourceType.dataset}</option>
<option value='datasetSeries'>{localization.resourceType.datasetSeries}</option>
</Select>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default async function DatasetSearchHitsPage({ params }: Params) {

const datasets: Dataset[] = await getDatasets(catalogId);
const organization: Organization = await getOrganization(catalogId).then((res) => res.json());
const hasWritePermission = await hasOrganizationWritePermission(session.accessToken, catalogId);
const hasWritePermission = hasOrganizationWritePermission(session.accessToken, catalogId);

const breadcrumbList = [
{
Expand Down
49 changes: 49 additions & 0 deletions apps/dataset-catalog/app/context/themes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client';

import { createContext, useContext, useEffect, useState } from 'react';
import { getDataThemes, getLosThemes } from '../../actions/actions';
import { DataTheme, LosTheme } from '@catalog-frontend/types';

interface ThemesContextProps {
losThemes: LosTheme[] | undefined;
dataThemes: DataTheme[] | undefined;
loading: boolean;
}

const ThemesContext = createContext<ThemesContextProps | undefined>(undefined);
ThemesContext.displayName = 'ThemesContext';

const ThemesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [losThemes, setLosThemes] = useState<LosTheme[] | undefined>(undefined);
const [dataThemes, setDataThemes] = useState<DataTheme[] | undefined>(undefined);
const [loading, setLoading] = useState<boolean>(true);

useEffect(() => {
const fetchThemes = async () => {
try {
const los = await getLosThemes();
const data: DataTheme[] = await getDataThemes();
setLosThemes(los.flat());
setDataThemes(data);
} catch (error) {
console.error('Failed to fetch themes:', error);
} finally {
setLoading(false);
}
};

fetchThemes();
}, []);

return <ThemesContext.Provider value={{ losThemes, dataThemes, loading }}>{children}</ThemesContext.Provider>;
};

const useThemes = () => {
const context = useContext(ThemesContext);
if (!context) {
throw new Error('useThemes must be used within a ThemesProvider');
}
return context;
};

export { ThemesProvider, useThemes };
25 changes: 14 additions & 11 deletions apps/dataset-catalog/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Layout, NextAuthProvider } from '@catalog-frontend/ui';
import { localization } from '@catalog-frontend/utils';
import { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { ThemesProvider } from './context/themes';

export const metadata: Metadata = {
title: localization.catalogType.dataset,
Expand All @@ -15,17 +16,19 @@ const RootLayout = ({ children }: { children: React.ReactNode }) => {
<html lang={localization.getLanguage()}>
<body>
<NextAuthProvider>
<Layout
className={font.className}
catalogAdminUrl={process.env.CATALOG_ADMIN_BASE_URI}
fdkRegistrationBaseUrl={`${process.env.CATALOG_PORTAL_BASE_URI}/catalogs`}
adminGuiBaseUrl={process.env.ADMIN_GUI_BASE_URI}
fdkCommunityBaseUrl={process.env.FDK_COMMUNITY_BASE_URI}
fdkBaseUrl={process.env.FDK_BASE_URI}
catalogTitle={localization.catalogType.service}
>
{children}
</Layout>
<ThemesProvider>
<Layout
className={font.className}
catalogAdminUrl={process.env.CATALOG_ADMIN_BASE_URI}
fdkRegistrationBaseUrl={`${process.env.CATALOG_PORTAL_BASE_URI}/catalogs`}
adminGuiBaseUrl={process.env.ADMIN_GUI_BASE_URI}
fdkCommunityBaseUrl={process.env.FDK_COMMUNITY_BASE_URI}
fdkBaseUrl={process.env.FDK_BASE_URI}
catalogTitle={localization.catalogType.service}
>
{children}
</Layout>
</ThemesProvider>
</NextAuthProvider>
</body>
</html>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const AccessRightsSection = ({ errors, values }: AccessRightsSectionProps
label={
<TitleWithTag
title={localization.access}
tagColor='third'
tagColor='info'
tagTitle={localization.tag.recommended}
/>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { Dataset, Option } from '@catalog-frontend/types';
import { FormContainer, TitleWithTag } from '@catalog-frontend/ui';
import { useThemes } from '../../app/context/themes/index';
import { Chip, Combobox, Spinner } from '@digdir/designsystemet-react';
import { getTranslateText, localization } from '@catalog-frontend/utils';
import { FormikErrors, useFormikContext } from 'formik';
import styles from './dataset-form.module.css';

type Props = {
errors: FormikErrors<Dataset>;
values: Dataset;
};

export const TemaSection = ({ errors, values }: Props) => {
const { losThemes, dataThemes, loading } = useThemes();
const { setFieldValue, values: formikValues } = useFormikContext<Dataset>();

const getNameFromLosPath = (path: string): string | string[] => {
const obj = losThemes?.find((obj) => obj.losPaths.includes(path));
return obj ? getTranslateText(obj.name) : [];
};

const getParentNames = (inputPaths: string[]): string => {
const results: string[] = [];

inputPaths.forEach((path) => {
const parts = path.split('/').slice(0, -1);
const parentPath = parts.slice(0, -1).join('/');
const childPath = parts.join('/');

const parentName = getNameFromLosPath(parentPath);
const childName = getNameFromLosPath(childPath);

const formattedResult = `${parentName} - ${childName}`;
results.push(formattedResult);
});

return `${localization.datasetForm.helptext.parentTheme}: ${results.join('; ')}`;
};

const containsFilter = (inputValue: string, option: Option): boolean => {
return option.label.toLowerCase().includes(inputValue.toLowerCase());
};

const handleLosValueChange = (newValues: string[]) => {
setFieldValue('losThemeList', newValues);
};

const handleEuValueChange = (newValues: string[]) => {
setFieldValue('euThemeList', newValues);
};

const handleRemoveLosChip = (valueToRemove: string) => {
const newValues = formikValues.losThemeList && formikValues.losThemeList.filter((value) => value !== valueToRemove);
setFieldValue('losThemeList', newValues);
};

const handleRemoveEuChip = (valueToRemove: string) => {
const newValues = formikValues.euThemeList && formikValues.euThemeList.filter((value) => value !== valueToRemove);
setFieldValue('euThemeList', newValues);
};

return (
<FormContainer>
<FormContainer.Header
title={localization.datasetForm.heading.losTheme}
subtitle={localization.datasetForm.helptext.theme}
/>
<>
<div className={styles.combobox}>
<TitleWithTag
title={localization.datasetForm.fieldLabel.losTheme}
tagTitle={localization.tag.recommended}
tagColor='info'
/>
<Combobox
multiple
hideChips
onValueChange={handleLosValueChange}
virtual
filter={containsFilter}
placeholder={`${localization.search.search}...`}
name='losThemeList'
initialValue={values.losThemeList}
loading={loading}
loadingLabel={localization.loading}
>
<Combobox.Empty>{localization.search.noHits}</Combobox.Empty>
{losThemes?.map((theme) => (
<Combobox.Option
key={theme.uri}
value={theme.uri}
description={getParentNames(theme.losPaths)}
>
{getTranslateText(theme.name)}
</Combobox.Option>
))}
</Combobox>
</div>
<div className={styles.chip}>
{loading ? (
<div className={styles.spinner}>
<Spinner title={localization.loading} />
</div>
) : (
<Chip.Group>
{formikValues?.losThemeList &&
formikValues.losThemeList.map((value) => {
const theme = losThemes?.find((theme) => theme.uri === value);
return (
<Chip.Removable
key={`chip-${value}`}
onClick={() => handleRemoveLosChip(value)}
>
{theme ? getTranslateText(theme.name) : ''}
</Chip.Removable>
);
})}
</Chip.Group>
)}
</div>
</>
<FormContainer.Header
title={localization.datasetForm.heading.euTheme}
subtitle={localization.datasetForm.helptext.theme}
/>
<>
<div className={styles.combobox}>
<TitleWithTag
title={localization.datasetForm.fieldLabel.euTheme}
tagTitle={localization.tag.required}
/>

<Combobox
multiple
hideChips
onValueChange={handleEuValueChange}
filter={containsFilter}
placeholder={`${localization.search.search}...`}
name='euThemeList'
initialValue={values.euThemeList}
error={errors.euThemeList}
loading={loading}
loadingLabel={localization.loading}
>
<Combobox.Empty>{localization.search.noHits}</Combobox.Empty>
{dataThemes?.map((eutheme) => (
<Combobox.Option
key={eutheme.uri}
value={eutheme.uri}
>
{getTranslateText(eutheme.label)}
</Combobox.Option>
))}
</Combobox>
</div>
<div className={styles.chip}>
{loading ? (
<div className={styles.spinner}>
<Spinner title={`${localization.loading}...`} />
</div>
) : (
<Chip.Group>
{formikValues?.euThemeList &&
formikValues.euThemeList.map((value) => {
const theme = dataThemes?.find((theme) => theme.uri === value);
return (
<Chip.Removable
key={`chip-${value}`}
onClick={() => handleRemoveEuChip(value)}
>
{theme ? getTranslateText(theme.label) : ''}
</Chip.Removable>
);
})}
</Chip.Group>
)}
</div>
</>
</FormContainer>
);
};

export default TemaSection;
Loading

0 comments on commit 0c27fca

Please sign in to comment.