feat(admin): comprehensive functionality pass across admin apps (Codex review)
- Restore admin order creation (top blocker): - New createAdminOrder server action wrapping create_order_with_items RPC with proper getAdminUser + can_manage_orders + brand scoping. - AdminOrdersPanel now supports ?new=true with working modal form (customer, stop/ship, dynamic items with fulfillment/price/qty, live total). - Fixed dashboard "New Order" and "Create your first order" links; added /admin/orders/new redirect. - Success flow: toast + redirect to list. - Product edit reliability and admin flows hardened. - Form a11y + validation foundation (cross-cutting): - AdminInput now generates stable ids, sets htmlFor, forwards required/aria-required/aria-describedby to children. - All admin forms benefit (labels programmatic, required semantics real). - Integrations: non-secret fields (email, name, phone for Resend/Twilio) no longer masked as password by default. Only isSecret fields use type=password + toggle. - Advanced/AI settings pages now expose real content (cards + links to actual config) instead of pure redirects. - Permission hardening example: water-log admin creates now enforce getAdminUser + can_manage_water_log + service key. - Systematic coverage of admin "apps": orders, products, stops, communications, wholesale, water-log, time-tracking, route-trace, settings (billing/integrations/ai/apps/etc), import, users, reports, etc. via exploration + targeted fixes. Empty states, toasts, scoping, quick actions improved. - Updated MEMORY.md with full pass summary, test instructions, and remaining items from Codex review. Non-destructive focus during pass; used dev auth for verification. Core blockers from review now addressed for testing. See session plan.md for detailed execution guide.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function AdminNewOrderRedirect() {
|
||||
// Preserve the "create first order" intent but route to the supported flow
|
||||
redirect("/admin/orders?new=true");
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { getAdminOrders } from "@/actions/orders";
|
||||
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
|
||||
import { PageHeader } from "@/components/admin/design-system";
|
||||
import { redirect } from "next/navigation";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -29,6 +30,33 @@ export default async function AdminOrdersPage() {
|
||||
)
|
||||
: orders;
|
||||
|
||||
// Fetch active products for this brand (for admin "New Order" item picker)
|
||||
let brandProducts: Array<{ id: string; name: string; price: number; type?: string | null; active?: boolean }> = [];
|
||||
try {
|
||||
let prodQuery = supabase
|
||||
.from("products")
|
||||
.select("id, name, price, type, active")
|
||||
.eq("active", true)
|
||||
.is("deleted_at", null)
|
||||
.order("name")
|
||||
.limit(200);
|
||||
|
||||
if (adminUser?.brand_id) {
|
||||
prodQuery = prodQuery.eq("brand_id", adminUser.brand_id);
|
||||
}
|
||||
|
||||
const { data: prods } = await prodQuery;
|
||||
brandProducts = (prods ?? []).map((p: any) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
price: Number(p.price),
|
||||
type: p.type ?? null,
|
||||
active: p.active ?? true,
|
||||
}));
|
||||
} catch {
|
||||
// non-fatal for the orders list
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--admin-bg)]">
|
||||
<div className="px-4 sm:px-6 md:px-8 pt-4 sm:pt-6">
|
||||
@@ -51,6 +79,7 @@ export default async function AdminOrdersPage() {
|
||||
<AdminOrdersPanel
|
||||
initialOrders={brandOrders}
|
||||
initialStops={brandStops}
|
||||
initialProducts={brandProducts}
|
||||
brandId={adminUser?.brand_id ?? null}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user