83 lines
3.0 KiB
TypeScript
83 lines
3.0 KiB
TypeScript
import type { QueueItem } from '../lib/api'
|
|
import { relativeTime, absoluteTime } from '../lib/time'
|
|
|
|
const statusColors: Record<string, string> = {
|
|
pending_verification: 'text-yellow-600',
|
|
ready_for_approval: 'text-blue-600 dark:text-blue-400',
|
|
returned: 'text-red-600',
|
|
approved: 'text-green-600',
|
|
redirected: 'text-orange-600',
|
|
}
|
|
|
|
function QueueItemCard({ item, onReview }: { item: QueueItem; onReview: () => void }) {
|
|
return (
|
|
<div className="group p-3 rounded-md border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-gray-400 dark:hover:border-gray-500 transition-colors">
|
|
<div className="flex items-start justify-between gap-3 mb-1">
|
|
<span className="font-medium text-sm text-gray-900 dark:text-gray-100 leading-snug">
|
|
{item.plan_title || `Plan #${item.plan_id}`}
|
|
</span>
|
|
<span className={`shrink-0 font-mono text-[10px] uppercase tracking-wider ${statusColors[item.status] ?? ''}`}>
|
|
{item.status.replace(/_/g, ' ')}
|
|
</span>
|
|
</div>
|
|
|
|
{item.task_title && (
|
|
<div className="font-mono text-[10px] text-gray-500 dark:text-gray-400 mb-1">
|
|
→ {item.task_title}
|
|
</div>
|
|
)}
|
|
|
|
{item.verification_notes && (
|
|
<div className="text-xs text-gray-600 dark:text-gray-400 mt-2 leading-relaxed line-clamp-2">
|
|
{item.verification_notes}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex items-center justify-between mt-3 pt-2 border-t border-gray-100 dark:border-gray-700/50">
|
|
<span className="font-mono text-[10px] tabular text-gray-400 dark:text-gray-500" title={absoluteTime(item.submitted_at)}>
|
|
submitted {relativeTime(item.submitted_at)}
|
|
</span>
|
|
{item.status === 'ready_for_approval' && (
|
|
<button
|
|
onClick={onReview}
|
|
className="font-mono text-[10px] uppercase tracking-wider px-3 py-1 rounded bg-blue-600 hover:bg-blue-700 text-white cursor-pointer"
|
|
>
|
|
review →
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function DirectorQueue({
|
|
items,
|
|
onReview,
|
|
}: {
|
|
items: QueueItem[]
|
|
onReview: (item: QueueItem) => void
|
|
}) {
|
|
return (
|
|
<div className="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-5">
|
|
<div className="flex items-baseline justify-between mb-4">
|
|
<h2 className="font-display text-xl font-medium text-gray-900 dark:text-gray-100">
|
|
Director Queue
|
|
</h2>
|
|
<span className="font-mono text-[10px] uppercase tracking-wider text-gray-400">
|
|
{items.length} {items.length === 1 ? 'item' : 'items'}
|
|
</span>
|
|
</div>
|
|
|
|
{items.length === 0 ? (
|
|
<p className="font-mono text-xs text-gray-500 italic">No items awaiting review</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{items.map((item) => (
|
|
<QueueItemCard key={item.id} item={item} onReview={() => onReview(item)} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|