Skip to content

Commit 3b7fd59

Browse files
committed
feat: convert DashBoard component to TypeScript
- Convert DashBoard.js to DashBoard.tsx with proper TypeScript types - Add comprehensive test suite for DashBoard component - Improve type safety across actions and reducers - Add jest-dom type declarations for better testing support
1 parent b8b068e commit 3b7fd59

14 files changed

Lines changed: 576 additions & 126 deletions

src/actions/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import _ from 'lodash';
22
import { createAction } from 'redux-actions';
33
import * as ApiClient from '../lib/municipalServicesClient';
44
import { ActionTypes } from '../constants';
5-
import { AllowedValue } from 'types';
5+
import { AllowedValue, UserLocation } from 'types';
66

77
// Action creators with proper TypeScript types
88
export const setResourceFetchStart = createAction(
@@ -65,7 +65,10 @@ export const retryImmediately = createAction(ActionTypes.FLUSH_UPDATE_QUEUE);
6565
export const finishRetryImmediately = createAction(ActionTypes.FLUSH_UPDATE_QUEUE_DISABLED);
6666
export const selectServiceGroup = createAction(ActionTypes.SELECT_SERVICE_GROUP);
6767

68-
export const setUserLocation = createAction(ActionTypes.SET_USER_LOCATION);
68+
export const setUserLocation = createAction(
69+
ActionTypes.SET_USER_LOCATION,
70+
(userLocation: UserLocation | null) => userLocation
71+
);
6972

7073
export const sendObservation = createAction(
7174
ActionTypes.POST_OBSERVATION,

src/components/DashBoard.js

Lines changed: 0 additions & 70 deletions
This file was deleted.

src/components/DashBoard.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import _ from 'lodash';
2+
import React, { useMemo } from 'react';
3+
import { useSelector } from 'react-redux';
4+
5+
import { UnitListElement } from './UnitList';
6+
import { Unit } from '../types';
7+
import { RootState, UnitsByUpdateCountState } from '../reducers/types';
8+
9+
interface UnitByUpdateCount {
10+
id: string;
11+
count: number;
12+
}
13+
14+
function topUnits(
15+
n: number,
16+
units: Record<string, Unit>,
17+
unitsByUpdateCount: UnitsByUpdateCountState
18+
): Unit[] {
19+
return _.reverse(
20+
_.map(
21+
_.sortBy(
22+
_.map(unitsByUpdateCount, (u, id) => ({
23+
id,
24+
count: u.count
25+
})),
26+
['count']
27+
),
28+
(u: UnitByUpdateCount) => {
29+
return units[u.id];
30+
}
31+
).filter(unit => unit != null).slice(0, n)
32+
);
33+
}
34+
35+
const DashBoard: React.FC = () => {
36+
const loading = useSelector((state: RootState) => state.data.loading.unit);
37+
const userLocation = useSelector((state: RootState) => state.userLocation);
38+
const unitData = useSelector((state: RootState) => state.data.unit);
39+
const unitsByDistance = useSelector((state: RootState) => state.data.unitsByDistance);
40+
const unitsByUpdateCount = useSelector((state: RootState) => state.unitsByUpdateCount);
41+
42+
const nearest = useMemo(() => {
43+
if (_.keys(unitData).length > 0) {
44+
return _.map(unitsByDistance, (u) => {
45+
return unitData[u.id.toString()];
46+
});
47+
}
48+
return [];
49+
}, [unitData, unitsByDistance]);
50+
51+
const frequent = useMemo(() => {
52+
if (_.keys(unitData).length > 0) {
53+
return topUnits(20, unitData, unitsByUpdateCount);
54+
}
55+
return [];
56+
}, [unitData, unitsByUpdateCount]);
57+
58+
if (loading) {
59+
return <div>Ladataan...</div>;
60+
}
61+
62+
const nearestElements = userLocation === null
63+
? <span>Sijainti ei saatavilla.</span>
64+
: nearest.filter(u => u?.id).map(u => <UnitListElement key={u.id} unit={u} />);
65+
66+
const frequentElements = frequent.map(u => <UnitListElement key={u.id} unit={u} />);
67+
68+
return (
69+
<div className="row">
70+
<div className="col-xs-12">
71+
<h5>Lähimmät</h5>
72+
<div className="list-group facility-drilldown">{nearestElements}</div>
73+
<h5>Yleisimmät</h5>
74+
<div className="list-group facility-drilldown">{frequentElements}</div>
75+
</div>
76+
</div>
77+
);
78+
};
79+
80+
export default DashBoard;

src/components/DeleteConfirmation.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface ConfirmButtonProps {
1414
clearObservation: (property: string, unitId: number) => void;
1515
}
1616

17-
function ConfirmButton({ unitId, clearObservation }: ConfirmButtonProps): React.ReactElement {
17+
function ConfirmButton({ unitId, clearObservation }: Readonly<ConfirmButtonProps>): React.ReactElement {
1818
const buttonClassName = 'btn btn-success btn-block';
1919
return (
2020
<Link to={`/unit/${unitId}`} className={buttonClassName} onClick={() => { clearObservation('notice', unitId); }}>
@@ -27,7 +27,7 @@ interface TextualDescriptionProps {
2727
text: string;
2828
}
2929

30-
function TextualDescription({ text }: TextualDescriptionProps): React.ReactElement {
30+
function TextualDescription({ text }: Readonly<TextualDescriptionProps>): React.ReactElement {
3131
const lines = text.split('\n');
3232
return (
3333
<div className="notice-large">
@@ -54,7 +54,7 @@ const DeleteConfirmation: React.FC = () => {
5454
const isLoading = useSelector((state: RootState) => state.data.loading.unit === true);
5555

5656
const observation = useMemo(() => {
57-
if (!unit || !unit.observations) return null;
57+
if (!unit?.observations) return null;
5858
return _.find(unit.observations, (o) => { return o.property === 'notice'; }) || null;
5959
}, [unit]);
6060

src/components/GroupList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface GroupListElementProps {
1010
name: string;
1111
}
1212

13-
function GroupListElement({ id, name }: GroupListElementProps): React.ReactElement {
13+
function GroupListElement({ id, name }: Readonly<GroupListElementProps>): React.ReactElement {
1414
const url = `/group/${id}`;
1515
return (
1616
<Link to={url} className="list-group-item">

0 commit comments

Comments
 (0)