Production Planning
A production planner app for managing assembly line operations. Track each step of item production, assign operators to tasks, and monitor progress in real-time. Built with Orvanta scripts for database operations.
| 1 | { |
| 2 | "summary": "Production Planning", |
| 3 | "value": { |
| 4 | "runnables": { |
| 5 | "get_steps": { |
| 6 | "type": "inline", |
| 7 | "inlineScript": { |
| 8 | "lock": "{\n \"dependencies\": {\n \"@orvanta/client\": \"latest\"\n }\n}\n//bun.lock\n{\n \"lockfileVersion\": 1,\n \"workspaces\": {\n \"\": {\n \"dependencies\": {\n \"@orvanta/client\": \"latest\",\n },\n },\n },\n \"packages\": {\n \"@orvanta/client\": [\"@orvanta/client@1.624.0\", \"\", {}, \"sha512-Y/Y3/7wIe7gyiwtvbme/+wO5fdEqXaskTxHum+Iz8A6qwU66KFohfx/WBpuyXThC6StwFIq1xEX0wOqTqM42AA==\"],\n }\n}\n", |
| 9 | "content": "import * as orvanta from '@orvanta/client';\n\nexport async function main() {\n try {\n console.log('get_steps: Starting...');\n const sql = orvanta.datatable();\n console.log('get_steps: Got datatable connection');\n const steps = await sql`\n SELECT step_number, name, theoretical_duration_min\n FROM assemblyline.steps\n ORDER BY step_number\n `.fetch();\n console.log('get_steps: Query complete, rows:', steps?.length);\n return steps;\n } catch (error: any) {\n console.error('get_steps ERROR:', error?.message || error);\n throw error;\n }\n}\n", |
| 10 | "language": "bun" |
| 11 | } |
| 12 | }, |
| 13 | "get_orders": { |
| 14 | "type": "inline", |
| 15 | "inlineScript": { |
| 16 | "lock": "{\n \"dependencies\": {\n \"@orvanta/client\": \"latest\"\n }\n}\n//bun.lock\n{\n \"lockfileVersion\": 1,\n \"workspaces\": {\n \"\": {\n \"dependencies\": {\n \"@orvanta/client\": \"latest\",\n },\n },\n },\n \"packages\": {\n \"@orvanta/client\": [\"@orvanta/client@1.624.0\", \"\", {}, \"sha512-Y/Y3/7wIe7gyiwtvbme/+wO5fdEqXaskTxHum+Iz8A6qwU66KFohfx/WBpuyXThC6StwFIq1xEX0wOqTqM42AA==\"],\n }\n}\n", |
| 17 | "content": "import * as orvanta from '@orvanta/client';\n\nexport async function main(status?: string) {\n try {\n console.log('get_orders: Starting with status:', status);\n const sql = orvanta.datatable();\n console.log('get_orders: Got datatable connection');\n\n let result;\n if (status && status !== 'all') {\n result = await sql`\n SELECT f.*,\n COALESCE(s.name, 'Non démarré') as current_step_name\n FROM assemblyline.orders f\n LEFT JOIN assemblyline.steps s ON s.step_number = f.current_step\n WHERE f.status = ${status}\n ORDER BY\n CASE f.priority\n WHEN 'urgent' THEN 1\n WHEN 'high' THEN 2\n WHEN 'normal' THEN 3\n ELSE 4\n END,\n f.due_date ASC NULLS LAST\n `.fetch();\n } else {\n result = await sql`\n SELECT f.*,\n COALESCE(s.name, 'Non démarré') as current_step_name\n FROM assemblyline.orders f\n LEFT JOIN assemblyline.steps s ON s.step_number = f.current_step\n ORDER BY\n CASE f.priority\n WHEN 'urgent' THEN 1\n WHEN 'high' THEN 2\n WHEN 'normal' THEN 3\n ELSE 4\n END,\n f.due_date ASC NULLS LAST\n `.fetch();\n }\n console.log('get_orders: Query complete, rows:', result?.length);\n return result;\n } catch (error: any) {\n console.error('get_orders ERROR:', error?.message || error);\n throw error;\n }\n}\n", |
| 18 | "language": "bun" |
| 19 | } |
| 20 | }, |
| 21 | "init_steps": { |
| 22 | "type": "inline", |
| 23 | "inlineScript": { |
| 24 | "lock": "{\n \"dependencies\": {\n \"@orvanta/client\": \"latest\"\n }\n}\n//bun.lock\n{\n \"lockfileVersion\": 1,\n \"workspaces\": {\n \"\": {\n \"dependencies\": {\n \"@orvanta/client\": \"latest\",\n },\n },\n },\n \"packages\": {\n \"@orvanta/client\": [\"@orvanta/client@1.624.0\", \"\", {}, \"sha512-Y/Y3/7wIe7gyiwtvbme/+wO5fdEqXaskTxHum+Iz8A6qwU66KFohfx/WBpuyXThC6StwFIq1xEX0wOqTqM42AA==\"],\n }\n}\n", |
| 25 | "content": "import * as orvanta from '@orvanta/client';\n\nexport async function main() {\n const sql = orvanta.datatable();\n\n // Define the 7 manufacturing steps\n const steps = [\n { step_number: 1, name: 'Order Received', theoretical_duration_min: 30 },\n { step_number: 2, name: 'Materials Ready', theoretical_duration_min: 45 },\n { step_number: 3, name: 'Production', theoretical_duration_min: 60 },\n { step_number: 4, name: 'Assembly', theoretical_duration_min: 90 },\n { step_number: 5, name: 'Quality Check', theoretical_duration_min: 30 },\n { step_number: 6, name: 'Packaging', theoretical_duration_min: 45 },\n { step_number: 7, name: 'Ready to Ship', theoretical_duration_min: 20 }\n ];\n\n // Clear existing steps and insert new ones\n await sql`DELETE FROM assemblyline.steps`;\n\n for (const step of steps) {\n await sql`\n INSERT INTO assemblyline.steps (step_number, name, theoretical_duration_min)\n VALUES (${step.step_number}, ${step.name}, ${step.theoretical_duration_min})\n `;\n }\n\n return { success: true, message: 'Steps initialized', steps_count: steps.length };\n}\n", |
| 26 | "language": "bun" |
| 27 | } |
| 28 | }, |
| 29 | "start_step": { |
| 30 | "type": "inline", |
| 31 | "inlineScript": { |
| 32 | "lock": "{\n \"dependencies\": {\n \"@orvanta/client\": \"latest\"\n }\n}\n//bun.lock\n{\n \"lockfileVersion\": 1,\n \"workspaces\": {\n \"\": {\n \"dependencies\": {\n \"@orvanta/client\": \"latest\",\n },\n },\n },\n \"packages\": {\n \"@orvanta/client\": [\"@orvanta/client@1.624.0\", \"\", {}, \"sha512-Y/Y3/7wIe7gyiwtvbme/+wO5fdEqXaskTxHum+Iz8A6qwU66KFohfx/WBpuyXThC6StwFIq1xEX0wOqTqM42AA==\"],\n }\n}\n", |
| 33 | "content": "import * as orvanta from '@orvanta/client';\n\nexport async function main(\n order_id: string,\n step_number: number,\n operator_id: string\n) {\n const sql = orvanta.datatable();\n\n // Validate step number\n if (step_number < 1 || step_number > 7) {\n throw new Error('Step number must be between 1 and 7');\n }\n\n // Check if previous step is completed (except for step 1)\n if (step_number > 1) {\n const prevStep = await sql`\n SELECT status FROM assemblyline.step_logs\n WHERE order_id = ${order_id} AND step_number = ${step_number - 1}\n `.fetchOne();\n\n if (prevStep?.status !== 'completed') {\n throw new Error(`Step ${step_number - 1} must be completed first`);\n }\n }\n\n // Start the step\n await sql`\n UPDATE assemblyline.step_logs\n SET status = 'in_progress',\n operator_id = ${operator_id},\n started_at = NOW()\n WHERE order_id = ${order_id} AND step_number = ${step_number}\n `.execute();\n\n // Update order status and current step\n await sql`\n UPDATE assemblyline.orders\n SET status = 'in_progress', current_step = ${step_number}\n WHERE id = ${order_id}\n `.execute();\n\n return { success: true, message: `Step ${step_number} started` };\n}\n", |
| 34 | "language": "bun" |
| 35 | } |
| 36 | }, |
| 37 | "create_order": { |
| 38 | "type": "inline", |
| 39 | "inlineScript": { |
| 40 | "lock": "{\n \"dependencies\": {\n \"@orvanta/client\": \"latest\"\n }\n}\n//bun.lock\n{\n \"lockfileVersion\": 1,\n \"workspaces\": {\n \"\": {\n \"dependencies\": {\n \"@orvanta/client\": \"latest\",\n },\n },\n },\n \"packages\": {\n \"@orvanta/client\": [\"@orvanta/client@1.624.0\", \"\", {}, \"sha512-Y/Y3/7wIe7gyiwtvbme/+wO5fdEqXaskTxHum+Iz8A6qwU66KFohfx/WBpuyXThC6StwFIq1xEX0wOqTqM42AA==\"],\n }\n}\n", |
| 41 | "content": "import * as orvanta from '@orvanta/client';\n\nexport async function main(\n id: string,\n client_name: string,\n product_description?: string,\n quantity?: number,\n priority?: string,\n due_date?: string\n) {\n const sql = orvanta.datatable();\n\n // Create order\n const dueDateValue = due_date ? new Date(due_date) : null;\n await sql`\n INSERT INTO assemblyline.orders (id, client_name, product_description, quantity, priority, due_date)\n VALUES (${id}, ${client_name}, ${product_description || null}, ${quantity || 1}, ${priority || 'normal'}, ${dueDateValue}::date)\n `.execute();\n\n // Initialize all 7 step logs for this order\n for (let step = 1; step <= 7; step++) {\n await sql`\n INSERT INTO assemblyline.step_logs (order_id, step_number, status)\n VALUES (${id}, ${step}, 'pending')\n `.execute();\n }\n\n return { success: true, order_id: id };\n}\n", |
| 42 | "language": "bun" |
| 43 | } |
| 44 | }, |
| 45 | "complete_step": { |
| 46 | "type": "inline", |
| 47 | "inlineScript": { |
| 48 | "lock": "{\n \"dependencies\": {\n \"@orvanta/client\": \"latest\"\n }\n}\n//bun.lock\n{\n \"lockfileVersion\": 1,\n \"workspaces\": {\n \"\": {\n \"dependencies\": {\n \"@orvanta/client\": \"latest\",\n },\n },\n },\n \"packages\": {\n \"@orvanta/client\": [\"@orvanta/client@1.624.0\", \"\", {}, \"sha512-Y/Y3/7wIe7gyiwtvbme/+wO5fdEqXaskTxHum+Iz8A6qwU66KFohfx/WBpuyXThC6StwFIq1xEX0wOqTqM42AA==\"],\n }\n}\n", |
| 49 | "content": "import * as orvanta from '@orvanta/client';\n\nexport async function main(\n order_id: string,\n step_number: number,\n notes?: string\n) {\n const sql = orvanta.datatable();\n\n // Get current step info\n const stepLog = await sql`\n SELECT * FROM assemblyline.step_logs\n WHERE order_id = ${order_id} AND step_number = ${step_number}\n `.fetchOne();\n\n if (!stepLog) {\n throw new Error('Step not found');\n }\n\n if (stepLog.status !== 'in_progress') {\n throw new Error('Step must be in progress to complete');\n }\n\n // Calculate actual duration\n const startTime = new Date(stepLog.started_at).getTime();\n const endTime = Date.now();\n const durationMin = Math.round((endTime - startTime) / 60000);\n\n // Complete the step\n await sql`\n UPDATE assemblyline.step_logs\n SET status = 'completed',\n completed_at = NOW(),\n actual_duration_min = ${durationMin},\n notes = ${notes || null}\n WHERE order_id = ${order_id} AND step_number = ${step_number}\n `.execute();\n\n // Check if this was the last step\n if (step_number === 7) {\n await sql`\n UPDATE assemblyline.orders\n SET status = 'completed', completed_at = NOW()\n WHERE id = ${order_id}\n `.execute();\n }\n\n // Get theoretical duration for comparison\n const stepRef = await sql`\n SELECT theoretical_duration_min FROM assemblyline.steps\n WHERE step_number = ${step_number}\n `.fetchOne();\n\n const theoreticalMin = stepRef?.theoretical_duration_min || 15;\n const isDelayed = durationMin > theoreticalMin;\n\n return {\n success: true,\n actual_duration_min: durationMin,\n theoretical_duration_min: theoreticalMin,\n is_delayed: isDelayed,\n delay_min: isDelayed ? durationMin - theoreticalMin : 0\n };\n}\n", |
| 50 | "language": "bun" |
| 51 | } |
| 52 | }, |
| 53 | "get_order_progress": { |
| 54 | "type": "inline", |
| 55 | "inlineScript": { |
| 56 | "lock": "{\n \"dependencies\": {\n \"@orvanta/client\": \"latest\"\n }\n}\n//bun.lock\n{\n \"lockfileVersion\": 1,\n \"workspaces\": {\n \"\": {\n \"dependencies\": {\n \"@orvanta/client\": \"latest\",\n },\n },\n },\n \"packages\": {\n \"@orvanta/client\": [\"@orvanta/client@1.624.0\", \"\", {}, \"sha512-Y/Y3/7wIe7gyiwtvbme/+wO5fdEqXaskTxHum+Iz8A6qwU66KFohfx/WBpuyXThC6StwFIq1xEX0wOqTqM42AA==\"],\n }\n}\n", |
| 57 | "content": "import * as orvanta from '@orvanta/client';\n\nexport async function main(order_id: string) {\n const sql = orvanta.datatable();\n\n // Get order info\n const order = await sql`\n SELECT * FROM assemblyline.orders WHERE id = ${order_id}\n `.fetchOne();\n\n if (!order) {\n throw new Error('Order not found');\n }\n\n // Get all steps with their logs (if any)\n const steps = await sql`\n SELECT\n s.step_number,\n s.name as step_name,\n s.theoretical_duration_min,\n COALESCE(sl.status, 'pending') as status,\n sl.operator_id,\n sl.started_at,\n sl.completed_at,\n sl.actual_duration_min,\n sl.notes\n FROM assemblyline.steps s\n LEFT JOIN assemblyline.step_logs sl ON sl.step_number = s.step_number AND sl.order_id = ${order_id}\n ORDER BY s.step_number\n `.fetch();\n\n // Calculate progress\n const completedSteps = steps.filter((s: any) => s.status === 'completed').length;\n const progressPercent = Math.round((completedSteps / 7) * 100);\n\n // Calculate total time\n const totalActual = steps.reduce((sum: number, s: any) => sum + (s.actual_duration_min || 0), 0);\n const totalTheoretical = steps.reduce((sum: number, s: any) => sum + s.theoretical_duration_min, 0);\n\n return {\n order,\n steps,\n progress: {\n completed_steps: completedSteps,\n total_steps: 7,\n percent: progressPercent,\n total_actual_min: totalActual,\n total_theoretical_min: totalTheoretical,\n efficiency: totalActual > 0 ? Math.round((totalTheoretical / totalActual) * 100) : null\n }\n };\n}\n", |
| 58 | "language": "bun" |
| 59 | } |
| 60 | }, |
| 61 | "get_dashboard_stats": { |
| 62 | "type": "inline", |
| 63 | "inlineScript": { |
| 64 | "lock": "{\n \"dependencies\": {\n \"@orvanta/client\": \"latest\"\n }\n}\n//bun.lock\n{\n \"lockfileVersion\": 1,\n \"workspaces\": {\n \"\": {\n \"dependencies\": {\n \"@orvanta/client\": \"latest\",\n },\n },\n },\n \"packages\": {\n \"@orvanta/client\": [\"@orvanta/client@1.624.0\", \"\", {}, \"sha512-Y/Y3/7wIe7gyiwtvbme/+wO5fdEqXaskTxHum+Iz8A6qwU66KFohfx/WBpuyXThC6StwFIq1xEX0wOqTqM42AA==\"],\n }\n}\n", |
| 65 | "content": "import * as orvanta from '@orvanta/client';\n\nexport async function main() {\n const sql = orvanta.datatable();\n\n // Get order counts by status\n const statusCounts = await sql`\n SELECT status, COUNT(*) as count\n FROM assemblyline.orders\n GROUP BY status\n `.fetch();\n\n // Get urgent/high priority orders in progress\n const priorityFolders = await sql`\n SELECT f.id, f.client_name, f.priority, f.due_date,\n s.name as current_step_name, f.current_step\n FROM assemblyline.orders f\n LEFT JOIN assemblyline.steps s ON s.step_number = f.current_step\n WHERE f.status = 'in_progress'\n AND f.priority IN ('urgent', 'high')\n ORDER BY\n CASE f.priority WHEN 'urgent' THEN 1 ELSE 2 END,\n f.due_date ASC\n LIMIT 5\n `.fetch();\n\n // Get step performance (avg actual vs theoretical)\n const stepPerformance = await sql`\n SELECT s.step_number, s.name, s.theoretical_duration_min,\n ROUND(AVG(sl.actual_duration_min)) as avg_actual_min,\n COUNT(sl.id) FILTER (WHERE sl.status = 'completed') as completed_count\n FROM assemblyline.steps s\n LEFT JOIN assemblyline.step_logs sl ON sl.step_number = s.step_number\n AND sl.status = 'completed'\n GROUP BY s.step_number, s.name, s.theoretical_duration_min\n ORDER BY s.step_number\n `.fetch();\n\n // Get orders due today or overdue\n const dueSoon = await sql`\n SELECT COUNT(*) as count\n FROM assemblyline.orders\n WHERE status NOT IN ('completed', 'cancelled')\n AND due_date <= CURRENT_DATE\n `.fetchOne();\n\n return {\n status_counts: statusCounts,\n priority_orders: priorityFolders,\n step_performance: stepPerformance,\n overdue_count: dueSoon?.count || 0\n };\n}\n", |
| 66 | "language": "bun" |
| 67 | } |
| 68 | } |
| 69 | }, |
| 70 | "files": { |
| 71 | "/App.tsx": "import React, { useState, useEffect, useMemo } from 'react'\nimport { backend } from './wmill'\nimport './index.css'\n\ntype Order = {\n id: string;\n client_name: string;\n product_description: string;\n quantity: number;\n priority: string;\n due_date: string;\n status: string;\n current_step: number;\n current_step_name: string;\n created_at: string;\n};\n\ntype Step = {\n step_number: number;\n name: string;\n theoretical_duration_min: number;\n};\n\nconst PRIORITY_CONFIG: Record<string, { color: string; bg: string; label: string }> = {\n urgent: { color: '#dc2626', bg: '#fef2f2', label: 'Urgent' },\n high: { color: '#ea580c', bg: '#fff7ed', label: 'High' },\n normal: { color: '#2563eb', bg: '#eff6ff', label: 'Normal' },\n low: { color: '#6b7280', bg: '#f9fafb', label: 'Low' }\n};\n\nconst STATUS_CONFIG: Record<string, { color: string; bg: string; label: string }> = {\n pending: { color: '#6b7280', bg: '#f3f4f6', label: 'Pending' },\n in_progress: { color: '#d97706', bg: '#fef3c7', label: 'In Progress' },\n completed: { color: '#059669', bg: '#d1fae5', label: 'Completed' },\n cancelled: { color: '#dc2626', bg: '#fef2f2', label: 'Cancelled' }\n};\n\nconst App = () => {\n const [allOrders, setAllOrders] = useState<Order[]>([]);\n const [steps, setSteps] = useState<Step[]>([]);\n const [loading, setLoading] = useState(false);\n const [selectedOrder, setSelectedOrder] = useState<string | null>(null);\n const [allProgress, setAllProgress] = useState<Record<string, any>>({});\n const [showNewForm, setShowNewForm] = useState(false);\n const [filterStatus, setFilterStatus] = useState('all');\n\n // Modal states\n const [showStartModal, setShowStartModal] = useState(false);\n const [showCompleteModal, setShowCompleteModal] = useState(false);\n const [modalStepNumber, setModalStepNumber] = useState<number | null>(null);\n const [operatorId, setOperatorId] = useState('');\n const [completionNotes, setCompletionNotes] = useState('');\n\n const [newOrder, setNewOrder] = useState({\n id: '',\n client_name: '',\n product_description: '',\n quantity: 1,\n priority: 'normal',\n due_date: ''\n });\n\n // Filter orders client-side based on selected status\n const filteredOrders = useMemo(() => {\n if (filterStatus === 'all') {\n return allOrders;\n }\n return allOrders.filter(order => order.status === filterStatus);\n }, [allOrders, filterStatus]);\n\n const orderProgress = selectedOrder ? allProgress[selectedOrder] ?? null : null;\n\n async function loadData() {\n setLoading(true);\n try {\n const [ordersData, stepsData] = await Promise.all([\n backend.get_orders({ status: 'all' }),\n backend.get_steps({})\n ]);\n const ordersArray = Array.isArray(ordersData) ? ordersData : [];\n const stepsArray = Array.isArray(stepsData) ? stepsData : [];\n setAllOrders(ordersArray as Order[]);\n setSteps(stepsArray as Step[]);\n\n // Load progress for all orders in parallel\n const orders = ordersArray as Order[];\n const progressResults = await Promise.all(\n orders.map(o => backend.get_order_progress({ order_id: o.id }).catch(() => null))\n );\n const progressMap: Record<string, any> = {};\n orders.forEach((o, i) => {\n if (progressResults[i]) progressMap[o.id] = progressResults[i];\n });\n setAllProgress(progressMap);\n } catch (e) {\n console.error('Error loading data:', e);\n }\n setLoading(false);\n }\n\n // Load data on mount\n useEffect(() => {\n loadData();\n }, []);\n\n async function handleCreateOrder(e: React.FormEvent) {\n e.preventDefault();\n try {\n const result = await backend.create_order({\n id: newOrder.id,\n client_name: newOrder.client_name,\n product_description: newOrder.product_description || undefined,\n quantity: newOrder.quantity,\n priority: newOrder.priority,\n due_date: newOrder.due_date || undefined\n });\n if (result && typeof result === 'object' && 'error' in result) {\n alert('Error: ' + JSON.stringify(result));\n return;\n }\n setShowNewForm(false);\n setNewOrder({ id: '', client_name: '', product_description: '', quantity: 1, priority: 'normal', due_date: '' });\n loadData();\n } catch (e: any) {\n alert('Error creating order: ' + (e?.message || JSON.stringify(e)));\n }\n }\n\n function openStartModal(stepNumber: number) {\n setModalStepNumber(stepNumber);\n setOperatorId('');\n setShowStartModal(true);\n }\n\n function openCompleteModal(stepNumber: number) {\n setModalStepNumber(stepNumber);\n setCompletionNotes('');\n setShowCompleteModal(true);\n }\n\n async function handleStartStep() {\n if (!selectedOrder || !modalStepNumber || !operatorId.trim()) return;\n\n try {\n await backend.start_step({ order_id: selectedOrder, step_number: modalStepNumber, operator_id: operatorId.trim() });\n setShowStartModal(false);\n setOperatorId('');\n loadData();\n loadOrderProgress(selectedOrder);\n } catch (e: any) {\n alert('Error: ' + e.message);\n }\n }\n\n async function handleCompleteStep() {\n if (!selectedOrder || !modalStepNumber) return;\n\n try {\n await backend.complete_step({\n order_id: selectedOrder,\n step_number: modalStepNumber,\n notes: completionNotes.trim() || undefined\n });\n setShowCompleteModal(false);\n setCompletionNotes('');\n loadData();\n loadOrderProgress(selectedOrder);\n } catch (e: any) {\n alert('Error: ' + e.message);\n }\n }\n\n function loadOrderProgress(orderId: string) {\n setSelectedOrder(orderId);\n }\n\n return (\n <div style={{\n minHeight: '100vh',\n background: 'linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%)',\n padding: '32px',\n fontFamily: \"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\"\n }}>\n <div style={{ maxWidth: '1400px', margin: '0 auto' }}>\n {/* Header */}\n <div style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: '32px'\n }}>\n <div>\n <h1 style={{\n fontSize: '28px',\n fontWeight: '700',\n color: '#0f172a',\n margin: 0,\n letterSpacing: '-0.025em'\n }}>\n Production Planning\n </h1>\n <p style={{ color: '#64748b', margin: '4px 0 0 0', fontSize: '14px' }}>\n Order and manufacturing tracking\n </p>\n </div>\n <div style={{\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n padding: '8px 16px',\n background: 'white',\n borderRadius: '12px',\n boxShadow: '0 1px 3px rgba(0,0,0,0.08)'\n }}>\n <div style={{\n width: '8px',\n height: '8px',\n borderRadius: '50%',\n background: '#22c55e',\n animation: 'pulse 2s infinite'\n }} />\n <span style={{ fontSize: '13px', color: '#64748b', fontWeight: '500' }}>Demo App</span>\n </div>\n </div>\n\n {/* Controls Bar */}\n <div style={{\n display: 'flex',\n gap: '12px',\n marginBottom: '24px',\n flexWrap: 'wrap',\n alignItems: 'center'\n }}>\n <button\n onClick={() => setShowNewForm(!showNewForm)}\n style={{\n padding: '12px 24px',\n background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',\n color: 'white',\n border: 'none',\n borderRadius: '12px',\n cursor: 'pointer',\n fontWeight: '600',\n fontSize: '14px',\n boxShadow: '0 4px 12px rgba(59, 130, 246, 0.35)',\n transition: 'all 0.2s ease',\n display: 'flex',\n alignItems: 'center',\n gap: '8px'\n }}\n >\n <svg width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" viewBox=\"0 0 24 24\">\n <path d=\"M12 5v14M5 12h14\" strokeLinecap=\"round\"/>\n </svg>\n New Order\n </button>\n\n <div style={{\n display: 'flex',\n background: 'white',\n borderRadius: '12px',\n padding: '4px',\n boxShadow: '0 1px 3px rgba(0,0,0,0.08)'\n }}>\n {[\n { value: 'all', label: 'All' },\n { value: 'pending', label: 'Pending' },\n { value: 'in_progress', label: 'In Progress' },\n { value: 'completed', label: 'Completed' }\n ].map(opt => (\n <button\n key={opt.value}\n onClick={() => setFilterStatus(opt.value)}\n style={{\n padding: '8px 16px',\n background: filterStatus === opt.value ? '#f1f5f9' : 'transparent',\n border: 'none',\n borderRadius: '8px',\n cursor: 'pointer',\n fontWeight: filterStatus === opt.value ? '600' : '500',\n fontSize: '13px',\n color: filterStatus === opt.value ? '#0f172a' : '#64748b',\n transition: 'all 0.15s ease'\n }}\n >\n {opt.label}\n </button>\n ))}\n </div>\n\n <div style={{ marginLeft: 'auto', fontSize: '13px', color: '#64748b' }}>\n {filteredOrders.length} order{filteredOrders.length !== 1 ? 's' : ''}\n </div>\n </div>\n\n {/* New Order Form */}\n {showNewForm && (\n <div style={{\n background: 'white',\n borderRadius: '16px',\n padding: '24px',\n marginBottom: '24px',\n boxShadow: '0 4px 20px rgba(0,0,0,0.08)',\n border: '1px solid #e2e8f0'\n }}>\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>\n <h3 style={{ margin: 0, fontSize: '16px', fontWeight: '600', color: '#0f172a' }}>\n Create New Order\n </h3>\n <button\n onClick={() => setShowNewForm(false)}\n style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '4px' }}\n >\n <svg width=\"20\" height=\"20\" fill=\"none\" stroke=\"#94a3b8\" strokeWidth=\"2\" viewBox=\"0 0 24 24\">\n <path d=\"M6 18L18 6M6 6l12 12\" strokeLinecap=\"round\"/>\n </svg>\n </button>\n </div>\n <form onSubmit={handleCreateOrder}>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '16px' }}>\n <div>\n <label style={{ display: 'block', fontSize: '12px', fontWeight: '600', color: '#475569', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>\n Order # *\n </label>\n <input\n placeholder=\"e.g. FLD-2024-001\"\n value={newOrder.id}\n onChange={(e) => setNewOrder({ ...newOrder, id: e.target.value })}\n required\n style={{\n width: '100%',\n padding: '12px 14px',\n borderRadius: '10px',\n border: '1px solid #e2e8f0',\n fontSize: '14px',\n transition: 'all 0.15s ease',\n outline: 'none',\n boxSizing: 'border-box'\n }}\n />\n </div>\n <div>\n <label style={{ display: 'block', fontSize: '12px', fontWeight: '600', color: '#475569', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>\n Client *\n </label>\n <input\n placeholder=\"Client name\"\n value={newOrder.client_name}\n onChange={(e) => setNewOrder({ ...newOrder, client_name: e.target.value })}\n required\n style={{\n width: '100%',\n padding: '12px 14px',\n borderRadius: '10px',\n border: '1px solid #e2e8f0',\n fontSize: '14px',\n outline: 'none',\n boxSizing: 'border-box'\n }}\n />\n </div>\n <div>\n <label style={{ display: 'block', fontSize: '12px', fontWeight: '600', color: '#475569', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>\n Description\n </label>\n <input\n placeholder=\"Product description\"\n value={newOrder.product_description}\n onChange={(e) => setNewOrder({ ...newOrder, product_description: e.target.value })}\n style={{\n width: '100%',\n padding: '12px 14px',\n borderRadius: '10px',\n border: '1px solid #e2e8f0',\n fontSize: '14px',\n outline: 'none',\n boxSizing: 'border-box'\n }}\n />\n </div>\n <div>\n <label style={{ display: 'block', fontSize: '12px', fontWeight: '600', color: '#475569', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>\n Quantity\n </label>\n <input\n type=\"number\"\n value={newOrder.quantity}\n onChange={(e) => setNewOrder({ ...newOrder, quantity: parseInt(e.target.value) || 1 })}\n min={1}\n style={{\n width: '100%',\n padding: '12px 14px',\n borderRadius: '10px',\n border: '1px solid #e2e8f0',\n fontSize: '14px',\n outline: 'none',\n boxSizing: 'border-box'\n }}\n />\n </div>\n <div>\n <label style={{ display: 'block', fontSize: '12px', fontWeight: '600', color: '#475569', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>\n Priority\n </label>\n <select\n value={newOrder.priority}\n onChange={(e) => setNewOrder({ ...newOrder, priority: e.target.value })}\n style={{\n width: '100%',\n padding: '12px 14px',\n borderRadius: '10px',\n border: '1px solid #e2e8f0',\n fontSize: '14px',\n outline: 'none',\n boxSizing: 'border-box',\n background: 'white',\n cursor: 'pointer'\n }}\n >\n <option value=\"low\">Low</option>\n <option value=\"normal\">Normal</option>\n <option value=\"high\">High</option>\n <option value=\"urgent\">Urgent</option>\n </select>\n </div>\n <div>\n <label style={{ display: 'block', fontSize: '12px', fontWeight: '600', color: '#475569', marginBottom: '6px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>\n Due Date\n </label>\n <input\n type=\"date\"\n value={newOrder.due_date}\n onChange={(e) => setNewOrder({ ...newOrder, due_date: e.target.value })}\n style={{\n width: '100%',\n padding: '12px 14px',\n borderRadius: '10px',\n border: '1px solid #e2e8f0',\n fontSize: '14px',\n outline: 'none',\n boxSizing: 'border-box'\n }}\n />\n </div>\n </div>\n <div style={{ marginTop: '20px', display: 'flex', gap: '12px' }}>\n <button\n type=\"submit\"\n style={{\n padding: '12px 28px',\n background: 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)',\n color: 'white',\n border: 'none',\n borderRadius: '10px',\n cursor: 'pointer',\n fontWeight: '600',\n fontSize: '14px',\n boxShadow: '0 4px 12px rgba(34, 197, 94, 0.35)'\n }}\n >\n Create Order\n </button>\n <button\n type=\"button\"\n onClick={() => setShowNewForm(false)}\n style={{\n padding: '12px 28px',\n background: '#f1f5f9',\n color: '#475569',\n border: 'none',\n borderRadius: '10px',\n cursor: 'pointer',\n fontWeight: '600',\n fontSize: '14px'\n }}\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Main Content Grid */}\n <div style={{\n display: 'grid',\n gridTemplateColumns: selectedOrder ? '1fr 1fr' : '1fr',\n gap: '24px',\n alignItems: 'start'\n }}>\n {/* Orders List */}\n <div>\n {loading ? (\n <div style={{\n background: 'white',\n borderRadius: '16px',\n padding: '60px 24px',\n textAlign: 'center',\n boxShadow: '0 1px 3px rgba(0,0,0,0.08)'\n }}>\n <div style={{\n width: '40px',\n height: '40px',\n border: '3px solid #e2e8f0',\n borderTopColor: '#3b82f6',\n borderRadius: '50%',\n margin: '0 auto 16px',\n animation: 'spin 1s linear infinite'\n }} />\n <p style={{ color: '#64748b', margin: 0 }}>Loading...</p>\n </div>\n ) : filteredOrders.length === 0 ? (\n <div style={{\n background: 'white',\n borderRadius: '16px',\n padding: '60px 24px',\n textAlign: 'center',\n boxShadow: '0 1px 3px rgba(0,0,0,0.08)'\n }}>\n <div style={{\n width: '64px',\n height: '64px',\n background: '#f1f5f9',\n borderRadius: '16px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n margin: '0 auto 16px'\n }}>\n <svg width=\"28\" height=\"28\" fill=\"none\" stroke=\"#94a3b8\" strokeWidth=\"1.5\" viewBox=\"0 0 24 24\">\n <path d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\" strokeLinecap=\"round\" strokeLinejoin=\"round\"/>\n </svg>\n </div>\n <p style={{ color: '#64748b', margin: 0, fontSize: '15px' }}>No orders found</p>\n <p style={{ color: '#94a3b8', margin: '8px 0 0', fontSize: '13px' }}>\n {filterStatus !== 'all' ? 'Try selecting a different status filter or ' : ''}Create your first order\n </p>\n </div>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>\n {filteredOrders.map((order) => {\n const priorityConf = PRIORITY_CONFIG[order.priority] || PRIORITY_CONFIG.normal;\n const statusConf = STATUS_CONFIG[order.status] || STATUS_CONFIG.pending;\n const isSelected = selectedOrder === order.id;\n\n return (\n <div\n key={order.id}\n onClick={() => loadOrderProgress(order.id)}\n style={{\n padding: '20px',\n background: isSelected ? '#f8fafc' : 'white',\n border: isSelected ? '2px solid #3b82f6' : '1px solid #e2e8f0',\n borderRadius: '14px',\n cursor: 'pointer',\n transition: 'all 0.2s ease',\n boxShadow: isSelected ? '0 4px 20px rgba(59, 130, 246, 0.15)' : '0 1px 3px rgba(0,0,0,0.05)'\n }}\n >\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '12px' }}>\n <div>\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>\n <span style={{\n fontSize: '16px',\n fontWeight: '700',\n color: '#0f172a'\n }}>\n {order.id}\n </span>\n <span style={{\n padding: '3px 8px',\n borderRadius: '6px',\n fontSize: '11px',\n fontWeight: '600',\n background: priorityConf.bg,\n color: priorityConf.color,\n textTransform: 'uppercase',\n letterSpacing: '0.03em'\n }}>\n {priorityConf.label}\n </span>\n </div>\n <div style={{ color: '#475569', marginTop: '4px', fontSize: '14px', fontWeight: '500' }}>\n {order.client_name}\n </div>\n </div>\n <span style={{\n padding: '6px 12px',\n borderRadius: '8px',\n fontSize: '12px',\n fontWeight: '600',\n background: statusConf.bg,\n color: statusConf.color\n }}>\n {statusConf.label}\n </span>\n </div>\n\n {/* Mini Progress */}\n <div style={{ marginBottom: '12px' }}>\n <div style={{ display: 'flex', gap: '4px' }}>\n {[1, 2, 3, 4, 5, 6, 7].map(step => (\n <div\n key={step}\n style={{\n flex: 1,\n height: '4px',\n borderRadius: '2px',\n background: step <= order.current_step\n ? (order.status === 'completed' ? '#22c55e' : '#3b82f6')\n : '#e2e8f0'\n }}\n />\n ))}\n </div>\n </div>\n\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\n <span style={{ fontSize: '13px', color: '#64748b' }}>\n {order.current_step === 0 ? 'Not started' : `Step ${order.current_step}/7 - ${order.current_step_name}`}\n </span>\n {order.due_date && (\n <span style={{\n fontSize: '12px',\n color: new Date(order.due_date) < new Date() && order.status !== 'completed' ? '#dc2626' : '#64748b',\n display: 'flex',\n alignItems: 'center',\n gap: '4px'\n }}>\n <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" viewBox=\"0 0 24 24\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/>\n <line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"/>\n <line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"/>\n </svg>\n {new Date(order.due_date).toLocaleDateString('en-US')}\n </span>\n )}\n </div>\n </div>\n );\n })}\n </div>\n )}\n </div>\n\n {/* Order Detail Panel */}\n {selectedOrder && orderProgress && (\n <div style={{\n background: 'white',\n borderRadius: '16px',\n boxShadow: '0 4px 20px rgba(0,0,0,0.08)',\n border: '1px solid #e2e8f0',\n overflow: 'hidden',\n position: 'sticky',\n top: '32px'\n }}>\n {/* Header */}\n <div style={{\n padding: '20px 24px',\n borderBottom: '1px solid #e2e8f0',\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)'\n }}>\n <div>\n <h2 style={{ margin: 0, fontSize: '18px', fontWeight: '700', color: '#0f172a' }}>\n {orderProgress.order.id}\n </h2>\n <p style={{ margin: '4px 0 0', fontSize: '14px', color: '#64748b' }}>\n {orderProgress.order.client_name}\n </p>\n </div>\n <button\n onClick={() => setSelectedOrder(null)}\n style={{\n background: 'white',\n border: '1px solid #e2e8f0',\n borderRadius: '8px',\n padding: '8px',\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center'\n }}\n >\n <svg width=\"16\" height=\"16\" fill=\"none\" stroke=\"#64748b\" strokeWidth=\"2\" viewBox=\"0 0 24 24\">\n <path d=\"M6 18L18 6M6 6l12 12\" strokeLinecap=\"round\"/>\n </svg>\n </button>\n </div>\n\n {/* Progress Circle */}\n <div style={{ padding: '24px', textAlign: 'center', borderBottom: '1px solid #e2e8f0' }}>\n <div style={{ position: 'relative', width: '120px', height: '120px', margin: '0 auto' }}>\n <svg width=\"120\" height=\"120\" style={{ transform: 'rotate(-90deg)' }}>\n <circle cx=\"60\" cy=\"60\" r=\"52\" fill=\"none\" stroke=\"#e2e8f0\" strokeWidth=\"12\"/>\n <circle\n cx=\"60\" cy=\"60\" r=\"52\" fill=\"none\"\n stroke={orderProgress.progress.percent === 100 ? '#22c55e' : '#3b82f6'}\n strokeWidth=\"12\"\n strokeDasharray={`${(orderProgress.progress.percent / 100) * 327} 327`}\n strokeLinecap=\"round\"\n style={{ transition: 'stroke-dasharray 0.5s ease' }}\n />\n </svg>\n <div style={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n textAlign: 'center'\n }}>\n <div style={{ fontSize: '28px', fontWeight: '700', color: '#0f172a' }}>\n {orderProgress.progress.percent}%\n </div>\n <div style={{ fontSize: '12px', color: '#64748b' }}>\n {orderProgress.progress.completed_steps}/7 steps\n </div>\n </div>\n </div>\n </div>\n\n {/* Steps List */}\n <div style={{ padding: '24px' }}>\n <h3 style={{\n margin: '0 0 20px',\n fontSize: '11px',\n fontWeight: '600',\n color: '#94a3b8',\n textTransform: 'uppercase',\n letterSpacing: '0.1em'\n }}>\n Steps\n </h3>\n <div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>\n {orderProgress.steps.map((step: any, index: number) => {\n const isCompleted = step.status === 'completed';\n const isInProgress = step.status === 'in_progress';\n const isPending = step.status === 'pending';\n const canStart = isPending && (index === 0 || orderProgress.steps[index - 1]?.status === 'completed');\n\n return (\n <div\n key={step.step_number}\n style={{\n padding: '16px 0',\n borderBottom: index < orderProgress.steps.length - 1 ? '1px solid #f1f5f9' : 'none'\n }}\n >\n <div style={{ display: 'flex', alignItems: 'flex-start', gap: '16px' }}>\n {/* Step Number */}\n <div style={{\n width: '24px',\n height: '24px',\n borderRadius: '50%',\n background: isCompleted ? '#0f172a' : isInProgress ? '#0f172a' : '#e2e8f0',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n marginTop: '2px'\n }}>\n {isCompleted ? (\n <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"white\" strokeWidth=\"2.5\" viewBox=\"0 0 24 24\">\n <path d=\"M5 13l4 4L19 7\" strokeLinecap=\"round\" strokeLinejoin=\"round\"/>\n </svg>\n ) : (\n <span style={{ fontSize: '11px', fontWeight: '600', color: isInProgress ? 'white' : '#94a3b8' }}>\n {step.step_number}\n </span>\n )}\n </div>\n\n {/* Step Content */}\n <div style={{ flex: 1, minWidth: 0 }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px' }}>\n <span style={{\n fontSize: '14px',\n fontWeight: isInProgress ? '600' : '500',\n color: isPending ? '#94a3b8' : '#0f172a'\n }}>\n {step.step_name}\n </span>\n\n {/* Action Button */}\n {canStart && (\n <button\n onClick={(e) => { e.stopPropagation(); openStartModal(step.step_number); }}\n style={{\n padding: '6px 12px',\n background: '#0f172a',\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n cursor: 'pointer',\n fontWeight: '500',\n fontSize: '12px'\n }}\n >\n Start\n </button>\n )}\n {isInProgress && (\n <button\n onClick={(e) => { e.stopPropagation(); openCompleteModal(step.step_number); }}\n style={{\n padding: '6px 12px',\n background: '#0f172a',\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n cursor: 'pointer',\n fontWeight: '500',\n fontSize: '12px'\n }}\n >\n Done\n </button>\n )}\n </div>\n\n {/* Meta info */}\n {(isCompleted || isInProgress) && (step.operator_id || step.actual_duration_min != null) && (\n <div style={{\n marginTop: '6px',\n fontSize: '12px',\n color: '#64748b',\n display: 'flex',\n gap: '12px'\n }}>\n {step.operator_id && (\n <span>{step.operator_id}</span>\n )}\n {isCompleted && step.actual_duration_min != null && (\n <span>\n {step.actual_duration_min} min\n {step.actual_duration_min > step.theoretical_duration_min && (\n <span style={{ color: '#f59e0b' }}> (+{step.actual_duration_min - step.theoretical_duration_min})</span>\n )}\n </span>\n )}\n </div>\n )}\n\n {/* Notes */}\n {isCompleted && step.notes && (\n <div style={{\n marginTop: '8px',\n fontSize: '12px',\n color: '#64748b',\n fontStyle: 'italic'\n }}>\n {step.notes}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Stats */}\n {orderProgress.progress.total_actual_min > 0 && (\n <div style={{\n padding: '20px 24px',\n borderTop: '1px solid #e2e8f0',\n background: '#f8fafc'\n }}>\n <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px', textAlign: 'center' }}>\n <div>\n <div style={{ fontSize: '11px', fontWeight: '600', color: '#64748b', textTransform: 'uppercase', letterSpacing: '0.05em' }}>\n Actual Time\n </div>\n <div style={{ fontSize: '20px', fontWeight: '700', color: '#0f172a', marginTop: '4px' }}>\n {orderProgress.progress.total_actual_min}<span style={{ fontSize: '12px', fontWeight: '500', color: '#64748b' }}>min</span>\n </div>\n </div>\n <div>\n <div style={{ fontSize: '11px', fontWeight: '600', color: '#64748b', textTransform: 'uppercase', letterSpacing: '0.05em' }}>\n Expected\n </div>\n <div style={{ fontSize: '20px', fontWeight: '700', color: '#0f172a', marginTop: '4px' }}>\n {orderProgress.progress.total_theoretical_min}<span style={{ fontSize: '12px', fontWeight: '500', color: '#64748b' }}>min</span>\n </div>\n </div>\n <div>\n <div style={{ fontSize: '11px', fontWeight: '600', color: '#64748b', textTransform: 'uppercase', letterSpacing: '0.05em' }}>\n Efficiency\n </div>\n <div style={{\n fontSize: '20px',\n fontWeight: '700',\n marginTop: '4px',\n color: orderProgress.progress.efficiency >= 100 ? '#22c55e' : orderProgress.progress.efficiency >= 80 ? '#f59e0b' : '#ef4444'\n }}>\n {orderProgress.progress.efficiency || '—'}<span style={{ fontSize: '12px', fontWeight: '500' }}>%</span>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n\n {/* Start Step Modal */}\n {showStartModal && (\n <div style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0, 0, 0, 0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 1000,\n backdropFilter: 'blur(4px)'\n }}>\n <div style={{\n background: 'white',\n borderRadius: '20px',\n padding: '32px',\n width: '100%',\n maxWidth: '400px',\n boxShadow: '0 25px 50px rgba(0, 0, 0, 0.25)',\n animation: 'modalIn 0.2s ease-out'\n }}>\n <div style={{ textAlign: 'center', marginBottom: '24px' }}>\n <div style={{\n width: '56px',\n height: '56px',\n background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',\n borderRadius: '16px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n margin: '0 auto 16px'\n }}>\n <svg width=\"28\" height=\"28\" fill=\"none\" stroke=\"white\" strokeWidth=\"2\" viewBox=\"0 0 24 24\">\n <path d=\"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z\" strokeLinecap=\"round\" strokeLinejoin=\"round\"/>\n <path d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" strokeLinecap=\"round\" strokeLinejoin=\"round\"/>\n </svg>\n </div>\n <h3 style={{ margin: 0, fontSize: '20px', fontWeight: '700', color: '#0f172a' }}>\n Start Step {modalStepNumber}\n </h3>\n <p style={{ margin: '8px 0 0', fontSize: '14px', color: '#64748b' }}>\n Enter operator ID to begin this step\n </p>\n </div>\n\n <div style={{ marginBottom: '24px' }}>\n <label style={{\n display: 'block',\n fontSize: '12px',\n fontWeight: '600',\n color: '#475569',\n marginBottom: '8px',\n textTransform: 'uppercase',\n letterSpacing: '0.05em'\n }}>\n Operator ID *\n </label>\n <input\n type=\"text\"\n value={operatorId}\n onChange={(e) => setOperatorId(e.target.value)}\n placeholder=\"Enter your ID\"\n autoFocus\n style={{\n width: '100%',\n padding: '14px 16px',\n borderRadius: '12px',\n border: '2px solid #e2e8f0',\n fontSize: '16px',\n outline: 'none',\n boxSizing: 'border-box',\n transition: 'border-color 0.15s ease'\n }}\n onKeyDown={(e) => e.key === 'Enter' && operatorId.trim() && handleStartStep()}\n />\n </div>\n\n <div style={{ display: 'flex', gap: '12px' }}>\n <button\n onClick={() => setShowStartModal(false)}\n style={{\n flex: 1,\n padding: '14px 24px',\n background: '#f1f5f9',\n color: '#475569',\n border: 'none',\n borderRadius: '12px',\n cursor: 'pointer',\n fontWeight: '600',\n fontSize: '14px'\n }}\n >\n Cancel\n </button>\n <button\n onClick={handleStartStep}\n disabled={!operatorId.trim()}\n style={{\n flex: 1,\n padding: '14px 24px',\n background: operatorId.trim() ? 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)' : '#e2e8f0',\n color: operatorId.trim() ? 'white' : '#94a3b8',\n border: 'none',\n borderRadius: '12px',\n cursor: operatorId.trim() ? 'pointer' : 'not-allowed',\n fontWeight: '600',\n fontSize: '14px',\n boxShadow: operatorId.trim() ? '0 4px 12px rgba(59, 130, 246, 0.35)' : 'none'\n }}\n >\n Start Step\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Complete Step Modal */}\n {showCompleteModal && (\n <div style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0, 0, 0, 0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 1000,\n backdropFilter: 'blur(4px)'\n }}>\n <div style={{\n background: 'white',\n borderRadius: '20px',\n padding: '32px',\n width: '100%',\n maxWidth: '400px',\n boxShadow: '0 25px 50px rgba(0, 0, 0, 0.25)',\n animation: 'modalIn 0.2s ease-out'\n }}>\n <div style={{ textAlign: 'center', marginBottom: '24px' }}>\n <div style={{\n width: '56px',\n height: '56px',\n background: 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)',\n borderRadius: '16px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n margin: '0 auto 16px'\n }}>\n <svg width=\"28\" height=\"28\" fill=\"none\" stroke=\"white\" strokeWidth=\"2.5\" viewBox=\"0 0 24 24\">\n <path d=\"M5 13l4 4L19 7\" strokeLinecap=\"round\" strokeLinejoin=\"round\"/>\n </svg>\n </div>\n <h3 style={{ margin: 0, fontSize: '20px', fontWeight: '700', color: '#0f172a' }}>\n Complete Step {modalStepNumber}\n </h3>\n <p style={{ margin: '8px 0 0', fontSize: '14px', color: '#64748b' }}>\n Add any notes before completing\n </p>\n </div>\n\n <div style={{ marginBottom: '24px' }}>\n <label style={{\n display: 'block',\n fontSize: '12px',\n fontWeight: '600',\n color: '#475569',\n marginBottom: '8px',\n textTransform: 'uppercase',\n letterSpacing: '0.05em'\n }}>\n Notes (optional)\n </label>\n <textarea\n value={completionNotes}\n onChange={(e) => setCompletionNotes(e.target.value)}\n placeholder=\"Any issues, delays, or comments...\"\n rows={3}\n autoFocus\n style={{\n width: '100%',\n padding: '14px 16px',\n borderRadius: '12px',\n border: '2px solid #e2e8f0',\n fontSize: '14px',\n outline: 'none',\n boxSizing: 'border-box',\n resize: 'none',\n fontFamily: 'inherit',\n transition: 'border-color 0.15s ease'\n }}\n />\n </div>\n\n <div style={{ display: 'flex', gap: '12px' }}>\n <button\n onClick={() => setShowCompleteModal(false)}\n style={{\n flex: 1,\n padding: '14px 24px',\n background: '#f1f5f9',\n color: '#475569',\n border: 'none',\n borderRadius: '12px',\n cursor: 'pointer',\n fontWeight: '600',\n fontSize: '14px'\n }}\n >\n Cancel\n </button>\n <button\n onClick={handleCompleteStep}\n style={{\n flex: 1,\n padding: '14px 24px',\n background: 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)',\n color: 'white',\n border: 'none',\n borderRadius: '12px',\n cursor: 'pointer',\n fontWeight: '600',\n fontSize: '14px',\n boxShadow: '0 4px 12px rgba(34, 197, 94, 0.35)'\n }}\n >\n Complete Step\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* CSS Animations */}\n <style>{`\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n @keyframes modalIn {\n from {\n opacity: 0;\n transform: scale(0.95) translateY(-10px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n }\n input:focus, select:focus, textarea:focus {\n border-color: #3b82f6 !important;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15) !important;\n }\n button:hover {\n transform: translateY(-1px);\n }\n button:active {\n transform: translateY(0);\n }\n `}</style>\n </div>\n );\n};\n\nexport default App;\n", |
| 72 | "/index.css": ".myclass {\n border: 1px solid gray;\n padding: 2px;\n}\n", |
| 73 | "/index.tsx": "import React from 'react'\n\nimport { createRoot } from 'react-dom/client'\nimport App from './App'\n\nconst root = createRoot(document.getElementById('root')!);\nroot.render(<App/>);\n", |
| 74 | "/package.json": "{\n \"dependencies\": {\n \"react\": \"19.0.0\",\n \"react-dom\": \"19.0.0\",\n \"@orvanta/client\": \"^1\"\n },\n \"devDependencies\": {\n \"@types/react-dom\": \"^19.0.0\",\n \"@types/react\": \"^19.0.0\"\n }\n}\n" |
| 75 | }, |
| 76 | "data": { |
| 77 | "schema": "assemblyline", |
| 78 | "tables": [ |
| 79 | "main/assemblyline:steps", |
| 80 | "main/assemblyline:orders", |
| 81 | "main/assemblyline:step_logs" |
| 82 | ], |
| 83 | "datatable": "main" |
| 84 | } |
| 85 | } |
| 86 | } |