Skip to content

Commit 4859a4e

Browse files
committed
done
1 parent a2ae04c commit 4859a4e

File tree

2 files changed

+178
-19
lines changed

2 files changed

+178
-19
lines changed

src/components/BMDashboard/RentalChart/ReturnedLateChart.jsx

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,34 @@ export default function ReturnedLateChart() {
8383
const [selectedToolDetail, setSelectedToolDetail] = useState(null);
8484
const [detailOpen, setDetailOpen] = useState(false);
8585
const [detailLoading, setDetailLoading] = useState(false);
86+
const [hiddenProjects, setHiddenProjects] = useState([]);
8687
const darkMode = useSelector(state => state.theme.darkMode);
8788
const [sortOption, setSortOption] = useState('DESC');
8889
const isMultiProjectView = selectedProject === 'All';
90+
const visibleDatasets = useMemo(
91+
() =>
92+
chartData.datasets.map(dataset => ({
93+
...dataset,
94+
hidden: Boolean(dataset.projectId) && hiddenProjects.includes(dataset.projectId),
95+
})),
96+
[chartData.datasets, hiddenProjects],
97+
);
8998
const maxChartValue = Math.max(
9099
0,
91-
...chartData.datasets.flatMap(dataset => dataset.data.filter(value => value != null)),
100+
...visibleDatasets
101+
.filter(dataset => !dataset.hidden)
102+
.flatMap(dataset => dataset.data.filter(value => value != null)),
103+
);
104+
const legendItems = useMemo(
105+
() =>
106+
chartData.datasets.map(dataset => ({
107+
projectId: dataset.projectId || dataset.label,
108+
label: dataset.label,
109+
backgroundColor: dataset.backgroundColor,
110+
borderColor: dataset.borderColor,
111+
hidden: Boolean(dataset.projectId) && hiddenProjects.includes(dataset.projectId),
112+
})),
113+
[chartData.datasets, hiddenProjects],
92114
);
93115

94116
const sortToolsData = data => {
@@ -281,6 +303,18 @@ export default function ReturnedLateChart() {
281303
fetchData();
282304
}, [availableProjects, darkMode, selectedProject, dateRange, selectedTools, sortOption]);
283305

306+
useEffect(() => {
307+
if (!isMultiProjectView) {
308+
setHiddenProjects([]);
309+
return;
310+
}
311+
312+
const validProjectIds = new Set(
313+
chartData.datasets.map(dataset => dataset.projectId).filter(Boolean),
314+
);
315+
setHiddenProjects(prev => prev.filter(projectId => validProjectIds.has(projectId)));
316+
}, [chartData.datasets, isMultiProjectView]);
317+
284318
const handleBarClick = useCallback(
285319
(event, elements) => {
286320
if (!elements || !elements.length) return;
@@ -312,17 +346,7 @@ export default function ReturnedLateChart() {
312346
},
313347
plugins: {
314348
legend: {
315-
display: isMultiProjectView && chartData.datasets.length > 1,
316-
position: 'top',
317-
align: 'end',
318-
labels: {
319-
color: textColor,
320-
usePointStyle: true,
321-
pointStyle: 'rectRounded',
322-
boxWidth: 14,
323-
boxHeight: 14,
324-
padding: 16,
325-
},
349+
display: false,
326350
},
327351
title: {
328352
display: false,
@@ -387,7 +411,17 @@ export default function ReturnedLateChart() {
387411
},
388412
},
389413
};
390-
}, [chartData, darkMode, handleBarClick, isMultiProjectView, maxChartValue, rawToolsData]);
414+
}, [darkMode, handleBarClick, maxChartValue, rawToolsData]);
415+
416+
const toggleProjectVisibility = projectId => {
417+
if (!projectId || !isMultiProjectView) return;
418+
419+
setHiddenProjects(prev =>
420+
prev.includes(projectId) ? prev.filter(id => id !== projectId) : [...prev, projectId],
421+
);
422+
};
423+
424+
const multiProjectLegendVisible = isMultiProjectView && legendItems.length > 1;
391425

