Initial kitchen scaffold — Phase 1 kitchen port (build-verified 2026-07-11)

FastAPI backend (Python 3.11, MSSQL ODBC for SambaPOS, Azure DI OCR),
kitchen_db on central PG. React/TS/Vite frontend with navy sidebar layout.

Backend: auth.py (APP_SLUG=kitchen, SimpleNamespace — archive routes use
.kitchen_id/.is_admin without modification), main.py (51 migrations, scheduler,
internal router for KDS bookings feed), api/internal.py, full archive API
(31 routers: invoices, recipes, menus, sambapos, resos, newbook, disputes,
purchase_orders, etc.), models, migrations, OCR pipeline.
kitchen_id pinned to 1 (B1 — single hotel).

Frontend: AuthGate (app=kitchen, token shim for archive compat — B5b pending),
Layout (navy sidebar, 6 sections, Lucide icons, teal --app-primary),
App.tsx (Outlet pattern, UploadApp outside Layout), index.css (full :root block).
strict: false — archive components have type issues; build clean.

Note: 45 archive components call fetch('/api/...') without /kitchen/ prefix
(B5b). Runtime 404s; deferred until after initial testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-12 12:15:39 +00:00
commit 8d688b459d
10003 changed files with 1928395 additions and 0 deletions

View file

@ -0,0 +1,95 @@
import { CSSProperties } from 'react'
interface FlagInfo {
name?: string | null
code?: string | null
icon?: string | null
category?: string | null
propagation?: string | null
active?: boolean | null
excludable?: boolean | null
food_flag_id?: number | null
flag_name?: string | null
flag_code?: string | null
flag_icon?: string | null
category_name?: string | null
propagation_type?: string | null
is_active?: boolean | null
excludable_on_request?: boolean | null
}
interface FlagSourceTrace {
[flagName: string]: string[]
}
interface Props {
flags: FlagInfo[]
size?: 'small' | 'medium'
source_ingredients?: FlagSourceTrace
}
export default function FoodFlagBadges({ flags, size = 'small', source_ingredients }: Props) {
const activeFlags = flags.filter(f => (f.active ?? f.is_active) !== false)
if (!activeFlags.length) return null
const fontSize = size === 'small' ? '0.7rem' : '0.8rem'
const padding = size === 'small' ? '1px 5px' : '2px 8px'
return (
<div style={styles.container}>
{activeFlags.map((f, i) => {
const name = f.name || f.flag_name || ''
const code = f.code || f.flag_code || ''
const icon = f.icon || f.flag_icon || ''
const propagation = f.propagation || f.propagation_type || 'contains'
const excludable = f.excludable ?? f.excludable_on_request ?? false
const bg = propagation === 'contains' ? '#dc3545' : '#28a745'
// Build tooltip with optional source trace
const sourceList = source_ingredients?.[name]
let tooltipText = name
if (sourceList && sourceList.length > 0) {
tooltipText += ` \u2014 from: ${sourceList.join(', ')}`
}
if (excludable) {
tooltipText += ' (excludable on request)'
}
return (
<span
key={i}
title={tooltipText}
style={{
...styles.badge,
background: bg,
fontSize,
padding,
border: excludable ? '1.5px dashed rgba(255,255,255,0.6)' : 'none',
opacity: excludable ? 0.85 : 1,
}}
>
{icon ? `${icon} ` : ''}{code || name.substring(0, 3)}
</span>
)
})}
</div>
)
}
const styles: Record<string, CSSProperties> = {
container: {
display: 'flex',
flexWrap: 'wrap',
gap: '3px',
alignItems: 'center',
},
badge: {
display: 'inline-block',
borderRadius: '10px',
color: 'white',
fontWeight: 600,
whiteSpace: 'nowrap',
lineHeight: 1.4,
},
}