improved UI
All checks were successful
CI / build-test (push) Successful in 56s
Deploy Staging (Docker) / deploy (push) Successful in 33s

This commit is contained in:
Do Manh Phuong 2026-05-12 15:53:16 +07:00
parent 67ea288de6
commit 0917124bd3

View File

@ -15,72 +15,60 @@ export function DeviceGrid({
totalSeats?: number; totalSeats?: number;
}) { }) {
const getMachineNumber = useMachineNumber(); const getMachineNumber = useMachineNumber();
const deviceMap = new Map<number, any>(); const parsedDevices = devices
.map((device, index) => ({
device,
index,
number: getMachineNumber(device.id || ""),
}))
.sort((a, b) => {
const aNumber = a.number > 0 ? a.number : Number.MAX_SAFE_INTEGER;
const bNumber = b.number > 0 ? b.number : Number.MAX_SAFE_INTEGER;
let maxMachineNumber = 0; if (aNumber !== bNumber) {
devices.forEach((device) => { return aNumber - bNumber;
const number = getMachineNumber(device.id || ""); }
if (number > maxMachineNumber) maxMachineNumber = number;
return a.index - b.index;
}); });
const seatCount = const seatCount =
typeof totalSeats === "number" && totalSeats > 0 ? totalSeats : maxMachineNumber; typeof totalSeats === "number" && totalSeats > 0 ? totalSeats : parsedDevices.length;
const rightCapacity = Math.ceil(seatCount / 2);
const inRangeCount = parsedDevices.filter(
(item) => item.number > 0 && item.number <= seatCount
).length;
const useThresholdSplit =
seatCount > 0 && inRangeCount >= Math.ceil(parsedDevices.length * 0.6);
devices.forEach((device) => { let rightDevices = parsedDevices;
const number = getMachineNumber(device.id || ""); let leftDevices: typeof parsedDevices = [];
if (number > 0 && number <= seatCount) deviceMap.set(number, device);
});
const columnsPerSide = 4; if (useThresholdSplit) {
const rightCount = Math.ceil(seatCount / 2); rightDevices = parsedDevices.filter(
const leftCount = seatCount - rightCount; (item) => item.number > 0 && item.number <= rightCapacity
const totalRows = Math.max(
Math.ceil(rightCount / columnsPerSide),
Math.ceil(leftCount / columnsPerSide)
); );
leftDevices = parsedDevices.filter((item) => item.number > rightCapacity);
const buildRowPositions = (side: "left" | "right", rowIndexFromBottom: number) => { const unassigned = parsedDevices.filter(
const start = (item) => item.number <= 0 || item.number > seatCount
side === "right" );
? rowIndexFromBottom * columnsPerSide + 1 leftDevices = [...leftDevices, ...unassigned];
: rightCount + 1 + rowIndexFromBottom * columnsPerSide; } else {
const end = const splitIndex = Math.ceil(parsedDevices.length / 2);
side === "right" rightDevices = parsedDevices.slice(0, splitIndex);
? Math.min(start + columnsPerSide - 1, rightCount) leftDevices = parsedDevices.slice(splitIndex);
: Math.min(start + columnsPerSide - 1, seatCount);
const count = Math.max(0, end - start + 1);
const positions = Array<number | null>(columnsPerSide).fill(null);
if (count === 0) {
return positions;
} }
const numbers: number[] = []; const renderDevice = (item: (typeof parsedDevices)[number]) => {
for (let n = end; n >= start; n -= 1) { const device = item.device;
numbers.push(n); const position = item.number > 0 ? item.number : item.index + 1;
}
const offset = side === "right" ? 0 : columnsPerSide - count;
numbers.forEach((n, index) => {
positions[offset + index] = n;
});
return positions;
};
const renderSeat = (position: number | null, key: string) => {
if (!position) {
return <div key={key} className="w-24 h-24 shrink-0" />;
}
const device = deviceMap.get(position);
const macAddress = device?.networkInfos?.[0]?.macAddress || device?.id; const macAddress = device?.networkInfos?.[0]?.macAddress || device?.id;
const folderStatus = folderStatuses?.get(macAddress); const folderStatus = folderStatuses?.get(macAddress);
return ( return (
<ComputerCard <ComputerCard
key={key} key={device?.id || `device-${item.index}`}
device={device} device={device}
position={position} position={position}
folderStatus={folderStatus} folderStatus={folderStatus}
@ -89,33 +77,28 @@ export function DeviceGrid({
); );
}; };
const renderRow = (rowIndex: number) => { const gridTemplateColumns = "repeat(auto-fit, minmax(6rem, 6rem))";
const rowIndexFromBottom = totalRows - 1 - rowIndex;
const leftPositions = buildRowPositions("left", rowIndexFromBottom);
const rightPositions = buildRowPositions("right", rowIndexFromBottom);
return (
<div key={rowIndex} className="flex items-center justify-center gap-3">
{leftPositions.map((position, i) =>
renderSeat(position, `left-${rowIndex}-${i}`)
)}
{/* Đường chia giữa */}
<div className="w-32 flex items-center justify-center">
<div className="h-px w-full bg-border border-t-2 border-dashed" />
</div>
{rightPositions.map((position, i) =>
renderSeat(position, `right-${rowIndex}-${i}`)
)}
</div>
);
};
return ( return (
<div className="px-0.5 py-8 space-y-6"> <div className="px-0.5 py-8 space-y-6">
<div className="space-y-4"> <div className="flex items-start justify-center gap-6">
{Array.from({ length: totalRows }).map((_, i) => renderRow(i))} <div
className="grid flex-1 gap-3"
style={{ gridTemplateColumns, justifyContent: "end" }}
>
{leftDevices.map(renderDevice)}
</div>
<div className="flex items-stretch justify-center w-6">
<div className="w-px bg-border border-l-2 border-dashed" />
</div>
<div
className="grid flex-1 gap-3"
style={{ gridTemplateColumns, justifyContent: "start" }}
>
{rightDevices.map(renderDevice)}
</div>
</div> </div>
<div className="flex items-center justify-between px-4 pb-6 border-b-2 border-dashed"> <div className="flex items-center justify-between px-4 pb-6 border-b-2 border-dashed">
<div className="flex items-center gap-3 px-6 py-4 bg-muted rounded-lg border-2 border-border"> <div className="flex items-center gap-3 px-6 py-4 bg-muted rounded-lg border-2 border-border">