Units CRUD Pattern
Convention notes when implementing Units with Convex + Next, safe MDX for AI
Objectives
- Standardize CRUD approach for units (Units) from backend to frontend.
- Quickly reusable for similar catalog modules (name, abbr, order, active).
- Follow safe MDX rules as in
fumadocs-ai-notes.mdx
.
Units Data Model
- Main fields:
name
,abbr?
,order
,active
. - Should have indexes:
by_name
(unique check in code),by_active
. - Every table has
order: number
andactive: boolean
for sorting/hiding.
// packages/backend/convex/schema.ts (excerpt)
// units: defineTable({ name: v.string(), abbr: v.optional(v.string()), order: v.number(), active: v.boolean() })
// .index("by_name", ["name"]).index("by_active", ["active"])
Backend (Convex) patterns
- File:
packages/backend/convex/units.ts
. - Required standard functions:
listBrief
: return brief list, sorted byorder
.create
: create new unit, check duplicate name viaby_name
.update
: updatename?
,abbr?
,order?
,active?
.toggleActive
: quick enable/disableactive
.safeDelete
: safe delete, block ifproducts
still reference viaunitId
.reorder
: bulk updateorder
to save drag-drop results.
// Reorder sample
export const reorder = mutation({
args: { items: v.array(v.object({ id: v.id("units"), order: v.number() })) },
handler: async (ctx, args) => {
for (const it of args.items) await ctx.db.patch(it.id, { order: it.order });
return { success: true };
},
});
Frontend patterns (Next + Convex React)
- List page:
apps/web/src/app/(dashboard)/units/page.tsx
.- Data:
useQuery(api.units.listBrief, {})
. - Actions:
useMutation(api.units.toggleActive)
,useMutation(api.units.safeDelete)
,useMutation(api.units.reorder)
. - UI features:
- Search by
name
/abbr
(client-side). - Filter status:
All
,Active
(active=true
),Inactive
(active=false
). - Sort by name: click "Name" column header to
asc/desc
. - Sort by order: click "Order" column header to return to
order
mode. - Drag-drop to change
order
and save viaapi.units.reorder
.- Only enable drag-drop when: no search keyword, viewing "Order", filter =
All
.
- Only enable drag-drop when: no search keyword, viewing "Order", filter =
- Quick enable/disable status on list via
toggleActive
.
- Search by
- Data:
// Call Convex in FE
import { useQuery, useMutation } from "convex/react";
import { api } from "@dohy/backend/convex/_generated/api";
const units = useQuery(api.units.listBrief, {});
const toggleActive = useMutation(api.units.toggleActive);
const safeDelete = useMutation(api.units.safeDelete);
const reorder = useMutation(api.units.reorder);
// Example enable/disable
await toggleActive({ id: unitId as any, active: true });
// Example save drag-drop
await reorder({ items: localList.map((u, idx) => ({ id: u._id as any, order: idx })) });
- Navigation (Sidebar + TopNav) needs updating when adding new modules to avoid forgetting:
// apps/web/src/app/(dashboard)/layout.tsx
// Add link to `links` array
const links = [
{ title: "Overview", href: "/dashboard" },
{ title: "Products", href: "/products" }, // example new module
{ title: "Units", href: "/units" },
];
// apps/web/src/components/layout/data/sidebar-data.ts
// Add item to appropriate group (e.g. General group)
{ title: 'Products', url: '/products', icon: Package }
- Create new page:
apps/web/src/app/(dashboard)/units/new/page.tsx
.- Form:
name
(required),abbr?
,order
. - Call
useMutation(api.units.create)
and redirect to/units
on success.
- Form:
await create({ name, abbr: abbr || undefined, order });
- Edit page:
apps/web/src/app/(dashboard)/units/[id]/edit/unit-edit.client.tsx
.- Load from
listBrief
to get record by_id
. - Update via
useMutation(api.units.update)
withname?
,abbr?
,order?
.
- Load from
await update({ id: _id as any, name, abbr: abbr || undefined, order });
Quick UX Conventions
- Display status: "Active" when
active=true
, "Inactive" whenactive=false
. - Quick button: "Activate" when inactive, "Deactivate" when active.
- Delete: use
safeDelete
; ifproducts
still reference it, show clear error message.
Checklist for Creating Similar Catalog CRUD
- Schema: add table with
name
,order
,active
(andabbr?
if needed) + indexesby_name
,by_active
. - Backend module: add
listBrief
,create
,update
,toggleActive
,safeDelete
,reorder
. - FE pages:
- List: table with search, status filter, sort by name, drag-drop by
order
, quick enable/disable. - New/Edit: simple form with
Card + Input + Label + Button
.
- List: table with search, status filter, sort by name, drag-drop by
- Connect Convex:
useQuery/useMutation
use@dohy/backend/convex/_generated/api
. - Quick test: run dev for Convex to generate
_generated
and check/units
page. - Update navigation:
- TopNav: edit
apps/web/src/app/(dashboard)/layout.tsx
to add link tolinks
. - Sidebar: edit
apps/web/src/components/layout/data/sidebar-data.ts
to add item to appropriate group.
- TopNav: edit
Safe MDX Notes (per fumadocs-ai-notes.mdx)
- Wrap all special characters in inline code with backticks: e.g.
[{ id, order }]
,{ name?, abbr? }
. - Put examples in 3-backtick code blocks to avoid parse errors.
- Don't embed strange JSX in MDX; only Markdown content + code blocks.