Skip to content

Commit

Permalink
feat(createTracker): add objectives creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Clm-Roig committed Mar 18, 2022
1 parent b2630bf commit be174f3
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 21 deletions.
36 changes: 21 additions & 15 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-undef */
import { useState } from 'react';
import { Container } from '@mui/material';
import styled from '@emotion/styled';
Expand All @@ -7,6 +8,9 @@ import {
ThemeProvider,
responsiveFontSizes
} from '@mui/material/styles';
import DateAdapter from '@mui/lab/AdapterDateFns';
import frLocale from 'date-fns/locale/fr';
import { LocalizationProvider } from '@mui/lab';
import { palette, typography } from '../config/CustomTheme';
import { DRAWER_MENU_WIDTH } from '../config/Constants';
import AppBar from './AppBar';
Expand All @@ -33,21 +37,23 @@ function App() {
};

return (
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<Container disableGutters maxWidth={false}>
<AppBar toggleDrawerMenu={toggleDrawerMenu} />
<DrawerMenu
width={DRAWER_MENU_WIDTH}
open={isMenuOpen}
toggleDrawerMenu={toggleDrawerMenu}
/>
<MainContent>
<Router />
</MainContent>
</Container>
</ThemeProvider>
</StyledEngineProvider>
<LocalizationProvider dateAdapter={DateAdapter} locale={frLocale}>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<Container disableGutters maxWidth={false}>
<AppBar toggleDrawerMenu={toggleDrawerMenu} />
<DrawerMenu
width={DRAWER_MENU_WIDTH}
open={isMenuOpen}
toggleDrawerMenu={toggleDrawerMenu}
/>
<MainContent>
<Router />
</MainContent>
</Container>
</ThemeProvider>
</StyledEngineProvider>
</LocalizationProvider>
);
}

Expand Down
140 changes: 140 additions & 0 deletions src/components/TrackerForm/CompletionsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { FC } from 'react';
import { Button, Grid, GridProps, IconButton, TextField, Typography } from '@mui/material';
import styled from '@emotion/styled';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import DeleteIcon from '@mui/icons-material/Delete';
import {
Control,
Controller,
FieldArrayWithId,
UseFieldArrayAppend,
UseFieldArrayRemove
} from 'react-hook-form';
import { FormValues } from './types';

export const FieldsetGrid = styled(Grid)`
border: 1px solid rgba(0, 0, 0, 0.23);
border-radius: 4px;
padding: 8px;
`;

export const CompletionUnitTextField = styled(TextField)`
fieldset {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
`;

export const CompletionQuantityTextField = styled(TextField)`
fieldset {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
`;

interface Props {
append: UseFieldArrayAppend<FormValues, 'requiredCompletions'>;
control: Control<FormValues, any> /* eslint-disable-line @typescript-eslint/no-explicit-any */;
fields: FieldArrayWithId<FormValues, 'requiredCompletions', 'id'>[];
gridProps?: GridProps;
remove: UseFieldArrayRemove;
}

const CompletionsForm: FC<Props> = ({ append, control, fields, gridProps, remove }) => {
return (
<>
{fields.map((field, index) => (
<FieldsetGrid
columns={2}
container
key={field.id}
sx={{
mb: 1
}}
{...gridProps}>
<Grid item xs={2} sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="subtitle1">Objectif n°{index + 1}</Typography>
<IconButton onClick={() => remove(index)} sx={{ p: 0 }}>
<DeleteIcon color="error" />
</IconButton>
</Grid>
<Grid item xs={1}>
<Controller
control={control}
name={`requiredCompletions.${index}.quantity` as const}
rules={{
min: 0,
pattern: /^\d+$/,
required: true
}}
render={({ field: { onChange, value }, fieldState: { error } }) => {
let errorText = '';
if (error) {
switch (error.type) {
case 'min':
errorText = 'La quantité doit être supérieure à 0.';
break;

case 'pattern':
errorText = 'La quantité doit être un nombre.';
break;

case 'required':
errorText = 'La quantité est requise';
break;
}
}
return (
<CompletionUnitTextField
error={!!error}
helperText={error && errorText}
label={'Quantité'}
onChange={onChange}
required
size="small"
inputProps={{
style: {
textAlign: 'right'
}
}}
sx={{ mb: 1 }}
style={{}}
value={value || ''}
/>
);
}}
/>
</Grid>
<Grid item xs={1}>
<Controller
control={control}
name={`requiredCompletions.${index}.unit` as const}
rules={{ required: true }}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<CompletionQuantityTextField
error={!!error}
helperText={error ? 'Une unité est requise' : ''}
label={'Unité'}
onChange={onChange}
required
size="small"
sx={{ mb: 1 }}
value={value}
/>
)}
/>
</Grid>
</FieldsetGrid>
))}
<Button
onClick={() => append({})}
startIcon={<AddCircleOutlineIcon />}
sx={{ mb: 2 }}
variant="contained">
Objectif
</Button>
</>
);
};

