Skip to content

Commit db54499

Browse files
committed
EditProject: switch client/endCustomer. Use a hook for setting new client
1 parent 6bc0be0 commit db54499

File tree

11 files changed

+180
-197
lines changed

11 files changed

+180
-197
lines changed

frontend/src/components/client/NewClient.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const NewClientForm = ({onFinalize, fullWidth, newClientTypes}: NewClient
6767
if (btwResp && btwResp.valid) {
6868
onFinalize(btwResponseToModel(config, btwResp, newClientTypes));
6969
} else {
70-
onFinalize({...getNewClient(config), btw });
70+
onFinalize({...getNewClient(config), btw, types: newClientTypes ?? [] });
7171
}
7272
}}
7373
onBtwChange={btw => setBtwResponse(btwResponseToModel(config, btw, newClientTypes))}

frontend/src/components/client/controls/ClientSelectWithCreateModal.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,29 @@ export const EndCustomerSelectWithCreateModal = (props: ClientSelectWithCreateMo
2727
);
2828

2929

30-
export const ClientSelectWithCreateModal = ({value, onChange, clientType}: ClientSelectWithCreateModalProps) => {
30+
export const ClientSelectWithCreateModal = ({value, onChange, clientType = 'client'}: ClientSelectWithCreateModalProps) => {
3131
const [modalId, setModalId] = useState<ModalState>(null);
3232
const client = useSelector((state: ConfacState) => state.clients
3333
.filter(c => clientType === undefined || c.types.includes(clientType))
3434
.find(c => c._id === value)
3535
);
3636

37-
const clientTypeName = clientType || 'client';
3837
return (
3938
<>
4039
{modalId && (
4140
<ClientModal
4241
clientId={modalId === 'create' ? null : (client?._id ?? null)}
43-
newClientTypes={[clientTypeName]}
42+
newClientTypes={[clientType]}
4443
show
45-
title={t(`client.createNewModal.${clientTypeName}`)}
44+
title={t(`client.createNewModal.${clientType}`)}
4645
onClose={() => setModalId(null)}
4746
onConfirm={(model: ClientModel) => onChange(model._id, model)}
4847
/>
4948
)}
50-
<SelectWithCreateButton claim={Claim.ManageClients} setModalId={setModalId} createButtonText={`invoice.${clientTypeName}New`}>
49+
<SelectWithCreateButton claim={Claim.ManageClients} setModalId={setModalId} createButtonText={`invoice.${clientType}New`}>
5150
<Form.Group className="form-group">
5251
<Form.Label>
53-
<span style={{marginRight: 8}}>{t(`invoice.${clientTypeName}`)}</span>
52+
<span style={{marginRight: 8}}>{t(`invoice.${clientType}`)}</span>
5453
{client && <ClientIconLinks client={client} />}
5554
</Form.Label>
5655
<ClientSelect value={value || ''} clientType={clientType} onChange={(id, model) => onChange(id, model)} />

frontend/src/components/consultant/controls/ConsultantSelectWithCreateModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const ConsultantSelectWithCreateModal = ({value, onChange}: SelectWithCre
2323
{modalId && (
2424
<ConsultantModal
2525
consultant={modalId !== 'create' ? consultant : null}
26-
show={!!modalId}
26+
show
2727
onClose={() => setModalId(null)}
2828
onConfirm={(model: ConsultantModel) => dispatch(saveConsultant(model, savedModel => onChange(savedModel._id, savedModel)) as any)}
2929
/>

frontend/src/components/controls/form-controls/lib/ComponentsTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
type CustomComponents = 'InvoiceLineTypeSelect' | 'ProjectLineTypeSelect' | 'InvoiceDateStrategySelect' | 'PropertiesSelect'
22
| 'ExtraFields' | 'StringsSelect' | 'ClientSelect' | 'ClientTypeSelect' | 'ConsultantTypeSelect' | 'EditProjectClient' | 'EditProjectEndCustomer' | 'ProjectSelect'
3-
| 'ConsultantSelectWithCreateModal' | 'InvoiceReplacementsInput' | 'InvoiceReplacementsTextEditor'
3+
| 'ConsultantSelectWithCreateModal' | 'InvoiceReplacementsInput' | 'InvoiceReplacementsTextEditor' | 'EditProjectClientAsEndCustomer'
44
| 'PartnerSelectWithCreateModal' | 'EndCustomerSelectWithCreateModal' | 'LanguageSelect' | 'EditInvoiceLines' | 'RolesSelect' | 'ClaimsSelect'
55
| 'ProjectMonthInboundStatusSelect' | 'ProjectMonthStatusSelect' | 'AttachmentsTypeSelect' | 'EditProjectPartner'
66
| 'ClientSelectWithCreateModal' | 'ContractStatusWithNotes' | 'ProjectClientContractStatus' | 'CountrySelect' | 'ProjectProformaSelect';

frontend/src/components/controls/form-controls/lib/EditComponentFactory.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {ExtraFieldsInput} from '../inputs/ExtraFieldsInput';
2626
import {TextEditor} from '../inputs/TextEditor';
2727
import {AttachmentsTypeSelect} from '../../attachments/AttachmentsTypeSelect';
2828
import {DatePicker} from '../DatePicker';
29-
import {EditProjectClient, EditProjectEndCustomer, EditProjectPartner} from '../../../project/controls/EditProjectClient';
29+
import {EditProjectClient, EditProjectEndCustomer, EditProjectPartner, EditProjectClientAsEndCustomer} from '../../../project/controls/EditProjectClient';
3030
import {ConsultantSelectWithCreateModal} from '../../../consultant/controls/ConsultantSelectWithCreateModal';
3131
import {LanguageSelect} from '../../LanguageSelect';
3232
import {EditInvoiceLines} from '../../../invoice/invoice-edit/invoice-lines/EditInvoiceLines';
@@ -81,6 +81,7 @@ export function getComponent(col: FormConfig) {
8181
ProjectSelect,
8282
EditProjectClient,
8383
EditProjectPartner,
84+
EditProjectClientAsEndCustomer,
8485
EditProjectEndCustomer,
8586
ConsultantSelectWithCreateModal,
8687
InvoiceReplacementsInput,

frontend/src/components/hooks/useProjects.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
import {Moment} from 'moment';
2-
import {useSelector} from 'react-redux';
3-
import {ConfacState} from '../../reducers/app-state';
1+
import { Moment } from 'moment';
2+
import { useSelector } from 'react-redux';
3+
import { ConfacState } from '../../reducers/app-state';
44
import { ClientModel } from '../client/models/ClientModels';
55
import { ConsultantModel } from '../consultant/models/ConsultantModel';
66
import InvoiceModel from '../invoice/models/InvoiceModel';
7-
import {FullProjectModel} from '../project/models/FullProjectModel';
8-
import {FullProjectMonthModel, IFullProjectMonthModel} from '../project/models/FullProjectMonthModel';
7+
import { FullProjectModel } from '../project/models/FullProjectModel';
8+
import { FullProjectMonthModel, IFullProjectMonthModel } from '../project/models/FullProjectMonthModel';
99
import { IProjectModel } from '../project/models/IProjectModel';
10-
import {ProjectMonthModel} from '../project/models/ProjectMonthModel';
10+
import { ProjectMonthModel } from '../project/models/ProjectMonthModel';
1111

1212

1313
export function useProjects(month?: Moment): FullProjectModel[] {
14-
const {projects, clients, consultants} = useSelector((state: ConfacState) => ({
15-
projects: state.projects,
16-
clients: state.clients,
17-
consultants: state.consultants
18-
}));
14+
const projects = useSelector((state: ConfacState) => state.projects);
15+
const clients = useSelector((state: ConfacState) => state.clients);
16+
const consultants = useSelector((state: ConfacState) => state.consultants);
1917

2018
return projects.map(project => {
2119
const consultant = consultants.find(x => x._id === project.consultantId);

frontend/src/components/project/EditProject.tsx

Lines changed: 96 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,62 @@
1-
import {useState} from 'react';
2-
import {useDispatch, useSelector} from 'react-redux';
3-
import {Container, Row, Form} from 'react-bootstrap';
4-
import {useNavigate} from 'react-router-dom';
5-
import {t} from '../utils';
6-
import {ArrayInput} from '../controls/form-controls/inputs/ArrayInput';
7-
import {deleteProject, saveProject} from '../../actions';
8-
import {StickyFooter} from '../controls/other/StickyFooter';
9-
import {BusyButton} from '../controls/form-controls/BusyButton';
10-
import {IProjectModel, ProjectClientInvoiceLine} from './models/IProjectModel';
11-
import {getNewProject, getNewProjectEndCustomer} from './models/getNewProject';
12-
import {ConfacState} from '../../reducers/app-state';
13-
import {getDefaultProjectMonthConfig} from './models/ProjectMonthModel';
14-
import {useDocumentTitle} from '../hooks/useDocumentTitle';
15-
import {getNewClient} from '../client/models/getNewClient';
16-
import {getNewConsultant} from '../consultant/models/getNewConsultant';
17-
import {Audit} from '../admin/audit/Audit';
18-
import {CopyProject} from './CopyProject';
19-
import {Claim} from '../users/models/UserModel';
20-
import {useParams} from 'react-router-dom';
21-
import {Col} from 'react-bootstrap';
1+
import { useEffect, useState } from 'react';
2+
import { useDispatch, useSelector } from 'react-redux';
3+
import { Container, Row, Form, Col } from 'react-bootstrap';
4+
import { useNavigate, useParams } from 'react-router-dom';
5+
import { t } from '../utils';
6+
import { ArrayInput } from '../controls/form-controls/inputs/ArrayInput';
7+
import { deleteProject, saveProject } from '../../actions';
8+
import { StickyFooter } from '../controls/other/StickyFooter';
9+
import { BusyButton } from '../controls/form-controls/BusyButton';
10+
import { IProjectModel, ProjectClientInvoiceLine } from './models/IProjectModel';
11+
import { getNewProject, getNewProjectEndCustomer } from './models/getNewProject';
12+
import { ConfacState } from '../../reducers/app-state';
13+
import { getDefaultProjectMonthConfig } from './models/ProjectMonthModel';
14+
import { useDocumentTitle } from '../hooks/useDocumentTitle';
15+
import { Audit } from '../admin/audit/Audit';
16+
import { CopyProject } from './CopyProject';
17+
import { Claim } from '../users/models/UserModel';
2218
import { SingleContractIcon } from "../client/contract/SingleContractIcon";
23-
import {EnhanceWithConfirmation} from '../enhancers/EnhanceWithConfirmation';
24-
import {Button} from '../controls/form-controls/Button';
25-
import {isDateIntervalValid} from '../controls/other/ProjectValidator';
19+
import { EnhanceWithConfirmation } from '../enhancers/EnhanceWithConfirmation';
20+
import { Button} from '../controls/form-controls/Button';
21+
import { isDateIntervalValid } from '../controls/other/ProjectValidator';
2622
import useEntityChangedToast from '../hooks/useEntityChangedToast';
27-
import { projectFormConfigClient, projectFormConfigEndCustomer } from './models/ProjectFormConfig';
23+
import { getProjectFormConfig } from './models/ProjectFormConfig';
2824

2925

3026
const ConfirmationButton = EnhanceWithConfirmation(Button);
3127

3228

33-
export const EditProject = () => {
34-
const dispatch = useDispatch();
35-
const navigate = useNavigate();
29+
const useProjectState = () => {
3630
const params = useParams();
37-
const model = useSelector((state: ConfacState) => state.projects.find(c => c._id === params.id));
31+
const storeProject = useSelector((state: ConfacState) => state.projects.find(c => c._id === params.id));
32+
3833
const consultants = useSelector((state: ConfacState) => state.consultants);
39-
const [project, setProject] = useState<IProjectModel>(model || getNewProject());
40-
const consultant = useSelector((state: ConfacState) => state.consultants.find(x => x._id === project.consultantId) || getNewConsultant());
34+
const [project, setSimpleProject] = useState<IProjectModel>(storeProject || getNewProject());
4135
const clients = useSelector((state: ConfacState) => state.clients);
42-
const client = useSelector((state: ConfacState) => state.clients.find(x => x._id === project.client.clientId) || getNewClient());
36+
4337
const hasProjectMonths = useSelector((state: ConfacState) => state.projectsMonth.some(pm => pm.projectId === params.id));
44-
const [needsSync, setNeedsSync] = useState<{consultant: boolean, client: boolean}>({consultant: false, client: false});
45-
const [clientFormConfig, setClientFormConfig] = useState(project.forEndCustomer ? projectFormConfigClient : projectFormConfigEndCustomer)
4638

47-
useEntityChangedToast(project._id);
4839

49-
const docTitle = consultant._id ? 'projectEdit' : 'projectNew';
50-
useDocumentTitle(docTitle, {consultant: consultant.firstName, client: client.name});
51-
if (model && (!project._id || project._id !== params.id)) {
52-
setProject(model);
53-
}
54-
55-
if (needsSync.consultant || needsSync.client) {
56-
let newProject = {...project};
57-
58-
if (needsSync.consultant) {
59-
// Set ProjectMonth invoicing config based on the Consultant.Type
60-
const selectedConsultant = consultants.find(c => c._id === project.consultantId);
61-
newProject = {
62-
...newProject,
63-
projectMonthConfig: {
64-
...newProject.projectMonthConfig,
65-
...getDefaultProjectMonthConfig(selectedConsultant && selectedConsultant.type),
66-
},
67-
};
40+
useEffect(() => {
41+
if (storeProject) {
42+
setSimpleProject(storeProject);
43+
} else {
44+
setSimpleProject(getNewProject());
6845
}
46+
}, [storeProject]); // eslint-disable-line
6947

70-
if (needsSync.client) {
71-
// Set ProjectMonth invoicing config based on the Client.ChangingOrderNr
72-
const selectedClient = clients.find(x => x._id === project.client.clientId);
48+
49+
const setProject = (value: IProjectModel) => {
50+
const newProject = {...project, ...value};
51+
52+
// Set ProjectMonth invoicing config based on the Client.ChangingOrderNr
53+
if (newProject.client.clientId !== project.client.clientId) {
54+
const selectedClient = clients.find(x => x._id === newProject.client.clientId);
7355
if (selectedClient) {
74-
newProject.projectMonthConfig.changingOrderNr = selectedClient.defaultChangingOrderNr;
56+
newProject.projectMonthConfig = {
57+
...newProject.projectMonthConfig,
58+
changingOrderNr: selectedClient.defaultChangingOrderNr,
59+
};
7560

7661
if (selectedClient.defaultInvoiceLines?.length > 1) {
7762
newProject.client = {
@@ -83,45 +68,63 @@ export const EditProject = () => {
8368
}
8469
}
8570

86-
setNeedsSync({consultant: false, client: false});
87-
setProject(newProject);
88-
}
89-
90-
const setProjectInterceptor = (value: IProjectModel) => {
91-
const newProject = {...project, ...value};
71+
// Set ProjectMonth invoicing config based on the Consultant.Type
72+
if (newProject.consultantId !== project.consultantId) {
73+
const selectedConsultant = consultants.find(c => c._id === newProject.consultantId);
74+
if (selectedConsultant) {
75+
newProject.projectMonthConfig = {
76+
...newProject.projectMonthConfig,
77+
...getDefaultProjectMonthConfig(selectedConsultant.type),
78+
};
79+
}
80+
}
9281

82+
// Enable/Disable "project is via client at end customer"
9383
if (!newProject.forEndCustomer && newProject.endCustomer) {
84+
newProject.client = {
85+
...newProject.client,
86+
clientId: newProject.endCustomer.clientId,
87+
}
9488
newProject.endCustomer = null;
89+
} else if (newProject.forEndCustomer && !newProject.endCustomer) {
90+
newProject.endCustomer = {
91+
...getNewProjectEndCustomer(),
92+
clientId: newProject.client.clientId,
93+
};
94+
newProject.client = {
95+
...newProject.client,
96+
clientId: '',
97+
};
9598
}
9699

97-
if (newProject.forEndCustomer && !newProject.endCustomer) {
98-
newProject.endCustomer = getNewProjectEndCustomer();
99-
}
100+
setSimpleProject(newProject);
101+
};
100102

101-
const clientTypeForClientField = (newProject.forEndCustomer ? 'client' : 'endCustomer')
102-
if (project.client.clientId) {
103-
const selectedClient = clients.find(x => x._id === project.client.clientId);
104-
if (selectedClient && !selectedClient.types.includes(clientTypeForClientField)) {
105-
newProject.client.clientId = '';
106-
}
107-
}
103+
return {
104+
project,
105+
setProject,
106+
canDelete: !hasProjectMonths,
107+
};
108+
}
108109

109-
setProject(newProject);
110+
const useEditProjectTitle = (project: IProjectModel) => {
111+
const consultant = useSelector((state: ConfacState) => state.consultants.find(x => x._id === project.consultantId));
112+
const client = useSelector((state: ConfacState) => state.clients.find(x => x._id === project.client.clientId));
113+
const docTitle = consultant?._id ? 'projectEdit' : 'projectNew';
114+
useDocumentTitle(docTitle, {consultant: consultant?.firstName ?? '', client: client?.name ?? ''});
115+
}
110116

111-
setClientFormConfig(newProject.forEndCustomer ? projectFormConfigClient: projectFormConfigEndCustomer)
112117

118+
export const EditProject = () => {
119+
const dispatch = useDispatch();
120+
const navigate = useNavigate();
121+
const {project, setProject, canDelete} = useProjectState();
122+
const client = useSelector((state: ConfacState) => state.clients.find(x => x._id === project.client.clientId));
113123

124+
useEntityChangedToast(project._id);
125+
useEditProjectTitle(project);
114126

115-
// Set a flag to update fields that receive default values from the
116-
// selected Consultant/Client. Cannot update them at this point because
117-
// the selectors have not yet included the Consultant/Client when the
118-
// user created a new entity with the CreateModal button
119-
const consultantChanged = value.consultantId !== project.consultantId;
120-
const clientChanged = value.client.clientId !== project.client.clientId;
121-
if (consultantChanged || clientChanged) {
122-
setNeedsSync({consultant: consultantChanged, client: clientChanged});
123-
}
124-
};
127+
const clientFormConfig = getProjectFormConfig(project);
125128

126129
const isButtonDisabled = !project.consultantId
127130
|| !project.client || !project.client.clientId
@@ -132,12 +135,15 @@ export const EditProject = () => {
132135
<Container className="edit-container">
133136
<Row className="page-title-container">
134137
<h1>{project._id ? t('project.project') : t('project.createNew')}</h1>
135-
<Audit model={model} modelType="project" />
138+
<Audit model={project} modelType="project" />
136139
</Row>
137140
<Row>
138141
<Col>
139142
<h2>
140-
<SingleContractIcon contracts={[project.contract, client?.frameworkAgreement]} style={{fontSize: 28}} />
143+
<SingleContractIcon
144+
contracts={client ? [project.contract, client?.frameworkAgreement] : [project.contract]}
145+
style={{fontSize: 28}}
146+
/>
141147
{t('project.contract.title')}
142148
</h2>
143149
</Col>
@@ -147,7 +153,7 @@ export const EditProject = () => {
147153
<ArrayInput
148154
config={clientFormConfig}
149155
model={project}
150-
onChange={(value: IProjectModel) => setProjectInterceptor(value)}
156+
onChange={(value: IProjectModel) => setProject(value)}
151157
tPrefix="project."
152158
/>
153159
</Row>
@@ -159,7 +165,7 @@ export const EditProject = () => {
159165
variant="danger"
160166
title={t('project.deleteConfirm.title')}
161167
componentChildren={t('delete')}
162-
claim={claims => !hasProjectMonths && !!project._id && claims.includes(Claim.DeleteProject)}
168+
claim={claims => canDelete && !!project._id && claims.includes(Claim.DeleteProject)}
163169
>
164170
{t('project.deleteConfirm.content')}
165171
</ConfirmationButton>

0 commit comments

Comments
 (0)