Initial scaffold: full-stack RackMapper application

Complete project scaffold with working auth, REST API, Prisma/SQLite
schema, Docker config, and React frontend for both Rack Planner and
Service Mapper modules. Both server and client pass TypeScript strict
mode with zero errors. Initial migration applied.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 21:48:56 -05:00
parent 61a4d37d94
commit 231de3d005
79 changed files with 12983 additions and 0 deletions
@@ -0,0 +1,84 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Plus, Download, Map, LogOut } from 'lucide-react';
import { toast } from 'sonner';
import { toPng } from 'html-to-image';
import { Button } from '../ui/Button';
import { AddRackModal } from '../modals/AddRackModal';
import { useAuthStore } from '../../store/useAuthStore';
interface RackToolbarProps {
rackCanvasRef: React.RefObject<HTMLDivElement | null>;
}
export function RackToolbar({ rackCanvasRef }: RackToolbarProps) {
const navigate = useNavigate();
const { logout } = useAuthStore();
const [addRackOpen, setAddRackOpen] = useState(false);
const [exporting, setExporting] = useState(false);
async function handleExport() {
if (!rackCanvasRef.current) return;
setExporting(true);
const toastId = toast.loading('Exporting…');
try {
const dataUrl = await toPng(rackCanvasRef.current, { cacheBust: true });
const link = document.createElement('a');
link.download = `rackmapper-rack-${Date.now()}.png`;
link.href = dataUrl;
link.click();
toast.success('Exported successfully', { id: toastId });
} catch {
toast.error('Export failed', { id: toastId });
} finally {
setExporting(false);
}
}
async function handleLogout() {
await logout();
navigate('/login', { replace: true });
}
return (
<>
<div className="flex items-center justify-between px-4 py-2 bg-slate-800 border-b border-slate-700">
{/* Left: brand */}
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<div className="w-6 h-6 bg-blue-500 rounded flex items-center justify-center">
<svg width="14" height="14" viewBox="0 0 18 18" fill="none">
<rect x="1" y="2" width="16" height="3" rx="1" fill="white" />
<rect x="1" y="7" width="16" height="3" rx="1" fill="white" opacity="0.7" />
<rect x="1" y="12" width="16" height="3" rx="1" fill="white" opacity="0.4" />
</svg>
</div>
<span className="text-sm font-bold text-slate-200 tracking-wider">RACKMAPPER</span>
</div>
<span className="text-slate-600 text-xs hidden sm:inline">Rack Planner</span>
</div>
{/* Right: actions */}
<div className="flex items-center gap-2">
<Button size="sm" variant="secondary" onClick={() => navigate('/map')}>
<Map size={14} />
Service Map
</Button>
<Button size="sm" onClick={() => setAddRackOpen(true)}>
<Plus size={14} />
Add Rack
</Button>
<Button size="sm" variant="secondary" onClick={handleExport} loading={exporting} disabled={exporting}>
<Download size={14} />
Export PNG
</Button>
<Button size="sm" variant="ghost" onClick={handleLogout} aria-label="Sign out">
<LogOut size={14} />
</Button>
</div>
</div>
<AddRackModal open={addRackOpen} onClose={() => setAddRackOpen(false)} />
</>
);
}