Skip to content

Commit a3b5258

Browse files
authored
Merge pull request #54 from Lemoncode/feature/create-modal-for-records
Feature/create modal for records
2 parents 14c3d30 + 18012b8 commit a3b5258

23 files changed

+379
-5
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import { Record } from './create-record.vm';
3+
4+
interface CreateRecordContextProps {
5+
formData: Record;
6+
activeStep: number;
7+
isOpen: boolean;
8+
onOpen: () => void;
9+
onNextStep: <K extends keyof Record>(step: K, value: Record[K]) => void;
10+
onPreviousStep: () => void;
11+
onCancel: () => void;
12+
}
13+
14+
export const CreateRecordContext = React.createContext<CreateRecordContextProps | undefined>(undefined);
15+
16+
export const useCreateRecordContext = () => {
17+
const context = React.useContext(CreateRecordContext);
18+
if (!context) {
19+
throw new Error('useCreateRecordContext must be used within a CreateRecordProvider');
20+
}
21+
return context;
22+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from 'react';
2+
import { CreateRecordContext } from './create-record.context';
3+
import { createEmptyRecordFormData, Record, Steps } from './create-record.vm';
4+
5+
interface Props {
6+
children: React.ReactNode;
7+
}
8+
9+
export const CreateRecordProvider: React.FC<Props> = props => {
10+
const { children } = props;
11+
12+
const [formData, setFormData] = React.useState<Record>(createEmptyRecordFormData);
13+
const [activeStep, setActiveStep] = React.useState<number>(Steps.generalData);
14+
const [isOpen, setIsOpen] = React.useState<boolean>(false);
15+
16+
const reset = () => {
17+
setFormData(createEmptyRecordFormData());
18+
setActiveStep(Steps.generalData);
19+
};
20+
21+
const updateStepData = <K extends keyof Record>(step: K, data: Record[K]) =>
22+
setFormData(prev => ({ ...prev, [step]: data }));
23+
24+
const toggleModal = () => setIsOpen(!isOpen);
25+
const isLastStep = activeStep === Steps.temporality;
26+
27+
const handleSubmitAll = async (data: Record) => {
28+
console.log('Formulario enviado:', data);
29+
toggleModal();
30+
alert('Formulario enviado con éxito');
31+
};
32+
33+
const handleNextStep = <K extends keyof Record>(step: K, value: Record[K]) => {
34+
updateStepData(step, value);
35+
if (!isLastStep) {
36+
setActiveStep(prev => prev + 1);
37+
} else {
38+
handleSubmitAll({ ...formData, [step]: value });
39+
}
40+
};
41+
42+
const handlePreviusStep = () => setActiveStep(prev => prev - 1);
43+
44+
React.useEffect(() => {
45+
if (!isOpen) {
46+
reset();
47+
}
48+
}, [isOpen]);
49+
50+
return (
51+
<CreateRecordContext.Provider
52+
value={{
53+
formData,
54+
activeStep,
55+
isOpen,
56+
onOpen: toggleModal,
57+
onNextStep: handleNextStep,
58+
onPreviousStep: handlePreviusStep,
59+
onCancel: toggleModal,
60+
}}
61+
>
62+
{children}
63+
</CreateRecordContext.Provider>
64+
);
65+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export interface GeneralData {
2+
name: string;
3+
}
4+
5+
export interface Budget {
6+
amount: number;
7+
}
8+
9+
export interface Temporality {
10+
description: string;
11+
}
12+
13+
export interface Record {
14+
generalData: GeneralData;
15+
budget: Budget;
16+
temporality: Temporality;
17+
}
18+
19+
export const createEmptyRecordFormData = (): Record => ({
20+
generalData: {
21+
name: '',
22+
},
23+
budget: {
24+
amount: 0,
25+
},
26+
temporality: {
27+
description: '',
28+
},
29+
});
30+
31+
export enum Steps {
32+
generalData = 1,
33+
budget = 2,
34+
temporality = 3,
35+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './create-record.context';
2+
export * from './create-record.provider';
3+
export * from './create-record.vm';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './create-record';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './steps';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react';
2+
import { Button } from '@mui/material';
3+
import { useWithTheme } from '#core/theme/theme.hooks.ts';
4+
import { Steps, useCreateRecordContext } from '#modules/records/core/providers';
5+
import * as innerClasses from './step-navigation.styles';
6+
7+
export const StepNavigation: React.FC = () => {
8+
const { activeStep, onCancel, onPreviousStep } = useCreateRecordContext();
9+
const classes = useWithTheme(innerClasses);
10+
11+
return (
12+
<div className={classes.buttonContainer}>
13+
<Button onClick={onCancel}>Cancelar</Button>
14+
<div className={classes.buttonGroup}>
15+
{activeStep !== Steps.generalData && (
16+
<Button variant="outlined" onClick={onPreviousStep}>
17+
Anterior
18+
</Button>
19+
)}
20+
<Button type="submit" variant="contained">
21+
{activeStep === Steps.temporality ? 'Guardar' : 'Siguiente'}
22+
</Button>
23+
</div>
24+
</div>
25+
);
26+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { css } from '@emotion/css';
2+
import { Theme } from '@mui/material';
3+
4+
export const buttonContainer = (theme: Theme) => css`
5+
display: flex;
6+
justify-content: space-between;
7+
width: 100%;
8+
padding-top: ${theme.spacing(2)};
9+
`;
10+
11+
export const buttonGroup = (theme: Theme) => css`
12+
display: flex;
13+
gap: ${theme.spacing(1)};
14+
`;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { Form, Formik } from 'formik';
3+
import { TextFieldForm } from '#common/components';
4+
import { useCreateRecordContext, Budget } from '#modules/records/core/providers';
5+
import { StepNavigation } from '../step-navigation.component';
6+
import { budgetValidation } from '../validations';
7+
import * as classes from './steps.styles';
8+
9+
export const BudgetStep: React.FC = () => {
10+
const { formData, onNextStep } = useCreateRecordContext();
11+
12+
const handleSubmit = (values: Budget) => {
13+
onNextStep('budget', values);
14+
};
15+
16+
return (
17+
<Formik
18+
initialValues={formData.budget}
19+
enableReinitialize={true}
20+
onSubmit={handleSubmit}
21+
validate={budgetValidation.validateForm}
22+
>
23+
{() => (
24+
<Form className={classes.form}>
25+
<TextFieldForm type="number" name="amount" label="Cantidad" />
26+
<StepNavigation />
27+
</Form>
28+
)}
29+
</Formik>
30+
);
31+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { Form, Formik } from 'formik';
3+
import { TextFieldForm } from '#common/components';
4+
import { GeneralData, useCreateRecordContext } from '#modules/records/core/providers';
5+
import { generalDataValidation } from '../validations';
6+
import { StepNavigation } from '../step-navigation.component';
7+
import * as classes from './steps.styles';
8+
9+
export const GeneralDataStep: React.FC = () => {
10+
const { formData, onNextStep } = useCreateRecordContext();
11+
12+
const handleSubmit = (values: GeneralData) => {
13+
onNextStep('generalData', values);
14+
};
15+
16+
return (
17+
<Formik
18+
initialValues={formData.generalData}
19+
enableReinitialize={true}
20+
onSubmit={handleSubmit}
21+
validate={generalDataValidation.validateForm}
22+
>
23+
{() => (
24+
<Form className={classes.form}>
25+
<TextFieldForm name="name" label="Nombre" />
26+
<StepNavigation />
27+
</Form>
28+
)}
29+
</Formik>
30+
);
31+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './general-data-step.component';
2+
export * from './budget-step.component';
3+
export * from './temporality-step.component';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { css } from '@emotion/css';
2+
3+
export const form = css`
4+
width: 100%;
5+
`;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { Form, Formik } from 'formik';
3+
import { TextFieldForm } from '#common/components';
4+
import { useCreateRecordContext, Temporality } from '#modules/records/core/providers';
5+
import { StepNavigation } from '../step-navigation.component';
6+
import { temporalityValidation } from '../validations';
7+
import * as classes from './steps.styles';
8+
9+
export const TemporalityStep: React.FC = () => {
10+
const { formData, onNextStep } = useCreateRecordContext();
11+
12+
const handleSubmit = (values: Temporality) => {
13+
onNextStep('temporality', values);
14+
};
15+
16+
return (
17+
<Formik
18+
initialValues={formData.temporality}
19+
enableReinitialize={true}
20+
onSubmit={handleSubmit}
21+
validate={temporalityValidation.validateForm}
22+
>
23+
{() => (
24+
<Form className={classes.form}>
25+
<TextFieldForm name="description" label="Descripcion" />
26+
<StepNavigation />
27+
</Form>
28+
)}
29+
</Formik>
30+
);
31+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Validators } from '@lemoncode/fonk';
2+
import { createFormikValidation } from '@lemoncode/fonk-formik';
3+
4+
const validationSchema = {
5+
field: {
6+
amount: [Validators.required],
7+
},
8+
};
9+
10+
export const budgetValidation = createFormikValidation(validationSchema);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Validators } from '@lemoncode/fonk';
2+
import { createFormikValidation } from '@lemoncode/fonk-formik';
3+
4+
const validationSchema = {
5+
field: {
6+
name: [Validators.required],
7+
},
8+
};
9+
10+
export const generalDataValidation = createFormikValidation(validationSchema);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './general-data-step.validations';
2+
export * from './budget-step.validations';
3+
export * from './temporality-step.validations';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Validators } from '@lemoncode/fonk';
2+
import { createFormikValidation } from '@lemoncode/fonk-formik';
3+
4+
const validationSchema = {
5+
field: {
6+
description: [Validators.required],
7+
},
8+
};
9+
10+
export const temporalityValidation = createFormikValidation(validationSchema);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import { Dialog, DialogContent, DialogTitle, Step, StepLabel, Stepper } from '@mui/material';
3+
import { useWithTheme } from '#core/theme';
4+
import { BudgetStep, GeneralDataStep, TemporalityStep } from './components';
5+
import * as innerClasses from './create-record.styles';
6+
import { useCreateRecordContext } from '../core/providers';
7+
8+
export const CreateRecord: React.FC = () => {
9+
const { activeStep, isOpen, onCancel } = useCreateRecordContext();
10+
const classes = useWithTheme(innerClasses);
11+
12+
const steps = ['Datos generales', 'Presupuesto base', 'Temporalidad'];
13+
14+
const stepComponent = (activeStep: number) => {
15+
switch (activeStep) {
16+
case 1:
17+
return <GeneralDataStep />;
18+
case 2:
19+
return <BudgetStep />;
20+
case 3:
21+
return <TemporalityStep />;
22+
}
23+
};
24+
25+
return (
26+
<Dialog open={isOpen} onClose={onCancel} maxWidth="md" fullWidth>
27+
<DialogTitle>Crear nuevo expediente</DialogTitle>
28+
<DialogContent>
29+
<div className={classes.root}>
30+
<Stepper activeStep={activeStep} className={classes.stepperContainer}>
31+
{steps.map(step => (
32+
<Step key={step}>
33+
<StepLabel>{step}</StepLabel>
34+
</Step>
35+
))}
36+
</Stepper>
37+
{stepComponent(activeStep)}
38+
</div>
39+
</DialogContent>
40+
</Dialog>
41+
);
42+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import React from 'react';
2+
import { CreateRecord } from './create-record.component';
3+
4+
export const CreateRecordPod: React.FC = () => {
5+
return <CreateRecord />;
6+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { css } from '@emotion/css';
2+
import { Theme } from '@mui/material';
3+
4+
export const root = (theme: Theme) => css`
5+
display: flex;
6+
flex-direction: column;
7+
align-items: center;
8+
gap: ${theme.spacing(4)};
9+
`;
10+
11+
export const stepperContainer = css`
12+
max-width: 602px;
13+
width: 100%;
14+
`;

src/modules/records/create/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './create-record.pod';

0 commit comments

Comments
 (0)