Skip to content

Commit

Permalink
Merge branch 'whoopies'
Browse files Browse the repository at this point in the history
  • Loading branch information
koro committed Sep 27, 2024
2 parents 6accc79 + e9b0df9 commit 4b890c8
Show file tree
Hide file tree
Showing 40 changed files with 4,777 additions and 26 deletions.
46 changes: 46 additions & 0 deletions client/src/apps/admin/AdminApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { MaterialSymbol } from 'react-material-symbols';
import LinkButton from '../../components/LinkButton';
import { useStatusRecieve } from '../../lib/useStatus';
import { ScouterTable } from './components/ScouterTable';
import { MatchTable } from './components/MatchTable';
//import { useFetchJson } from "../../lib/useFetch";

function AdminApp() {
const status = useStatusRecieve();

// const [schedule] = useFetchJson<MatchSchedule>('/matchSchedule.json');

return (
<main className='flex h-screen w-screen select-none flex-col items-center text-center'>
<h1 className='col-span-4 my-8 text-3xl'>Admin Interface</h1>

<div className='fixed left-4 top-4 z-20 flex gap-2 rounded-md p-2'>
<LinkButton link='/' className='snap-none'>
<MaterialSymbol
icon='home'
size={60}
fill
grade={200}
color='green'
className='snap-none'
/>
</LinkButton>
</div>

<div className='grid grid-cols-2 items-center justify-center gap-4'>
<div>
<ScouterTable scouters={status.scouters} />
<p className='my-6'>Connected Tablets</p>
</div>
<div>
<p>Match Display</p>
<div className='table-container'>
<MatchTable matches={status.matches}></MatchTable>
</div>
</div>
</div>
</main>
);
}

export default AdminApp;
27 changes: 27 additions & 0 deletions client/src/apps/admin/components/MatchRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { RobotPosition, SuperPosition } from 'requests';
import PositionCell from './PositionCell';

function MatchRow({
matchNumber,
scouters,
}: {
matchNumber: string;
scouters: Record<RobotPosition, { schedule: number; real: number[] }> &
Record<SuperPosition, boolean>;
}) {
return (
<tr>
<th>{matchNumber}</th>
<PositionCell scouter={scouters.red_1} />
<PositionCell scouter={scouters.red_2} />
<PositionCell scouter={scouters.red_3} />
<PositionCell scouter={scouters.red_ss} />
<PositionCell scouter={scouters.blue_1} />
<PositionCell scouter={scouters.blue_2} />
<PositionCell scouter={scouters.blue_3} />
<PositionCell scouter={scouters.blue_ss} />
</tr>
);
}

export default MatchRow;
29 changes: 29 additions & 0 deletions client/src/apps/admin/components/MatchTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { StatusRecieve } from 'requests';
import MatchRow from './MatchRow';

function MatchTable({ matches }: { matches: StatusRecieve['matches'] }) {
return (
<table className='match-status h-72 overflow-auto'>
<thead>
<tr>
<th>Match</th>
<th className='status-red col-span-1'>Red 1</th>
<th className='status-red col-span-1'>Red 2</th>
<th className='status-red col-span-1'>Red 3</th>
<th className='status-red col-span-1'>Red SS</th>
<th className='status-blue col-span-1'>Blue 1</th>
<th className='status-blue col-span-1'>Blue 2</th>
<th className='status-blue col-span-1'>Blue 3</th>
<th className='status-blue col-span-1'>Blue SS</th>
</tr>
</thead>
<tbody>
{Object.entries(matches).map(([matchNumber, scouters]) => (
<MatchRow matchNumber={matchNumber} scouters={scouters} />
))}
</tbody>
</table>
);
}

export { MatchTable };
24 changes: 24 additions & 0 deletions client/src/apps/admin/components/PositionCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MaterialSymbol } from 'react-material-symbols';