392426
const handleProjectChange = e => setSelectedProject(e.target.value);
393427
const handleStartDateChange = date =>
@@ -403,10 +437,40 @@ export default function ReturnedLateChart() {
403437
<div className={`${styles['returned-late-chart']} ${isOxfordBlue}`}>
404438
<div className={styles['returned-late-header-row']}>
405439
<h1 className={darkMode ? 'text-white' : ''}>Percent of Tools Returned Late</h1>
406-
{isMultiProjectView && chartData.datasets.length > 1 && (
407-
<p className={`${styles['returned-late-legend-hint']} ${darkMode ? 'text-white' : ''}`}>
408-
Click legend items to show or hide project bars.
409-
</p>
440+
{multiProjectLegendVisible && (
441+
<div className={styles['returned-late-legend-block']}>
442+
<p className={`${styles['returned-late-legend-hint']} ${darkMode ? 'text-white' : ''}`}>
443+
Click legend items to show or hide project bars.
444+
</p>
445+
<div
446+
className={styles['returned-late-legend']}
447+
role="group"
448+
aria-label="Project color legend"
449+
>
450+
{legendItems.map(item => (
451+
<button
452+
key={item.projectId}
453+
type="button"
454+
className={`${styles['returned-late-legend-item']} ${
455+
item.hidden ? styles['returned-late-legend-item-hidden'] : ''
456+
}`}
457+
onClick={() => toggleProjectVisibility(item.projectId)}
458+
aria-pressed={!item.hidden}
459+
title={`${item.hidden ? 'Show' : 'Hide'} ${item.label}`}
460+
>
461+
<span
462+
className={styles['returned-late-legend-swatch']}
463+
style={{
464+
backgroundColor: item.backgroundColor,
465+
borderColor: item.borderColor,
466+
}}
467+
aria-hidden="true"
468+
/>
469+
<span className={styles['returned-late-legend-label']}>{item.label}</span>
470+
</button>
471+
))}
472+
</div>
473+
</div>
410474
)}
411475
</div>
412476
<div className={styles['returned-late-filters']}>
@@ -515,7 +579,12 @@ export default function ReturnedLateChart() {
515579
</div>
516580
)}
517581
{!loading && !error && chartData.labels.length > 0 && (
518-
<Bar ref={chartRef} data={chartData} options={options} plugins={[ChartDataLabels]} />
582+
<Bar
583+
ref={chartRef}
584+
data={{ ...chartData, datasets: visibleDatasets }}
585+
options={options}
586+
plugins={[ChartDataLabels]}
587+
/>
519588
)}
520589
</div>
521590
{detailOpen && (

src/components/BMDashboard/RentalChart/ReturnedLateChart.module.css

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,74 @@
1818
}
1919

2020
.returned-late-legend-hint {
21-
margin: 4px 0 20px;
21+
margin: 4px 0 10px;
2222
color: #475569;
2323
font-size: 14px;
2424
font-weight: 500;
2525
}
2626

27+
.returned-late-legend-block {
28+
display: flex;
29+
flex-direction: column;
30+
align-items: flex-end;
31+
gap: 6px;
32+
max-width: min(100%, 720px);
33+
}
34+
35+
.returned-late-legend {
36+
display: flex;
37+
flex-wrap: wrap;
38+
justify-content: flex-end;
39+
gap: 10px;
40+
}
41+
42+
.returned-late-legend-item {
43+
display: inline-flex;
44+
align-items: center;
45+
gap: 8px;
46+
padding: 6px 10px;
47+
border: 1px solid #cbd5e1;
48+
border-radius: 999px;
49+
background: #fff;
50+
color: #1f2937;
51+
cursor: pointer;
52+
transition:
53+
border-color 0.2s ease,
54+
background-color 0.2s ease,
55+
color 0.2s ease,
56+
opacity 0.2s ease,
57+
transform 0.2s ease;
58+
}
59+
60+
.returned-late-legend-item:hover {
61+
border-color: #94a3b8;
62+
transform: translateY(-1px);
63+
}
64+
65+
.returned-late-legend-item:focus-visible {
66+
outline: 3px solid #2563eb;
67+
outline-offset: 2px;
68+
}
69+
70+
.returned-late-legend-item-hidden {
71+
opacity: 0.55;
72+
background: #f8fafc;
73+
}
74+
75+
.returned-late-legend-swatch {
76+
width: 14px;
77+
height: 14px;
78+
border: 2px solid transparent;
79+
border-radius: 4px;
80+
flex-shrink: 0;
81+
}
82+
83+
.returned-late-legend-label {
84+
font-size: 13px;
85+
font-weight: 600;
86+
line-height: 1.2;
87+
}
88+
2789
.returned-late-filters {
2890
display: flex;
2991
gap: 20px;
@@ -139,6 +201,25 @@
139201
color: #cbd5e1;
140202
}
141203

204+
:global(html.dark-mode) .returned-late-legend-item,
205+
:global(body.dark-mode) .returned-late-legend-item {
206+
background: #0f172a;
207+
color: #f8fafc;
208+
border-color: #475569;
209+
}
210+
211+
:global(html.dark-mode) .returned-late-legend-item:hover,
212+
:global(body.dark-mode) .returned-late-legend-item:hover {
213+
border-color: #94a3b8;
214+
background: #172033;
215+
}
216+
217+
:global(html.dark-mode) .returned-late-legend-item-hidden,
218+
:global(body.dark-mode) .returned-late-legend-item-hidden {
219+
background: #172033;
220+
color: #cbd5e1;
221+
}
222+
142223
:global(html.dark-mode) .returned-late-filter-label,
143224
:global(body.dark-mode) .returned-late-filter-label {
144225
color: #fff;
@@ -208,4 +289,13 @@
208289
.returned-late-legend-hint {
209290
margin-top: 0;
210291
}
292+
293+
.returned-late-legend-block {
294+
align-items: stretch;
295+
width: 100%;
296+
}
297+
298+
.returned-late-legend {
299+
justify-content: flex-start;
300+
}
211301
}

0 commit comments

Comments
 (0)