Skip to content

Commit fa8d05e

Browse files
committed
refactor: Convert UpdateConfirmation component from JS to TypeScript
- Convert class component to functional component using React hooks - Replace withRouter HOC and connect HOC with useParams, useSelector, and useDispatch hooks - Add comprehensive TypeScript typing for props, state, and function parameters - Implement useMemo optimization for selector performance to prevent unnecessary rerenders - Create extensive test suite covering all component functionality and edge cases - Fix routing warnings in tests by adding missing route definitions
1 parent cd79f09 commit fa8d05e

4 files changed

Lines changed: 506 additions & 133 deletions

File tree

src/components/UpdateConfirmation.js

Lines changed: 0 additions & 133 deletions
This file was deleted.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import _ from 'lodash';
2+
import React, { useMemo } from 'react';
3+
import { Link, useParams } from 'react-router-dom';
4+
import { useSelector, useDispatch } from 'react-redux';
5+
6+
import UnitStatusSummary from './UnitStatusSummary';
7+
import { COLORS, ICONS } from './utils';
8+
import { unitObservableProperties } from '../lib/municipalServicesClient';
9+
import { AllowedValue, ObservableProperty } from '../types';
10+
import { RootState } from '../reducers/types';
11+
12+
import * as actions from '../actions/index';
13+
14+
export const ACTION_TYPE = {
15+
observed: 'Todettu',
16+
serviced: 'Kunnostettu'
17+
} as const;
18+
19+
export const HELP_TEXTS = {
20+
observed: `Valitse "${ACTION_TYPE.observed}" jos liikuntapaikkaa ei ole juuri nyt kunnostettu, mutta haluat merkitä sen tilaksi`,
21+
serviced: `Valitse "${ACTION_TYPE.serviced}" jos liikuntapaikka on juuri kunnostettu, ja haluat samalla merkitä sen tilaksi`
22+
} as const;
23+
24+
type ActionType = keyof typeof ACTION_TYPE;
25+
26+
interface ConfirmButtonProps {
27+
unitId: number;
28+
allowedValue: AllowedValue;
29+
type: ActionType;
30+
enqueueObservation: (property: string, allowedValue: AllowedValue, unitId: number, addServicedObservation?: boolean) => void;
31+
}
32+
33+
function ConfirmButton({ unitId, allowedValue, type, enqueueObservation }: ConfirmButtonProps) {
34+
const iconClassName = `icon ${ICONS[allowedValue.identifier]}`;
35+
const buttonClassName = `btn btn-${COLORS[allowedValue.quality] || 'primary'} btn-block btn__confirmation`;
36+
return (
37+
<Link to={`/unit/${unitId}`} className={buttonClassName} onClick={() => { enqueueObservation(allowedValue.property, allowedValue, unitId, (type === 'serviced')); }}>
38+
<h6>{ ACTION_TYPE[type] }</h6>
39+
<span className={iconClassName}></span><br/>
40+
{ allowedValue.name.fi }
41+
</Link>
42+
);
43+
}
44+
45+
export function canPropertyBeMaintained(property: string): boolean {
46+
return property !== 'swimming_water_cyanobacteria';
47+
}
48+
49+
interface AllowedValueParams {
50+
propertyId: string;
51+
valueId: string;
52+
}
53+
54+
function allowedValueSelector(properties: ObservableProperty[], { propertyId, valueId }: AllowedValueParams): AllowedValue | null {
55+
if (properties.length === 0) {
56+
return null;
57+
}
58+
const property = _.find(properties, (p) => { return p.id == propertyId; });
59+
if (!property) {
60+
return null;
61+
}
62+
const foundValue = _.find(property.allowed_values, (value) => {
63+
return (value.identifier == valueId);
64+
});
65+
if (!foundValue) {
66+
return null;
67+
}
68+
return {
69+
...foundValue,
70+
property: property.id
71+
};
72+
}
73+
74+
interface Params extends Record<string, string> {
75+
unitId: string;
76+
propertyId: string;
77+
valueId: string;
78+
}
79+
80+
const UpdateConfirmation: React.FC = () => {
81+
const params = useParams<Params>();
82+
const dispatch = useDispatch();
83+
84+
const unit = useSelector((state: RootState) =>
85+
params.unitId ? state.data.unit[Number(params.unitId)] : undefined
86+
);
87+
88+
const services = useSelector((state: RootState) => state.data.service);
89+
90+
const observableProperties = useMemo(() => {
91+
if (!unit) return [];
92+
return unitObservableProperties(unit, services);
93+
}, [unit, services]);
94+
95+
const allowedValue = useMemo(() => {
96+
if (!params.unitId || !params.propertyId || !params.valueId) return null;
97+
return allowedValueSelector(
98+
observableProperties,
99+
{ propertyId: params.propertyId, valueId: params.valueId }
100+
);
101+
}, [observableProperties, params.propertyId, params.valueId]);
102+
103+
const enqueueObservation = (property: string, allowedValue: AllowedValue, unitId: number, addServicedObservation?: boolean) => {
104+
dispatch((actions.enqueueObservation as any)(property, allowedValue, unitId, addServicedObservation));
105+
};
106+
107+
if (unit === undefined || allowedValue === null) {
108+
return <div>Ladataan...</div>;
109+
}
110+
111+
const quality = allowedValue.quality;
112+
let buttonRow: React.ReactNode;
113+
let helpRow: React.ReactNode;
114+
115+
if (canPropertyBeMaintained(allowedValue.property) &&
116+
(quality === 'good' ||
117+
quality === 'satisfactory' ||
118+
allowedValue.identifier === 'event')) {
119+
buttonRow = (
120+
<div className="row">
121+
<div className="col-xs-6">
122+
<ConfirmButton type='observed' unitId={unit.id} allowedValue={allowedValue} enqueueObservation={enqueueObservation} />
123+
</div>
124+
<div className="col-xs-6">
125+
<ConfirmButton type='serviced' unitId={unit.id} allowedValue={allowedValue} enqueueObservation={enqueueObservation} />
126+
</div>
127+
</div>
128+
);
129+
helpRow = (
130+
<div className="row">
131+
<div className="col-xs-6">
132+
<p className="help-block"><small>{ HELP_TEXTS.observed } "{allowedValue.name.fi}".</small></p>
133+
</div>
134+
<div className="col-xs-6">
135+
<p className="help-block"><small>{ HELP_TEXTS.serviced } "{allowedValue.name.fi}".</small></p>
136+
</div>
137+
</div>
138+
);
139+
} else {
140+
buttonRow = (
141+
<div className="row">
142+
<div className="col-xs-12">
143+
<ConfirmButton type='observed' unitId={unit.id} allowedValue={allowedValue} enqueueObservation={enqueueObservation} />
144+
</div>
145+
</div>);
146+
helpRow = null;
147+
}
148+
const unitUrl = `/unit/${unit.id}`;
149+
return (
150+
<div className="facility-status">
151+
<UnitStatusSummary unit={unit} />
152+
<div className="panel panel-warning">
153+
<div className="panel-heading">
154+
<h6>Oletko varma että haluat päivittää paikan kuntotiedon?</h6>
155+
</div>
156+
<div className="panel-body">
157+
{ buttonRow }
158+
<div className="row">
159+
<div className="col-xs-12">
160+
<br/>
161+
<Link to={unitUrl} className="btn btn-primary btn-block"><h5>Peruuta</h5></Link>
162+
</div>
163+
</div>
164+
{ helpRow }
165+
</div>
166+
</div>
167+
</div>
168+
);
169+
};
170+
171+
export default UpdateConfirmation;

0 commit comments

Comments
 (0)