summaryrefslogblamecommitdiff
path: root/makima/frontend/src/routes/orders.tsx
blob: 06e091a0a70d3cd87993804ab640d491cd381fb0 (plain) (tree)
1
2
3
4
5
6
7
8






                                                               
                                           










                                                                                                             
                                                                                                                            
                                         
                                                            





                                                                          
                                                                   








                                                                 
                                                                                         








                                                                        
                                                    





                                                 
                                    





                               
                            



























                                                                     

                                           



















                                                                      
                                                                                       
                          
                                                            
                          
                                                                                                                                        












                                                        
                                                        
                         
                                                                 





























                                                                                                                                                       














                                                                                                                                              

































                                                                                                                                                
                                                                  

















                                                                                                                                                             
                         


                                                   
















                                                                            
import { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router";
import { Masthead } from "../components/Masthead";
import { OrderList } from "../components/orders/OrderList";
import { OrderDetail } from "../components/orders/OrderDetail";
import { useOrders, useOrder } from "../hooks/useOrders";
import { useDirectives } from "../hooks/useDirectives";
import { useDogs } from "../hooks/useDogs";
import { useAuth } from "../contexts/AuthContext";
import type { OrderStatus, OrderType, OrderPriority } from "../lib/api";

export default function OrdersPage() {
  const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth();
  const navigate = useNavigate();
  const { id: selectedId } = useParams<{ id: string }>();

  const [statusFilter, setStatusFilter] = useState<OrderStatus | undefined>(undefined);
  const [typeFilter, setTypeFilter] = useState<OrderType | undefined>(undefined);
  const { orders, loading: listLoading, create, refresh: refreshList } = useOrders(statusFilter, typeFilter);
  const { order, refresh: refreshDetail, update, remove: removeOrder, linkDirective, convertToStep } = useOrder(selectedId);
  const { directives } = useDirectives();
  const { dogs } = useDogs(order?.directiveId ?? undefined);

  const [showCreate, setShowCreate] = useState(false);
  const [newTitle, setNewTitle] = useState("");
  const [newDesc, setNewDesc] = useState("");
  const [newPriority, setNewPriority] = useState<OrderPriority>("medium");
  const [newType, setNewType] = useState<OrderType>("feature");
  const [newDirectiveId, setNewDirectiveId] = useState<string>("");

  useEffect(() => {
    if (!authLoading && isAuthConfigured && !isAuthenticated) {
      navigate("/login");
    }
  }, [authLoading, isAuthConfigured, isAuthenticated, navigate]);

  if (authLoading) {
    return (
      <div className="relative z-10 h-screen flex flex-col overflow-hidden bg-[#0a1628]">
        <Masthead showNav />
        <main className="flex-1 flex items-center justify-center">
          <p className="text-[#7788aa] font-mono text-sm">Loading...</p>
        </main>
      </div>
    );
  }

  const handleCreate = async () => {
    if (!newTitle.trim() || !newDirectiveId) return;
    try {
      const o = await create({
        title: newTitle.trim(),
        description: newDesc.trim() || undefined,
        priority: newPriority,
        orderType: newType,
        directiveId: newDirectiveId,
      });
      setShowCreate(false);
      setNewTitle("");
      setNewDesc("");
      setNewPriority("medium");
      setNewType("feature");
      setNewDirectiveId("");
      navigate(`/orders/${o.id}`);
    } catch (e) {
      console.error("Failed to create order:", e);
    }
  };

  const handleDelete = async () => {
    if (!selectedId) return;
    if (!window.confirm("Delete this order?")) return;
    try {
      await removeOrder();
      await refreshList();
      navigate("/orders");
    } catch (e) {
      console.error("Failed to delete:", e);
    }
  };

  const handleUpdate = async (req: Parameters<typeof update>[0]) => {
    await update(req);
    await refreshList();
  };

  const handleLinkDirective = async (directiveId: string) => {
    await linkDirective(directiveId);
    await refreshList();
  };

  const handleConvertToStep = async () => {
    await convertToStep();
    await refreshList();
  };

  const priorityOptions: { value: OrderPriority; label: string }[] = [
    { value: "critical", label: "Critical" },
    { value: "high", label: "High" },
    { value: "medium", label: "Medium" },
    { value: "low", label: "Low" },
    { value: "none", label: "None" },
  ];

  const typeOptions: { value: OrderType; label: string }[] = [
    { value: "feature", label: "Feature" },
    { value: "bug", label: "Bug" },
    { value: "spike", label: "Spike" },
    { value: "chore", label: "Chore" },
    { value: "improvement", label: "Improvement" },
  ];

  return (
    <div className="relative z-10 h-screen flex flex-col overflow-hidden bg-[#0a1628]">
      <Masthead showNav />
      <main className="flex-1 flex overflow-hidden min-h-0">
        {/* Left: List */}
        <div className="w-[280px] shrink-0 border-r border-dashed border-[rgba(117,170,252,0.2)] overflow-hidden flex flex-col min-h-0">
          <OrderList
            orders={orders}
            selectedId={selectedId ?? null}
            onSelect={(id) => navigate(`/orders/${id}`)}
            onCreate={() => setShowCreate(true)}
            statusFilter={statusFilter}
            onStatusFilter={setStatusFilter}
            typeFilter={typeFilter}
            onTypeFilter={setTypeFilter}
          />
        </div>

        {/* Right: Detail or Create */}
        <div className="flex-1 overflow-hidden min-h-0">
          {showCreate ? (
            <div className="p-4 max-w-lg h-full overflow-y-auto">
              <h2 className="text-[14px] font-mono text-white font-medium mb-4">
                New Order
              </h2>
              <div className="flex flex-col gap-3">
                <div>
                  <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
                    Title
                  </label>
                  <input
                    value={newTitle}
                    onChange={(e) => setNewTitle(e.target.value)}
                    placeholder="Order title..."
                    className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white"
                    onKeyDown={(e) => {
                      if (e.key === "Enter" && newTitle.trim()) handleCreate();
                    }}
                  />
                </div>
                <div>
                  <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
                    Description (optional)
                  </label>
                  <textarea
                    value={newDesc}
                    onChange={(e) => setNewDesc(e.target.value)}
                    placeholder="Describe the order..."
                    rows={4}
                    className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white resize-y"
                  />
                </div>
                <div>
                  <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
                    Directive *
                  </label>
                  <select
                    value={newDirectiveId}
                    onChange={(e) => setNewDirectiveId(e.target.value)}
                    className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white"
                  >
                    <option value="">Select directive...</option>
                    {directives.map((d) => (
                      <option key={d.id} value={d.id}>{d.title}</option>
                    ))}
                  </select>
                </div>
                <div className="flex gap-4">
                  <div className="flex-1">
                    <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
                      Priority
                    </label>
                    <select
                      value={newPriority}
                      onChange={(e) => setNewPriority(e.target.value as OrderPriority)}
                      className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white"
                    >
                      {priorityOptions.map((p) => (
                        <option key={p.value} value={p.value}>{p.label}</option>
                      ))}
                    </select>
                  </div>
                  <div className="flex-1">
                    <label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
                      Type
                    </label>
                    <select
                      value={newType}
                      onChange={(e) => setNewType(e.target.value as OrderType)}
                      className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white"
                    >
                      {typeOptions.map((t) => (
                        <option key={t.value} value={t.value}>{t.label}</option>
                      ))}
                    </select>
                  </div>
                </div>
                <div className="flex gap-2">
                  <button
                    type="button"
                    onClick={handleCreate}
                    disabled={!newTitle.trim() || !newDirectiveId}
                    className="text-[11px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-3 py-1 disabled:opacity-50"
                  >
                    Create
                  </button>
                  <button
                    type="button"
                    onClick={() => setShowCreate(false)}
                    className="text-[11px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-3 py-1"
                  >
                    Cancel
                  </button>
                </div>
              </div>
            </div>
          ) : selectedId && order ? (
            <OrderDetail
              order={order}
              directives={directives}
              dogs={dogs}
              onUpdate={handleUpdate}
              onDelete={handleDelete}
              onLinkDirective={handleLinkDirective}
              onConvertToStep={handleConvertToStep}
              onRefresh={refreshDetail}
            />
          ) : (
            <div className="flex-1 flex items-center justify-center h-full">
              <p className="text-[#556677] font-mono text-[12px]">
                {listLoading
                  ? "Loading..."
                  : "Select an order or create a new one"}
              </p>
            </div>
          )}
        </div>
      </main>
    </div>
  );
}