Skip to content

Commit

Permalink
Merge pull request #2401 from k-nut/issue-2348/import-gender
Browse files Browse the repository at this point in the history
Map gender fields on CSV import
  • Loading branch information
ziggabyte authored Dec 10, 2024
2 parents dc5b151 + 066cb5b commit 2c0177a
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 0 deletions.
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 @@ -43,6 +43,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 @@ -106,6 +110,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

0 comments on commit 2c0177a

Please sign in to comment.