summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/orders
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/components/orders')
-rw-r--r--makima/frontend/src/components/orders/OrderDetail.tsx530
-rw-r--r--makima/frontend/src/components/orders/OrderList.tsx188
2 files changed, 718 insertions, 0 deletions
diff --git a/makima/frontend/src/components/orders/OrderDetail.tsx b/makima/frontend/src/components/orders/OrderDetail.tsx
new file mode 100644
index 0000000..7f8a95d
--- /dev/null
+++ b/makima/frontend/src/components/orders/OrderDetail.tsx
@@ -0,0 +1,530 @@
+import { useState } from "react";
+import type {
+ Order,
+ OrderStatus,
+ OrderPriority,
+ OrderType,
+ UpdateOrderRequest,
+ DirectiveSummary,
+} from "../../lib/api";
+
+const STATUS_BADGE: Record<OrderStatus, { color: string; label: string }> = {
+ open: { color: "text-[#75aafc] border-[rgba(117,170,252,0.4)]", label: "OPEN" },
+ in_progress: { color: "text-yellow-400 border-yellow-800", label: "IN PROGRESS" },
+ done: { color: "text-emerald-400 border-emerald-800", label: "DONE" },
+ archived: { color: "text-[#556677] border-[#2a3a5a]", label: "ARCHIVED" },
+};
+
+const PRIORITY_OPTIONS: { value: OrderPriority; color: string; label: string }[] = [
+ { value: "critical", color: "text-red-400 border-red-800", label: "Critical" },
+ { value: "high", color: "text-orange-400 border-orange-800", label: "High" },
+ { value: "medium", color: "text-yellow-400 border-yellow-800", label: "Medium" },
+ { value: "low", color: "text-[#75aafc] border-[rgba(117,170,252,0.4)]", label: "Low" },
+ { value: "none", color: "text-[#556677] border-[#2a3a5a]", label: "None" },
+];
+
+const TYPE_OPTIONS: { value: OrderType; color: string; label: string }[] = [
+ { value: "feature", color: "text-[#75aafc]", label: "Feature" },
+ { value: "bug", color: "text-red-400", label: "Bug" },
+ { value: "spike", color: "text-yellow-400", label: "Spike" },
+ { value: "chore", color: "text-[#7788aa]", label: "Chore" },
+ { value: "improvement", color: "text-emerald-400", label: "Improvement" },
+];
+
+const STATUS_OPTIONS: OrderStatus[] = ["open", "in_progress", "done", "archived"];
+
+interface OrderDetailProps {
+ order: Order;
+ directives: DirectiveSummary[];
+ onUpdate: (req: UpdateOrderRequest) => Promise<void>;
+ onDelete: () => void;
+ onLinkDirective: (directiveId: string) => Promise<void>;
+ onLinkContract: (contractId: string) => Promise<void>;
+ onConvertToStep: (directiveId: string) => Promise<void>;
+ onRefresh: () => void;
+}
+
+export function OrderDetail({
+ order,
+ directives,
+ onUpdate,
+ onDelete,
+ onLinkDirective,
+ onLinkContract,
+ onConvertToStep,
+ onRefresh,
+}: OrderDetailProps) {
+ const [editingTitle, setEditingTitle] = useState(false);
+ const [titleText, setTitleText] = useState(order.title);
+ const [editingDesc, setEditingDesc] = useState(false);
+ const [descText, setDescText] = useState(order.description || "");
+ const [editingLabels, setEditingLabels] = useState(false);
+ const [labelsText, setLabelsText] = useState(order.labels.join(", "));
+ const [showLinkDirective, setShowLinkDirective] = useState(false);
+ const [showLinkContract, setShowLinkContract] = useState(false);
+ const [contractIdInput, setContractIdInput] = useState("");
+ const [showConvertToStep, setShowConvertToStep] = useState(false);
+
+ const badge = STATUS_BADGE[order.status] || STATUS_BADGE.open;
+ const currentPriority = PRIORITY_OPTIONS.find((p) => p.value === order.priority) || PRIORITY_OPTIONS[4];
+ const currentType = TYPE_OPTIONS.find((t) => t.value === order.orderType) || TYPE_OPTIONS[0];
+
+ const handleTitleSave = async () => {
+ if (titleText.trim() && titleText !== order.title) {
+ await onUpdate({ title: titleText.trim() });
+ }
+ setEditingTitle(false);
+ };
+
+ const handleDescSave = async () => {
+ const newDesc = descText.trim() || null;
+ if (newDesc !== order.description) {
+ await onUpdate({ description: newDesc });
+ }
+ setEditingDesc(false);
+ };
+
+ const handleLabelsSave = async () => {
+ const newLabels = labelsText
+ .split(",")
+ .map((l) => l.trim())
+ .filter((l) => l.length > 0);
+ await onUpdate({ labels: newLabels });
+ setEditingLabels(false);
+ };
+
+ const handleStatusChange = async (status: OrderStatus) => {
+ await onUpdate({ status });
+ };
+
+ const handlePriorityChange = async (priority: OrderPriority) => {
+ await onUpdate({ priority });
+ };
+
+ const handleTypeChange = async (orderType: OrderType) => {
+ await onUpdate({ orderType });
+ };
+
+ const handleLinkDirective = async (directiveId: string) => {
+ await onLinkDirective(directiveId);
+ setShowLinkDirective(false);
+ };
+
+ const handleLinkContract = async () => {
+ if (!contractIdInput.trim()) return;
+ await onLinkContract(contractIdInput.trim());
+ setContractIdInput("");
+ setShowLinkContract(false);
+ };
+
+ const handleConvertToStep = async (directiveId: string) => {
+ await onConvertToStep(directiveId);
+ setShowConvertToStep(false);
+ };
+
+ return (
+ <div className="flex flex-col h-full overflow-y-auto">
+ {/* Header */}
+ <div className="px-4 py-3 border-b border-dashed border-[rgba(117,170,252,0.2)]">
+ <div className="flex items-center justify-between mb-2">
+ {editingTitle ? (
+ <div className="flex-1 flex items-center gap-2 pr-2">
+ <input
+ value={titleText}
+ onChange={(e) => setTitleText(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") handleTitleSave();
+ if (e.key === "Escape") setEditingTitle(false);
+ }}
+ autoFocus
+ className="flex-1 bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1 text-[14px] font-mono text-white"
+ />
+ <button
+ type="button"
+ onClick={handleTitleSave}
+ className="text-[10px] font-mono text-emerald-400 hover:text-emerald-300"
+ >
+ [save]
+ </button>
+ <button
+ type="button"
+ onClick={() => setEditingTitle(false)}
+ className="text-[10px] font-mono text-[#556677] hover:text-white"
+ >
+ [cancel]
+ </button>
+ </div>
+ ) : (
+ <h2
+ className="text-[14px] font-mono text-white font-medium truncate pr-2 cursor-pointer hover:text-[#9bc3ff]"
+ onClick={() => {
+ setTitleText(order.title);
+ setEditingTitle(true);
+ }}
+ >
+ {order.title}
+ </h2>
+ )}
+ <div className="flex items-center gap-2 shrink-0">
+ <span
+ className={`text-[10px] font-mono ${badge.color} border rounded px-2 py-0.5`}
+ >
+ {badge.label}
+ </span>
+ <button
+ type="button"
+ onClick={onRefresh}
+ className="text-[10px] font-mono text-[#7788aa] hover:text-white"
+ title="Refresh"
+ >
+ [refresh]
+ </button>
+ </div>
+ </div>
+
+ {/* Type + Priority inline */}
+ <div className="flex items-center gap-3 mb-2">
+ <span className={`text-[10px] font-mono ${currentType.color}`}>
+ {currentType.label}
+ </span>
+ <span className="text-[10px] font-mono text-[#2a3a5a]">/</span>
+ <span className={`text-[10px] font-mono ${currentPriority.color} border rounded px-1.5 py-0.5`}>
+ {currentPriority.label}
+ </span>
+ </div>
+
+ {/* Linked entities */}
+ {order.directiveId && (
+ <div className="text-[10px] font-mono text-[#556677] mb-1 truncate">
+ Directive: <a href={`/directives/${order.directiveId}`} className="text-[#75aafc] hover:text-white underline">{order.directiveId.slice(0, 8)}...</a>
+ </div>
+ )}
+ {order.contractId && (
+ <div className="text-[10px] font-mono text-[#556677] mb-1 truncate">
+ Contract: <a href={`/contracts/${order.contractId}`} className="text-[#75aafc] hover:text-white underline">{order.contractId.slice(0, 8)}...</a>
+ </div>
+ )}
+ {order.directiveStepId && (
+ <div className="text-[10px] font-mono text-[#556677] mb-1 truncate">
+ Step: <span className="text-[#7788aa]">{order.directiveStepId.slice(0, 8)}...</span>
+ </div>
+ )}
+
+ {/* Controls */}
+ <div className="flex flex-wrap gap-2 mt-2">
+ <button
+ type="button"
+ onClick={onDelete}
+ className="text-[10px] font-mono text-red-400 hover:text-red-300 border border-red-800 rounded px-2 py-1 ml-auto"
+ >
+ Delete
+ </button>
+ </div>
+ </div>
+
+ {/* Status selector */}
+ <div className="px-4 py-3 border-b border-[rgba(117,170,252,0.1)]">
+ <div className="flex items-center justify-between mb-1.5">
+ <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide">
+ Status
+ </span>
+ </div>
+ <div className="flex gap-1.5 flex-wrap">
+ {STATUS_OPTIONS.map((s) => {
+ const sBadge = STATUS_BADGE[s];
+ return (
+ <button
+ key={s}
+ type="button"
+ onClick={() => handleStatusChange(s)}
+ className={`text-[10px] font-mono border rounded px-2 py-0.5 transition-colors ${
+ s === order.status
+ ? `${sBadge.color} bg-[rgba(117,170,252,0.1)]`
+ : "text-[#556677] border-[#2a3a5a] hover:text-[#7788aa]"
+ }`}
+ >
+ {sBadge.label}
+ </button>
+ );
+ })}
+ </div>
+ </div>
+
+ {/* Priority selector */}
+ <div className="px-4 py-3 border-b border-[rgba(117,170,252,0.1)]">
+ <div className="flex items-center justify-between mb-1.5">
+ <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide">
+ Priority
+ </span>
+ </div>
+ <div className="flex gap-1.5 flex-wrap">
+ {PRIORITY_OPTIONS.map((p) => (
+ <button
+ key={p.value}
+ type="button"
+ onClick={() => handlePriorityChange(p.value)}
+ className={`text-[10px] font-mono border rounded px-2 py-0.5 transition-colors ${
+ p.value === order.priority
+ ? `${p.color} bg-[rgba(117,170,252,0.1)]`
+ : "text-[#556677] border-[#2a3a5a] hover:text-[#7788aa]"
+ }`}
+ >
+ {p.label}
+ </button>
+ ))}
+ </div>
+ </div>
+
+ {/* Type selector */}
+ <div className="px-4 py-3 border-b border-[rgba(117,170,252,0.1)]">
+ <div className="flex items-center justify-between mb-1.5">
+ <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide">
+ Type
+ </span>
+ </div>
+ <div className="flex gap-1.5 flex-wrap">
+ {TYPE_OPTIONS.map((t) => (
+ <button
+ key={t.value}
+ type="button"
+ onClick={() => handleTypeChange(t.value)}
+ className={`text-[10px] font-mono border rounded px-2 py-0.5 transition-colors ${
+ t.value === order.orderType
+ ? `${t.color} border-current bg-[rgba(117,170,252,0.1)]`
+ : "text-[#556677] border-[#2a3a5a] hover:text-[#7788aa]"
+ }`}
+ >
+ {t.label}
+ </button>
+ ))}
+ </div>
+ </div>
+
+ {/* Description */}
+ <div className="px-4 py-3 border-b border-[rgba(117,170,252,0.1)]">
+ <div className="flex items-center justify-between mb-1">
+ <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide">
+ Description
+ </span>
+ {!editingDesc && (
+ <button
+ type="button"
+ onClick={() => {
+ setDescText(order.description || "");
+ setEditingDesc(true);
+ }}
+ className="text-[9px] font-mono text-[#556677] hover:text-[#75aafc]"
+ >
+ [edit]
+ </button>
+ )}
+ </div>
+ {editingDesc ? (
+ <div className="flex flex-col gap-1.5">
+ <textarea
+ value={descText}
+ onChange={(e) => setDescText(e.target.value)}
+ className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[11px] font-mono text-white resize-y min-h-[80px]"
+ rows={4}
+ autoFocus
+ />
+ <div className="flex gap-1.5">
+ <button
+ type="button"
+ onClick={handleDescSave}
+ className="text-[10px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-2 py-0.5"
+ >
+ Save
+ </button>
+ <button
+ type="button"
+ onClick={() => setEditingDesc(false)}
+ className="text-[10px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-2 py-0.5"
+ >
+ Cancel
+ </button>
+ </div>
+ </div>
+ ) : (
+ <p className="text-[11px] font-mono text-[#c0d0e0] whitespace-pre-wrap">
+ {order.description || <span className="text-[#556677] italic">No description</span>}
+ </p>
+ )}
+ </div>
+
+ {/* Labels */}
+ <div className="px-4 py-3 border-b border-[rgba(117,170,252,0.1)]">
+ <div className="flex items-center justify-between mb-1">
+ <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide">
+ Labels
+ </span>
+ {!editingLabels && (
+ <button
+ type="button"
+ onClick={() => {
+ setLabelsText(order.labels.join(", "));
+ setEditingLabels(true);
+ }}
+ className="text-[9px] font-mono text-[#556677] hover:text-[#75aafc]"
+ >
+ [edit]
+ </button>
+ )}
+ </div>
+ {editingLabels ? (
+ <div className="flex flex-col gap-1.5">
+ <input
+ value={labelsText}
+ onChange={(e) => setLabelsText(e.target.value)}
+ placeholder="label1, label2, ..."
+ className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[11px] font-mono text-white"
+ autoFocus
+ />
+ <div className="flex gap-1.5">
+ <button
+ type="button"
+ onClick={handleLabelsSave}
+ className="text-[10px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-2 py-0.5"
+ >
+ Save
+ </button>
+ <button
+ type="button"
+ onClick={() => setEditingLabels(false)}
+ className="text-[10px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-2 py-0.5"
+ >
+ Cancel
+ </button>
+ </div>
+ </div>
+ ) : (
+ <div className="flex gap-1 flex-wrap">
+ {order.labels.length > 0 ? (
+ order.labels.map((l) => (
+ <span
+ key={l}
+ className="text-[10px] font-mono text-[#9bc3ff] bg-[rgba(117,170,252,0.1)] border border-[rgba(117,170,252,0.2)] rounded px-1.5 py-0.5"
+ >
+ {l}
+ </span>
+ ))
+ ) : (
+ <span className="text-[10px] font-mono text-[#556677] italic">No labels</span>
+ )}
+ </div>
+ )}
+ </div>
+
+ {/* Actions */}
+ <div className="px-4 py-3 flex-1">
+ <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-2">
+ Actions
+ </span>
+
+ <div className="flex flex-col gap-2">
+ {/* Link to Directive */}
+ <div>
+ <button
+ type="button"
+ onClick={() => setShowLinkDirective(!showLinkDirective)}
+ className="text-[10px] font-mono text-[#75aafc] hover:text-white border border-[rgba(117,170,252,0.3)] rounded px-2 py-1 w-full text-left"
+ >
+ Link to Directive
+ </button>
+ {showLinkDirective && (
+ <div className="mt-1 border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto rounded">
+ {directives.length === 0 ? (
+ <div className="px-3 py-2 text-[10px] font-mono text-[#556677]">
+ No directives available
+ </div>
+ ) : (
+ directives.map((d) => (
+ <button
+ key={d.id}
+ type="button"
+ onClick={() => handleLinkDirective(d.id)}
+ className="w-full text-left px-3 py-1.5 text-[10px] font-mono text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0"
+ >
+ {d.title}
+ </button>
+ ))
+ )}
+ </div>
+ )}
+ </div>
+
+ {/* Link to Contract */}
+ <div>
+ <button
+ type="button"
+ onClick={() => setShowLinkContract(!showLinkContract)}
+ className="text-[10px] font-mono text-[#75aafc] hover:text-white border border-[rgba(117,170,252,0.3)] rounded px-2 py-1 w-full text-left"
+ >
+ Link to Contract
+ </button>
+ {showLinkContract && (
+ <div className="mt-1 flex gap-1.5">
+ <input
+ value={contractIdInput}
+ onChange={(e) => setContractIdInput(e.target.value)}
+ placeholder="Contract ID..."
+ className="flex-1 bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1 text-[10px] font-mono text-white"
+ autoFocus
+ />
+ <button
+ type="button"
+ onClick={handleLinkContract}
+ disabled={!contractIdInput.trim()}
+ className="text-[10px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-2 py-1 disabled:opacity-50"
+ >
+ Link
+ </button>
+ </div>
+ )}
+ </div>
+
+ {/* Convert to Directive Step */}
+ {!order.directiveStepId && (
+ <div>
+ <button
+ type="button"
+ onClick={() => setShowConvertToStep(!showConvertToStep)}
+ className="text-[10px] font-mono text-yellow-400 hover:text-yellow-300 border border-yellow-800 rounded px-2 py-1 w-full text-left"
+ >
+ Convert to Directive Step
+ </button>
+ {showConvertToStep && (
+ <div className="mt-1 border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto rounded">
+ {directives.length === 0 ? (
+ <div className="px-3 py-2 text-[10px] font-mono text-[#556677]">
+ No directives available
+ </div>
+ ) : (
+ directives.map((d) => (
+ <button
+ key={d.id}
+ type="button"
+ onClick={() => handleConvertToStep(d.id)}
+ className="w-full text-left px-3 py-1.5 text-[10px] font-mono text-yellow-400 hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0"
+ >
+ {d.title}
+ </button>
+ ))
+ )}
+ </div>
+ )}
+ </div>
+ )}
+ </div>
+ </div>
+
+ {/* Metadata */}
+ <div className="px-4 py-2 border-t border-[rgba(117,170,252,0.1)]">
+ <div className="flex items-center justify-between text-[9px] font-mono text-[#556677]">
+ <span>Created {new Date(order.createdAt).toLocaleDateString()}</span>
+ <span>Updated {new Date(order.updatedAt).toLocaleDateString()}</span>
+ </div>
+ </div>
+ </div>
+ );
+}
diff --git a/makima/frontend/src/components/orders/OrderList.tsx b/makima/frontend/src/components/orders/OrderList.tsx
new file mode 100644
index 0000000..76ac7a7
--- /dev/null
+++ b/makima/frontend/src/components/orders/OrderList.tsx
@@ -0,0 +1,188 @@
+import { useState, useMemo } from "react";
+import type { Order, OrderStatus, OrderPriority, OrderType } from "../../lib/api";
+
+const STATUS_BADGE: Record<OrderStatus, { color: string; label: string }> = {
+ open: { color: "text-[#75aafc] border-[rgba(117,170,252,0.4)]", label: "OPEN" },
+ in_progress: { color: "text-yellow-400 border-yellow-800", label: "IN PROGRESS" },
+ done: { color: "text-emerald-400 border-emerald-800", label: "DONE" },
+ archived: { color: "text-[#556677] border-[#2a3a5a]", label: "ARCHIVED" },
+};
+
+const PRIORITY_COLOR: Record<OrderPriority, string> = {
+ critical: "bg-red-400",
+ high: "bg-orange-400",
+ medium: "bg-yellow-400",
+ low: "bg-[#75aafc]",
+ none: "bg-[#556677]",
+};
+
+const TYPE_BADGE: Record<OrderType, { color: string; label: string }> = {
+ feature: { color: "text-[#75aafc] border-[rgba(117,170,252,0.3)]", label: "FEAT" },
+ bug: { color: "text-red-400 border-red-800", label: "BUG" },
+ spike: { color: "text-yellow-400 border-yellow-800", label: "SPIKE" },
+ chore: { color: "text-[#7788aa] border-[#2a3a5a]", label: "CHORE" },
+ improvement: { color: "text-emerald-400 border-emerald-800", label: "IMPROVE" },
+};
+
+interface OrderListProps {
+ orders: Order[];
+ selectedId: string | null;
+ onSelect: (id: string) => void;
+ onCreate: () => void;
+ statusFilter: OrderStatus | undefined;
+ onStatusFilter: (s: OrderStatus | undefined) => void;
+ typeFilter: OrderType | undefined;
+ onTypeFilter: (t: OrderType | undefined) => void;
+}
+
+const STATUS_OPTIONS: (OrderStatus | "all")[] = ["all", "open", "in_progress", "done", "archived"];
+const TYPE_OPTIONS: (OrderType | "all")[] = ["all", "feature", "bug", "spike", "chore", "improvement"];
+
+export function OrderList({
+ orders,
+ selectedId,
+ onSelect,
+ onCreate,
+ statusFilter,
+ onStatusFilter,
+ typeFilter,
+ onTypeFilter,
+}: OrderListProps) {
+ const [search, setSearch] = useState("");
+
+ const filtered = useMemo(() => {
+ if (!search.trim()) return orders;
+ const q = search.toLowerCase();
+ return orders.filter(
+ (o) =>
+ o.title.toLowerCase().includes(q) ||
+ (o.description && o.description.toLowerCase().includes(q)) ||
+ o.labels.some((l) => l.toLowerCase().includes(q)),
+ );
+ }, [orders, search]);
+
+ return (
+ <div className="flex flex-col h-full">
+ {/* Header */}
+ <div className="flex items-center justify-between px-3 py-2 border-b border-dashed border-[rgba(117,170,252,0.2)]">
+ <span className="text-[11px] font-mono text-[#9bc3ff] uppercase tracking-wide">
+ Orders
+ </span>
+ <button
+ type="button"
+ onClick={onCreate}
+ className="text-[11px] font-mono text-[#75aafc] hover:text-white bg-transparent border border-[rgba(117,170,252,0.3)] rounded px-2 py-0.5 hover:border-[rgba(117,170,252,0.6)] transition-colors"
+ >
+ + New
+ </button>
+ </div>
+
+ {/* Search */}
+ <div className="px-3 py-1.5 border-b border-[rgba(117,170,252,0.1)]">
+ <input
+ value={search}
+ onChange={(e) => setSearch(e.target.value)}
+ placeholder="Search orders..."
+ className="w-full bg-transparent border-none outline-none text-[11px] font-mono text-white placeholder:text-[#556677]"
+ />
+ </div>
+
+ {/* Filters */}
+ <div className="px-3 py-1.5 border-b border-[rgba(117,170,252,0.1)] flex flex-col gap-1">
+ <div className="flex items-center gap-1 flex-wrap">
+ <span className="text-[9px] font-mono text-[#556677] uppercase w-[38px] shrink-0">
+ Status
+ </span>
+ {STATUS_OPTIONS.map((s) => (
+ <button
+ key={s}
+ type="button"
+ onClick={() => onStatusFilter(s === "all" ? undefined : s)}
+ className={`text-[9px] font-mono px-1.5 py-0.5 rounded transition-colors ${
+ (s === "all" && !statusFilter) || s === statusFilter
+ ? "text-white bg-[rgba(117,170,252,0.15)] border border-[rgba(117,170,252,0.4)]"
+ : "text-[#556677] hover:text-[#7788aa] border border-transparent"
+ }`}
+ >
+ {s === "all" ? "ALL" : s === "in_progress" ? "WIP" : s.toUpperCase()}
+ </button>
+ ))}
+ </div>
+ <div className="flex items-center gap-1 flex-wrap">
+ <span className="text-[9px] font-mono text-[#556677] uppercase w-[38px] shrink-0">
+ Type
+ </span>
+ {TYPE_OPTIONS.map((t) => (
+ <button
+ key={t}
+ type="button"
+ onClick={() => onTypeFilter(t === "all" ? undefined : t)}
+ className={`text-[9px] font-mono px-1.5 py-0.5 rounded transition-colors ${
+ (t === "all" && !typeFilter) || t === typeFilter
+ ? "text-white bg-[rgba(117,170,252,0.15)] border border-[rgba(117,170,252,0.4)]"
+ : "text-[#556677] hover:text-[#7788aa] border border-transparent"
+ }`}
+ >
+ {t === "all" ? "ALL" : t.toUpperCase()}
+ </button>
+ ))}
+ </div>
+ </div>
+
+ {/* List */}
+ <div className="flex-1 overflow-y-auto">
+ {filtered.length === 0 ? (
+ <div className="px-3 py-6 text-center text-[#556677] font-mono text-[11px]">
+ No orders found
+ </div>
+ ) : (
+ filtered.map((o) => {
+ const statusBadge = STATUS_BADGE[o.status] || STATUS_BADGE.open;
+ const typeBadge = TYPE_BADGE[o.orderType] || TYPE_BADGE.feature;
+ const priorityColor = PRIORITY_COLOR[o.priority] || PRIORITY_COLOR.none;
+
+ return (
+ <button
+ key={o.id}
+ type="button"
+ onClick={() => onSelect(o.id)}
+ className={`w-full text-left px-3 py-2.5 border-b border-[rgba(117,170,252,0.1)] hover:bg-[rgba(117,170,252,0.05)] transition-colors ${
+ selectedId === o.id ? "bg-[rgba(117,170,252,0.1)]" : ""
+ }`}
+ >
+ <div className="flex items-start gap-2 mb-1">
+ {/* Priority dot */}
+ <span
+ className={`w-2 h-2 rounded-full ${priorityColor} shrink-0 mt-[3px]`}
+ title={o.priority}
+ />
+ <span className="text-[12px] font-mono text-white truncate flex-1">
+ {o.title}
+ </span>
+ </div>
+ <div className="flex items-center gap-1.5 pl-4">
+ <span
+ className={`text-[9px] font-mono ${statusBadge.color} border rounded px-1.5 py-0.5`}
+ >
+ {statusBadge.label}
+ </span>
+ <span
+ className={`text-[9px] font-mono ${typeBadge.color} border rounded px-1.5 py-0.5`}
+ >
+ {typeBadge.label}
+ </span>
+ {o.labels.length > 0 && (
+ <span className="text-[9px] font-mono text-[#556677] truncate">
+ {o.labels.slice(0, 2).join(", ")}
+ {o.labels.length > 2 && ` +${o.labels.length - 2}`}
+ </span>
+ )}
+ </div>
+ </button>
+ );
+ })
+ )}
+ </div>
+ </div>
+ );
+}