export default CompletionsForm;
74 changes: 68 additions & 6 deletions src/components/TrackerForm/TrackerForm.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
import { useForm, Controller } from 'react-hook-form';
import { useForm, Controller, useFieldArray } from 'react-hook-form';
import { Box, Button, TextField, Stack } from '@mui/material';
import DatePicker from '@mui/lab/DatePicker';
import { v4 } from 'uuid';
import Tracker from '../../models/Tracker';
import styled from '@emotion/styled';

import { FormValues } from './types';
import TrackerStatus from '../../models/TrackerStatus';
import { useAppDispatch } from '../../app/hooks';
import { createTracker } from '../../store/trackers/trackersSlice';

type FormValues = Omit<Tracker, 'duration'> & { duration: string }; // duration is used as a string for input control here
import CompletionsForm from './CompletionsForm';

export const CompletionUnitTextField = styled(TextField)`
fieldset {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
`;

export const CompletionQuantityTextField = styled(TextField)`
fieldset {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
`;

const getDefaultValues = () => ({
const getDefaultValues = (): FormValues => ({
id: v4(),
beginDate: new Date().toString(),
duration: '',
entries: [],
name: '',
requiredCompletions: [],
requiredCompletions: [
{
quantity: '1',
unit: 'fois'
}
],
status: TrackerStatus.active
});

function TrackerForm() {
const { control, handleSubmit, reset } = useForm<FormValues>({
defaultValues: getDefaultValues()
});
const { fields, append, remove } = useFieldArray({
control, // control props comes from useForm
name: 'requiredCompletions'
});

const dispatch = useAppDispatch();

Expand All @@ -33,7 +59,14 @@ function TrackerForm() {
dispatch(
createTracker({
...data,
duration: parseInt(data.duration)
beginDate: data.beginDate.toString(),
duration: parseInt(data.duration),
requiredCompletions: [
...data.requiredCompletions.map((c) => ({
quantity: parseInt(c.quantity),
unit: c.unit
}))
]
})
);
resetToDefault();
Expand All @@ -48,14 +81,39 @@ function TrackerForm() {
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
error={!!error}
fullWidth
helperText={error ? 'Un nom est requis' : ''}
label={'Nom du tracker'}
onChange={onChange}
required
sx={{ mb: 2 }}
value={value}
/>
)}
/>

<Controller
name={'beginDate'}
control={control}
rules={{ required: true }}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<DatePicker
onChange={onChange}
value={value}
renderInput={(params) => (
<TextField
{...params}
error={!!error}
helperText={error ? 'Une date de début est requise.' : ''}
label={'Début du tracker'}
required
sx={{ mb: 2 }}
/>
)}
/>
)}
/>

<Controller
control={control}
name={'duration'}
Expand All @@ -76,6 +134,7 @@ function TrackerForm() {
return (
<TextField
error={!!error}
fullWidth
helperText={error && errorText}
label={'Durée du tracker (en jours)'}
onChange={onChange}
Expand All @@ -85,6 +144,9 @@ function TrackerForm() {
);
}}
/>

<CompletionsForm append={append} control={control} fields={fields} remove={remove} />

<Stack direction="row" justifyContent="center" spacing={1}>
<Button type="submit" onClick={handleSubmit(onSubmit)} variant={'outlined'}>
Créer
Expand Down
7 changes: 7 additions & 0 deletions src/components/TrackerForm/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Tracker from '../../models/Tracker';

// Number "duration" and "requiredCompletions.quantity" are used as a string for input control here.
export type FormValues = Omit<Tracker, 'duration' | 'requiredCompletions'> & {
duration: string;
requiredCompletions: Array<{ quantity: string; unit: string }>;
};
6 changes: 6 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

/* MUI doesn't use fieldset, copy their Input Outlined border here. */
fieldset {
border: 1px solid rgba(0, 0, 0, 0.23);
border-radius: 4px;
}

0 comments on commit be174f3

Please sign in to comment.