function PositionCell({
scouter,
}: {
scouter: { schedule: number; real: number[] } | boolean;
}) {
const isBoolean = typeof scouter === 'boolean';
return (
<td
className={`w-auto border-2 border-slate-700 text-center ${(isBoolean ? scouter : scouter.real.length > 0) ? 'bg-amber-400' : ''}`}>
{isBoolean ? (
scouter && <MaterialSymbol icon='check' />
) : scouter.real.length === 0 ? (
scouter.schedule
) : scouter.real.length === 1 ? (
scouter.real[0]
) : (
<MaterialSymbol icon='warning' />
)}
</td>
);
}
export default PositionCell;
60 changes: 60 additions & 0 deletions client/src/apps/admin/components/ScouterCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { MaterialSymbol } from 'react-material-symbols';
import { StatusReport } from 'requests';

function BatteryLevelIcon(batteryLevel: number) {
if (batteryLevel > 75) {
return <MaterialSymbol icon='battery_full' className='mb-4 mr-3' />;
} else if (batteryLevel > 50) {
return <MaterialSymbol icon='battery_3_bar' className='mb-4 mr-3' />;
} else if (batteryLevel > 25) {
return <MaterialSymbol icon='battery_1_bar' className='mb-4 mr-3' />;
} else {
return <MaterialSymbol icon='battery_alert' className='mb-4 mr-3' />;
}
}
function ScouterCard({
scouter,
title,
red = false,
}: {
scouter: StatusReport[];
title: string;
red?: boolean;
}) {
return (
<div
className={`${red ? 'border-red-900 bg-red-500' : 'border-blue-900 bg-blue-500'} content-center items-center rounded-md border-2 px-4 text-center text-lg text-white`}>
<div className='mb-2 mt-4 font-semibold'>{title}</div>
{scouter.length === 0 ? (
<MaterialSymbol icon='wifi_off' size={32} />
) : scouter.length === 1 ? (
<>
<div className=''>{scouter[0].matchNumber}</div>
<div>{scouter[0].scouterName}</div>
<div>
{BatteryLevelIcon(
Math.floor((scouter[0].battery ?? 0) * 100)
)}
{Math.floor((scouter[0].battery ?? 0) * 100)}%
</div>
</>
) : (
<>
<div>
{' '}
<MaterialSymbol
icon='warning'
size={32}
className='text-yellow-300'
/>
</div>
{scouter.map(e => (
<div>{e.scouterName}</div>
))}
</>
)}
</div>
);
}

export { ScouterCard };
37 changes: 37 additions & 0 deletions client/src/apps/admin/components/ScouterTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { RobotPosition, StatusReport, SuperPosition } from 'requests';
import { ScouterCard } from './ScouterCard';

function ScouterTable({ scouters }: { scouters: StatusReport[] }) {
const sortedScouter = Object.fromEntries(
(
[
'red_1',
'red_2',
'red_3',
'blue_1',
'blue_2',
'blue_3',
'red_ss',
'blue_ss',
] satisfies (RobotPosition | SuperPosition)[]
).map(robotPosition => [
robotPosition,
scouters.filter(scouter => scouter.robotPosition === robotPosition),
])
) as Record<RobotPosition | SuperPosition, StatusReport[]>;

return (
<div className='grid grid-cols-4 gap-2'>
<ScouterCard scouter={sortedScouter.red_1} title='Red 1' red />
<ScouterCard scouter={sortedScouter.red_2} title='Red 2' red />
<ScouterCard scouter={sortedScouter.red_3} title='Red 3' red />
<ScouterCard scouter={sortedScouter.red_ss} title='Red SS' red />
<ScouterCard scouter={sortedScouter.blue_1} title='Blue 1' />
<ScouterCard scouter={sortedScouter.blue_2} title='Blue 2' />
<ScouterCard scouter={sortedScouter.blue_3} title='Blue 3' />
<ScouterCard scouter={sortedScouter.blue_ss} title='Blue SS' />
</div>
);
}

export { ScouterTable };
Loading

0 comments on commit 4b890c8

Please sign in to comment.