Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map gender fields on CSV import #2401

Merged
merged 7 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { FC } from 'react';
import { Box, Divider, Typography } from '@mui/material';

import messageIds from 'features/import/l10n/messageIds';
import { GenderColumn } from 'features/import/utils/types';
import { UIDataColumn } from 'features/import/hooks/useUIDataColumn';
import { Msg, useMessages } from 'core/i18n';
import GenderConfigRow from './GenderConfigRow';
import useGenderMapping from 'features/import/hooks/useGenderMapping';

interface GenderConfigProps {
uiDataColumn: UIDataColumn<GenderColumn>;
}

const GenderConfig: FC<GenderConfigProps> = ({ uiDataColumn }) => {
const messages = useMessages(messageIds);
const { selectGender, getSelectedGender, deselectGender } = useGenderMapping(
uiDataColumn.originalColumn,
uiDataColumn.columnIndex
);

return (
<Box
display="flex"
flexDirection="column"
overflow="hidden"
padding={2}
sx={{ overflowY: 'auto' }}
>
<Box alignItems="baseline" display="flex" justifyContent="space-between">
<Typography sx={{ paddingBottom: 2 }} variant="h5">
<Msg id={messageIds.configuration.configure.tags.header} />
</Typography>
</Box>
<Box alignItems="center" display="flex" paddingY={2}>
<Box width="50%">
<Typography variant="body2">
{uiDataColumn.title.toLocaleUpperCase()}
</Typography>
</Box>
<Box width="50%">
<Typography variant="body2">
{messages.configuration.configure.genders
.label()
.toLocaleUpperCase()}
</Typography>
</Box>
</Box>
{uiDataColumn.uniqueValues.map((uniqueValue, index) => (
<>
{index != 0 && <Divider sx={{ marginY: 1 }} />}
<GenderConfigRow
numRows={uiDataColumn.numRowsByUniqueValue[uniqueValue]}
onDeselectGender={() => deselectGender(uniqueValue)}
onSelectGender={(gender) => selectGender(gender, uniqueValue)}
selectedGender={getSelectedGender(uniqueValue)}
title={uniqueValue.toString()}
/>
</>
))}
</Box>
);
};

export default GenderConfig;
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { ArrowForward, Delete } from '@mui/icons-material';
import {
Box,
FormControl,
IconButton,
InputLabel,
MenuItem,
Select,
Typography,
} from '@mui/material';
import { FC } from 'react';

import messageIds from 'features/import/l10n/messageIds';
import { Msg, useMessages } from 'core/i18n';
import { Gender, genders } from '../../../../hooks/useGenderMapping';

interface GenderConfigRowProps {
italic?: boolean;
numRows: number;
onSelectGender: (gender: Gender | null) => void;
onDeselectGender: () => void;
selectedGender: Gender | 'unknown' | null;
title: string;
}

const GenderConfigRow: FC<GenderConfigRowProps> = ({
italic,
numRows,
onSelectGender,
onDeselectGender,
selectedGender,
title,
}) => {
const messages = useMessages(messageIds);
return (
<Box display="flex" flexDirection="column">
<Box display="flex">
<Box
alignItems="flex-start"
display="flex"
justifyContent="space-between"
paddingTop={1}
width="50%"
>
<Box display="flex" sx={{ wordBreak: 'break-all' }} width="100%">
<Typography fontStyle={italic ? 'italic' : ''}>{title}</Typography>
</Box>
<ArrowForward color="secondary" sx={{ marginRight: 1 }} />
</Box>
<Box
alignItems="flex-start"
display="flex"
paddingRight={1}
width="50%"
>
<FormControl fullWidth size="small">
<InputLabel>
<Msg id={messageIds.configuration.configure.genders.label} />
</InputLabel>
<Select
label={messages.configuration.configure.genders.label()}
onChange={(event) => {
const { value } = event.target;
if (value === 'm' || value === 'f' || value === 'o') {
onSelectGender(value);
} else if (value === 'unknown') {
onSelectGender(null);
}
}}
value={selectedGender}
>
{genders.map((key) => (
<MenuItem key={key} value={key}>
<Msg
id={
messageIds.configuration.configure.genders.selectLabels[
key
]
}
/>
</MenuItem>
))}
<MenuItem value="unknown">
<Msg
id={
messageIds.configuration.configure.genders.selectLabels
.unknown
}
/>
</MenuItem>
</Select>
</FormControl>
<IconButton
onClick={() => {
onDeselectGender();
}}
>
<Delete color="secondary" />
</IconButton>
</Box>
</Box>
<Typography color="secondary">
<Msg
id={messageIds.configuration.configure.tags.numberOfRows}
values={{ numRows }}
/>
</Typography>
</Box>
);
};

export default GenderConfigRow;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ColumnKind,
DateColumn,
EnumColumn,
GenderColumn,
IDFieldColumn,
OrgColumn,
TagColumn,
Expand All @@ -17,6 +18,7 @@ import useUIDataColumn, {
UIDataColumn,
} from 'features/import/hooks/useUIDataColumn';
import EnumConfig from './EnumConfig';
import GenderConfig from './GenderConfig';

