feat: initial commit

This commit is contained in:
2026-03-05 22:12:38 +01:00
commit bce762a783
380 changed files with 51955 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
import { useState, useEffect, useRef } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Terminal, ChevronDown, ChevronUp } from 'lucide-react'
import { getRenderLog } from '../api/worker'
import type { RenderLogEntry } from '../api/worker'
const LEVEL_COLORS: Record<string, string> = {
info: 'text-gray-300',
error: 'text-red-400',
success: 'text-green-400',
warn: 'text-yellow-400',
}
/**
* Live render log panel — polls Redis-backed log entries every 2s.
* Shows a compact terminal-style output for a render job.
*
* Always does an initial fetch to check if entries exist (so failed jobs
* still show their log). Polls only when isActive.
*/
export default function LiveRenderLog({
orderLineId,
isActive,
compact = false,
}: {
orderLineId: string
/** Whether the render is still processing — enables polling */
isActive: boolean
/** Compact mode (inline, no border) for table rows */
compact?: boolean
}) {
const [expanded, setExpanded] = useState(isActive)
const scrollRef = useRef<HTMLDivElement>(null)
// Always fetch once to probe for existing entries; poll only when active
const { data } = useQuery({
queryKey: ['render-log', orderLineId],
queryFn: () => getRenderLog(orderLineId),
refetchInterval: isActive ? 2000 : false,
})
const entries: RenderLogEntry[] = data?.entries ?? []
const hasEntries = entries.length > 0
// Auto-scroll to bottom when new entries arrive
useEffect(() => {
if (scrollRef.current && isActive) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight
}
}, [entries.length, isActive])
// Auto-expand when active
useEffect(() => {
if (isActive) setExpanded(true)
}, [isActive])
// Nothing to show at all
if (!hasEntries && !isActive) return null
if (compact) {
return (
<div className="mt-1">
<button
onClick={() => setExpanded((v) => !v)}
className="text-[10px] text-gray-400 hover:text-gray-600 flex items-center gap-1"
>
<Terminal size={10} />
Log ({entries.length})
{expanded ? <ChevronUp size={10} /> : <ChevronDown size={10} />}
</button>
{expanded && hasEntries && (
<LogPanel entries={entries} isActive={isActive} scrollRef={scrollRef} maxHeight="120px" />
)}
</div>
)
}
return (
<div className="mt-2">
<button
onClick={() => setExpanded((v) => !v)}
className="flex items-center gap-1.5 text-xs text-gray-500 hover:text-gray-700 mb-1"
>
<Terminal size={12} />
<span className="font-medium">Render Log</span>
<span className="text-gray-400">({entries.length} entries)</span>
{expanded ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
</button>
{expanded && (
<LogPanel entries={entries} isActive={isActive} scrollRef={scrollRef} maxHeight="200px" />
)}
</div>
)
}
function LogPanel({
entries,
isActive,
scrollRef,
maxHeight,
}: {
entries: RenderLogEntry[]
isActive: boolean
scrollRef: React.RefObject<HTMLDivElement | null>
maxHeight: string
}) {
return (
<div
ref={scrollRef}
className="bg-gray-900 rounded-md p-2 overflow-y-auto font-mono text-[11px] leading-relaxed"
style={{ maxHeight }}
>
{entries.map((entry, i) => (
<div key={i} className={`flex gap-2 ${LEVEL_COLORS[entry.level] || LEVEL_COLORS.info}`}>
<span className="text-gray-500 shrink-0 select-none">{entry.t}</span>
<span>{entry.msg}</span>
</div>
))}
{isActive && entries.length > 0 && (
<div className="text-gray-500 animate-pulse">...</div>
)}
{entries.length === 0 && (
<div className="text-gray-600 italic">No log entries yet</div>
)}
</div>
)
}