147 lines
5.3 KiB
TypeScript
147 lines
5.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useAppContext } from '../../context/AppContext.tsx';
|
|
import CameraCountCard from './CameraCountCard.tsx';
|
|
import { VideoIcon } from '../icons/Icons.tsx';
|
|
import AccordionItem from './AccordionItem.tsx';
|
|
|
|
const LocationCountBreakdown: React.FC = () => {
|
|
const { aggregatedCounts } = useAppContext();
|
|
|
|
const [openAccordions, setOpenAccordions] = useState<Record<string, boolean>>(() => {
|
|
const initialState: Record<string, boolean> = {};
|
|
if (aggregatedCounts.length > 0) {
|
|
const firstLocation = aggregatedCounts[0];
|
|
initialState[firstLocation.locationId] = true;
|
|
|
|
if (firstLocation.coops.length > 0) {
|
|
const firstCoop = firstLocation.coops[0];
|
|
initialState[firstCoop.coopId] = true;
|
|
|
|
// Open all floors within that first coop by default as they don't have sub-items
|
|
firstCoop.floors.forEach((floor) => {
|
|
initialState[floor.floorId] = true;
|
|
});
|
|
}
|
|
}
|
|
return initialState;
|
|
});
|
|
|
|
const handleToggle = (id: string) => {
|
|
setOpenAccordions((prev) => ({ ...prev, [id]: !prev[id] }));
|
|
};
|
|
|
|
const expandAll = () => {
|
|
const allIds: Record<string, boolean> = {};
|
|
aggregatedCounts.forEach((location) => {
|
|
allIds[location.locationId] = true;
|
|
location.coops.forEach((coop) => {
|
|
allIds[coop.coopId] = true;
|
|
coop.floors.forEach((floor) => {
|
|
allIds[floor.floorId] = true;
|
|
});
|
|
});
|
|
});
|
|
setOpenAccordions(allIds);
|
|
};
|
|
|
|
const collapseAll = () => {
|
|
setOpenAccordions({});
|
|
};
|
|
|
|
if (aggregatedCounts.length === 0) {
|
|
return (
|
|
<div className="bg-white p-6 rounded-xl shadow-sm h-full">
|
|
<h2 className="text-xl font-bold mb-4">Rincian Populasi per Lokasi</h2>
|
|
<div className="text-center py-16 border-dashed border-2 rounded-lg mt-4">
|
|
<VideoIcon className="text-6xl text-gray-300 mx-auto" />
|
|
<h3 className="mt-4 text-xl font-semibold text-gray-700">Tidak Ada Data Penghitungan</h3>
|
|
<p className="mt-1 text-gray-500">
|
|
Tidak ada kamera online yang melaporkan data penghitungan saat ini.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white p-6 rounded-xl shadow-sm h-full flex flex-col">
|
|
<div className="flex justify-between items-center mb-4 flex-shrink-0">
|
|
<h2 className="text-xl font-bold">Rincian Populasi per Lokasi</h2>
|
|
<div className="flex items-center space-x-2">
|
|
<button
|
|
onClick={expandAll}
|
|
className="flex items-center space-x-2 text-sm text-gray-600 font-medium px-3 py-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
|
title="Buka Semua"
|
|
>
|
|
<i className="fa-solid fa-expand-alt"></i>
|
|
<span>Buka Semua</span>
|
|
</button>
|
|
<button
|
|
onClick={collapseAll}
|
|
className="flex items-center space-x-2 text-sm text-gray-600 font-medium px-3 py-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
|
title="Tutup Semua"
|
|
>
|
|
<i className="fa-solid fa-compress-alt"></i>
|
|
<span>Tutup Semua</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2 overflow-y-auto pr-2 flex-grow">
|
|
{aggregatedCounts.map((location) => (
|
|
<AccordionItem
|
|
key={location.locationId}
|
|
id={location.locationId}
|
|
title={location.locationName}
|
|
count={location.totalCount}
|
|
icon="fa-building"
|
|
level="location"
|
|
isOpen={!!openAccordions[location.locationId]}
|
|
onToggle={handleToggle}
|
|
>
|
|
<div className="space-y-2">
|
|
{location.coops.map((coop) => (
|
|
<AccordionItem
|
|
key={coop.coopId}
|
|
id={coop.coopId}
|
|
title={coop.coopName}
|
|
count={coop.totalCount}
|
|
icon="fa-cube"
|
|
level="coop"
|
|
isOpen={!!openAccordions[coop.coopId]}
|
|
onToggle={handleToggle}
|
|
>
|
|
<div className="space-y-4 pl-6 pt-2">
|
|
{coop.floors.map((floor) => (
|
|
<div key={floor.floorId}>
|
|
<div className="pl-4 border-l-2 border-gray-200">
|
|
<AccordionItem
|
|
id={floor.floorId}
|
|
title={floor.floorName}
|
|
count={floor.count}
|
|
icon="fa-layer-group"
|
|
level="floor"
|
|
isOpen={!!openAccordions[floor.floorId]}
|
|
onToggle={handleToggle}
|
|
>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-3 gap-3 pt-3">
|
|
{floor.cameras.map((lc) => (
|
|
<CameraCountCard key={lc.cameraId} liveCount={lc} />
|
|
))}
|
|
</div>
|
|
</AccordionItem>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</AccordionItem>
|
|
))}
|
|
</div>
|
|
</AccordionItem>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LocationCountBreakdown;
|