first commit
CI / lint-and-test (push) Canceled after 0s

This commit is contained in:
Alberto-Audrix committed 2026-07-28 08:55:05 +07:00
commit 32a36cceff
444 files changed
+67186

No files matched your search

@@ -0,0 +1,146 @@
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;