interface ConfigurationProps {
columnIndexBeingConfigured: number;
Expand Down Expand Up @@ -49,6 +51,12 @@ const Configuration: FC<ConfigurationProps> = ({
uiDataColumn.originalColumn.kind == ColumnKind.ORGANIZATION && (
<OrgConfig uiDataColumn={uiDataColumn as UIDataColumn<OrgColumn>} />
)}
{uiDataColumn &&
uiDataColumn.originalColumn.kind == ColumnKind.GENDER && (
<GenderConfig
uiDataColumn={uiDataColumn as UIDataColumn<GenderColumn>}
/>
)}
{uiDataColumn && uiDataColumn.originalColumn.kind == ColumnKind.DATE && (
<DateConfig uiDataColumn={uiDataColumn as UIDataColumn<DateColumn>} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const FieldSelect: FC<FieldSelectProps> = ({
return `enum:${column.originalColumn.field}`;
}

if (column.originalColumn.kind == ColumnKind.GENDER) {
return `field:gender`;
}

if (column.originalColumn.kind != ColumnKind.UNKNOWN) {
return column.originalColumn.kind.toString();
}
Expand Down Expand Up @@ -90,6 +94,14 @@ const FieldSelect: FC<FieldSelectProps> = ({
selected: true,
});
onConfigureStart();
} else if (event.target.value == 'field:gender') {
onChange({
field: event.target.value,
kind: ColumnKind.GENDER,
mapping: [],
selected: true,
});
onConfigureStart();
} else if (event.target.value.startsWith('field')) {
onChange({
field: event.target.value.slice(6),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const isConfigurableColumn = (column: Column): column is ConfigurableColumn => {
ColumnKind.TAG,
ColumnKind.DATE,
ColumnKind.ENUM,
ColumnKind.GENDER,
].includes(column.kind);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import messageIds from 'features/import/l10n/messageIds';
import PreviewGrid from './PreviewGrid';
import { useMessages } from 'core/i18n';
import { CellData, ColumnKind, Sheet } from 'features/import/utils/types';

interface GenderPreviewProps {
currentSheet: Sheet;
fieldKey: string;
fields: Record<string, CellData> | undefined;
}

const GenderPreview = ({
currentSheet,
fieldKey,
fields,
}: GenderPreviewProps) => {
const messages = useMessages(messageIds);

const map = currentSheet.columns.find(
(column) => column.kind === ColumnKind.GENDER && column.mapping.length > 0
);

if (!map) {
return (
<PreviewGrid
columnHeader={messages.configuration.preview.columnHeader.gender()}
rowValue={null}
unmappedRow={true}
/>
);
}

const value = fields?.[fieldKey];

if (value === 'o' || value === 'f' || value === 'm' || value === null) {
const key = value === null ? 'unknown' : value;
return (
<PreviewGrid
columnHeader={messages.configuration.preview.columnHeader.gender()}
rowValue={messages.configuration.preview.genders[key]()}
unmappedRow={false}
/>
);
}

// This should never happen
return (
<PreviewGrid
columnHeader={messages.configuration.preview.columnHeader.gender()}
emptyLabel={messages.configuration.preview.noValue()}
rowValue={null}
unmappedRow={false}
/>
);
};

export default GenderPreview;
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import usePersonPreview from 'features/import/hooks/usePersonPreview';
import useSheets from 'features/import/hooks/useSheets';
import { ColumnKind, Sheet } from 'features/import/utils/types';
import EnumPreview from './EnumPreview';
import GenderPreview from './GenderPreview';

const Preview = () => {
const theme = useTheme();
Expand Down Expand Up @@ -155,6 +156,16 @@ const Preview = () => {
/>
);
}
if (column.kind === ColumnKind.GENDER) {
return (
<GenderPreview
key={columnIdx}
currentSheet={currentSheet}
fieldKey={column.field}
fields={fields}
/>
);
}
}
})}
{orgColumnSelected && (
Expand Down
67 changes: 67 additions & 0 deletions src/features/import/hooks/useGenderMapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { columnUpdate } from '../store';
import { useAppDispatch } from 'core/hooks';
import { CellData, Column, ColumnKind } from '../utils/types';

export const genders = ['f', 'm', 'o'] as const;
export type Gender = typeof genders[keyof typeof genders];

export default function useGenderMapping(column: Column, columnIndex: number) {
const dispatch = useAppDispatch();

const getSelectedGender = (value: CellData) => {
if (column.kind == ColumnKind.GENDER) {
const map = column.mapping.find((m) => m.value === value);
if (!map) {
return null;
}
return map.gender ?? 'unknown';
}
return null;
};

const selectGender = (gender: Gender | null, value: CellData) => {
if (column.kind !== ColumnKind.GENDER) {
return;
}

dispatch(
columnUpdate([
columnIndex,
{
...column,
mapping: [
...column.mapping.filter((m) => m.value !== value),
{ gender, value },
],
},
])
);
};

const deselectGender = (value: CellData) => {
if (column.kind != ColumnKind.GENDER) {
return;
}

const map = column.mapping.find((map) => map.value == value);
if (map) {
const filteredMapping = column.mapping.filter((m) => m.value != value);

dispatch(
columnUpdate([
columnIndex,
{
...column,
mapping: filteredMapping,
},
])
);
}
};

return {
deselectGender,
getSelectedGender,
selectGender,
};
}
Loading
Loading