Skip to content

Commit eb75c7e

Browse files
committed
feat: Add disabled prop to ReportGeneratorButton and enhance ActivityLogPage with period filtering
1 parent d6d18c9 commit eb75c7e

File tree

9 files changed

+65
-37
lines changed

9 files changed

+65
-37
lines changed

src/components/ReportGeneratorButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const ReportGeneratorButton: React.FC<ReportGeneratorButtonProps> = ({
2222
buildReportData,
2323
onReportGenerated,
2424
buttonText = 'Generate Report',
25+
disabled
2526
}) => {
2627
const [availableReportTypes, setAvailableReportTypes] = useState<ReportType[]>([]);
2728
const [showReportModal, setShowReportModal] = useState(false);
@@ -87,7 +88,7 @@ const ReportGeneratorButton: React.FC<ReportGeneratorButtonProps> = ({
8788
<Button
8889
variant="success"
8990
onClick={handleGenerateReport}
90-
disabled={generating || !isReportTypeAvailable}
91+
disabled={generating || !isReportTypeAvailable || disabled}
9192
title={!isReportTypeAvailable ? 'Required report type is not available.' : undefined}
9293
>
9394
{buttonText}

src/pages/ActivityLogPage.tsx

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,28 @@ import activityLogService from '../services/activityLogService';
88
import ReportGeneratorButton from '../components/ReportGeneratorButton';
99
import ContextSelect from '../components/ContextSelect';
1010
import { Volunteer } from '../types/Volunteer';
11+
import { Period } from '../types/Period';
1112
import { ActivityLog } from '../types/ActivityLog';
1213
import { ReportType } from '../types/Report';
13-
import { ALERT_NO_CONTEXT_SELECTED } from '../constants/messages';
14+
import { ALERT_NOT_LOGGED_IN } from '../constants/messages';
1415

1516

1617
const ActivityLogPage: React.FC = () => {
17-
const { selectedPeriod } = usePeriod();
18-
const { volunteers } = useVolunteers();
1918
const { user, token } = useContext(AuthContext);
20-
const [activityLogs, setActivityLogs] = useState<ActivityLog[]>([]);
19+
const { volunteers, loading: volunteersLoading } = useVolunteers();
20+
const { periods, loading: periodsLoading } = usePeriod();
2121
const [loading, setLoading] = useState(false);
22+
const [activityLogs, setActivityLogs] = useState<ActivityLog[]>([]);
23+
const [selectedPeriod, setSelectedPeriod] = useState<Period | null>(null);
2224
const [selectedVolunteer, setSelectedVolunteer] = useState<Volunteer | null>(null);
2325

2426
useEffect(() => {
25-
if (!selectedPeriod) return;
2627
setLoading(true);
2728
const fetchLogs = async () => {
2829
try {
2930
const token = localStorage.getItem('token') || '';
3031
let logs: ActivityLog[] = [];
31-
if (selectedVolunteer) {
32-
logs = await activityLogService.listByVolunteer(selectedVolunteer.volunteerId, token);
33-
// Filter to current period
34-
logs = logs.filter(l => l.periodId === selectedPeriod.periodId);
35-
} else {
36-
logs = await activityLogService.list(token);
37-
logs = logs.filter(l => l.periodId === selectedPeriod.periodId);
38-
}
32+
logs = await activityLogService.list(token);
3933
logs.sort((a, b) => new Date(a.timestamp || '').getTime() - new Date(b.timestamp || '').getTime());
4034
setActivityLogs(logs);
4135
} catch (e) {
@@ -47,9 +41,22 @@ const ActivityLogPage: React.FC = () => {
4741
fetchLogs();
4842
}, [selectedPeriod, selectedVolunteer]);
4943

50-
if (!selectedPeriod) {
51-
return <Alert variant="warning">{ALERT_NO_CONTEXT_SELECTED}</Alert>;
52-
}
44+
if (!token) return <Alert variant="warning">{ALERT_NOT_LOGGED_IN}</Alert>;
45+
46+
// Filter and sort activity logs before rendering
47+
const filteredActivityLogs = (activityLogs || [])
48+
.filter(l => {
49+
// Filter by selected period if set
50+
if (selectedPeriod && l.periodId !== selectedPeriod.periodId) return false;
51+
// Filter by selected volunteer if set
52+
if (selectedVolunteer && l.volunteerId !== selectedVolunteer.volunteerId) return false;
53+
return true;
54+
})
55+
.sort((a, b) => {
56+
const aTimestamp = new Date(a.timestamp || '').getTime();
57+
const bTimestamp = new Date(b.timestamp || '').getTime();
58+
return aTimestamp - bTimestamp;
59+
});
5360

5461
return (
5562
<Container>
@@ -58,26 +65,35 @@ const ActivityLogPage: React.FC = () => {
5865
<Row className="align-items-center">
5966
<Col><strong>Activity Log</strong></Col>
6067
<Col md="auto" className="d-flex align-items-center gap-2">
68+
<ContextSelect
69+
label="Period"
70+
options={periods}
71+
value={selectedPeriod ? selectedPeriod.periodId : null}
72+
onSelect={id => setSelectedPeriod(id ? periods.find(p => p.periodId === id) ?? null : null)}
73+
loading={periodsLoading}
74+
getOptionLabel={p => p.description}
75+
getOptionValue={p => p.periodId}
76+
/>
6177
<ContextSelect
6278
label="Volunteer"
6379
options={volunteers}
6480
value={selectedVolunteer ? selectedVolunteer.volunteerId : null}
6581
onSelect={id => setSelectedVolunteer(id ? volunteers.find(v => v.volunteerId === id) ?? null : null)}
66-
loading={loading}
67-
getOptionLabel={v => v.name}
82+
loading={volunteersLoading}
83+
getOptionLabel={v => v.callsign ? `${v.name} (${v.callsign})` : v.name}
6884
getOptionValue={v => v.volunteerId}
6985
/>
7086
<ReportGeneratorButton
7187
requiredReportType={ReportType.ICS214}
72-
token={token || ''}
88+
token={token}
7389
user={user}
7490
buttonText="Generate ICS-214"
75-
disabled={!selectedPeriod || activityLogs.length === 0}
91+
disabled={filteredActivityLogs.length === 0}
7692
buildReportData={({ positionTitle }) => {
7793
// ICS-214 report payload builder
7894
// Find referenced volunteers and count log entries per volunteer
7995
const logCounts: Record<string, number> = {};
80-
for (const log of activityLogs) {
96+
for (const log of filteredActivityLogs) {
8197
if (log.volunteerId) {
8298
logCounts[log.volunteerId] = (logCounts[log.volunteerId] || 0) + 1;
8399
}
@@ -89,7 +105,7 @@ const ActivityLogPage: React.FC = () => {
89105
const referencedVolunteers = volunteers.filter(v => topVolunteerIds.includes(v.volunteerId));
90106
return {
91107
period: selectedPeriod,
92-
activityLogs,
108+
activityLogs: filteredActivityLogs,
93109
volunteers: referencedVolunteers,
94110
preparedBy: user,
95111
positionTitle,
@@ -115,10 +131,10 @@ const ActivityLogPage: React.FC = () => {
115131
<td><Placeholder animation="glow"><Placeholder xs={10} /></Placeholder></td>
116132
</tr>
117133
))
118-
) : activityLogs.length === 0 ? (
134+
) : filteredActivityLogs.length === 0 ? (
119135
<tr><td colSpan={2} className="text-center">No activity log entries found.</td></tr>
120136
) : (
121-
activityLogs.map(entry => (
137+
filteredActivityLogs.map(entry => (
122138
<tr key={entry.logId}>
123139
<td>{entry.timestamp ? formatLocalDateTime(entry.timestamp) : ''}</td>
124140
<td>{entry.details ? `${entry.details}` : ''}</td>

src/pages/Incidents/IncidentsPage.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ const IncidentsPage: React.FC = () => {
1616
incidents,
1717
loading,
1818
error,
19-
selectedIncident,
20-
setSelectedIncident,
2119
addIncident,
2220
updateIncident,
2321
deleteIncident
@@ -26,6 +24,7 @@ const IncidentsPage: React.FC = () => {
2624
const [editIncident, setEditIncident] = useState<Incident | null>(null);
2725
const [showView, setShowView] = useState(false);
2826
const [viewIncident, setViewIncident] = useState<Incident | null>(null);
27+
const [selectedIncident, setSelectedIncident] = useState<Incident | null>(null);
2928
// Sorting and filtering state
3029
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
3130

src/pages/Locations/LocationsPage.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AuthContext } from '../../context/AuthContext';
66
import { Container, Card, Table, Button, Alert, Placeholder, Row, Col, Spinner } from 'react-bootstrap';
77
import { ArchiveFill, CheckCircleFill, DashCircleFill } from 'react-bootstrap-icons';
88
import { Location, LocationStatus } from '../../types/Location';
9+
import { Unit } from '../../types/Unit';
910
import LocationForm from './LocationForm';
1011
import { useIncident } from '../../context/IncidentContext';
1112
import LocationViewModal from './LocationViewModal';
@@ -23,15 +24,16 @@ const LocationsPage: React.FC = () => {
2324
updateLocation,
2425
deleteLocation
2526
} = useLocation();
27+
const { units, loading: unitsLoading } = useUnit();
28+
const { selectedIncident } = useIncident();
2629
const [showForm, setShowForm] = useState(false);
2730
const [editLocation, setEditLocation] = useState<Location | null>(null);
2831
const [showView, setShowView] = useState(false);
2932
const [viewLocation, setViewLocation] = useState<Location | null>(null);
33+
const [selectedLocation, setSelectedLocation] = useState<Location | null>(null);
34+
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(null);
3035
// Sorting and filtering state
3136
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
32-
const [selectedLocation, setSelectedLocation] = useState<Location | null>(null);
33-
const { units, loading: unitsLoading, selectedUnit, setSelectedUnit } = useUnit();
34-
const { selectedIncident } = useIncident();
3537

3638
const handleAdd = () => {
3739
setEditLocation(null);

src/pages/Periods/PeriodsPage.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { AuthContext } from '../../context/AuthContext';
44
import { Container, Card, Table, Button, Alert, Placeholder, Row, Col } from 'react-bootstrap';
55
import ContextSelect from '../../components/ContextSelect';
66
import { Period } from '../../types/Period';
7+
import { Unit } from '../../types/Unit';
78
import { formatLocalDateTime } from '../../utils/dateFormat';
89
import { useIncident } from '../../context/IncidentContext';
910
import PeriodForm from './PeriodForm';
@@ -22,17 +23,17 @@ const PeriodsPage: React.FC = () => {
2223
addPeriod,
2324
updatePeriod,
2425
deletePeriod,
25-
selectedPeriod,
26-
setSelectedPeriod,
2726
} = usePeriod();
27+
const { incidents, loading: incidentsLoading } = useIncident();
28+
const { units, loading: unitsLoading } = useUnit();
2829
const [showForm, setShowForm] = useState(false);
2930
const [editPeriod, setEditPeriod] = useState<Period | null>(null);
3031
const [showView, setShowView] = useState(false);
3132
const [viewPeriod, setViewPeriod] = useState<Period | null>(null);
33+
const [selectedUnit, setSelectedUnit] = useState<Unit | null>(null);
34+
const [selectedPeriod, setSelectedPeriod] = useState<Period | null>(null);
3235
// Sorting and filtering state
3336
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
34-
const { incidents, loading: incidentsLoading } = useIncident();
35-
const { units, loading: unitsLoading, selectedUnit, setSelectedUnit } = useUnit();
3637

3738
const handleAdd = () => {
3839
setEditPeriod(null);

src/pages/Resources/PeoplePage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const PeoplePage: React.FC = () => {
132132
value={selectedVolunteer ? selectedVolunteer.volunteerId : null}
133133
onSelect={id => setSelectedVolunteer(id ? volunteers.find(v => v.volunteerId === id) ?? null : null)}
134134
loading={loading}
135-
getOptionLabel={v => v.name}
135+
getOptionLabel={v => v.callsign ? `${v.name} (${v.callsign})` : v.name}
136136
getOptionValue={v => v.volunteerId}
137137
/>
138138
</div>

src/pages/Resources/RadiosResourcePage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ const RadiosResourcePage: React.FC = () => {
141141
value={selectedVolunteer ? selectedVolunteer.volunteerId : null}
142142
onSelect={id => setSelectedVolunteer(id ? volunteers.find(v => v.volunteerId === id) ?? null : null)}
143143
loading={loading}
144-
getOptionLabel={v => v.name}
144+
getOptionLabel={v => v.callsign ? `${v.name} (${v.callsign})` : v.name}
145145
getOptionValue={v => v.volunteerId}
146146
/>
147147
</div>

src/pages/Volunteers/VolunteersPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ const VolunteersPage: React.FC = () => {
9797
value={selectedVolunteer ? selectedVolunteer.volunteerId : null}
9898
onSelect={id => setSelectedVolunteer(id ? volunteers.find(v => v.volunteerId === id) ?? null : null)}
9999
loading={loading}
100-
getOptionLabel={v => v.name}
100+
getOptionLabel={v => v.callsign ? `${v.name} (${v.callsign})` : v.name}
101101
getOptionValue={v => v.volunteerId}
102102
/>
103103
<Button variant="success" onClick={handleAdd} disabled={loading}>

src/services/activityLogService.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ const activityLogService = {
2222
});
2323
},
2424

25+
// List activity logs by period ID using Global Secondary Index
26+
async listByPeriod(id: string, token: string, onAuthError?: () => void): Promise<ActivityLog[]> {
27+
return apiFetch<ActivityLog[]>({
28+
path: `${API_BASE}/period/${id}`,
29+
token,
30+
onAuthError,
31+
});
32+
},
33+
2534
// No get method as ActivityLog is immutable
2635

2736
async create(data: Partial<ActivityLog>, token: string, onAuthError?: () => void): Promise<ActivityLog> {

0 commit comments

Comments
 (0)