feat(customers): full CRUD module — pattern for all other modules

Establishes the multi-tenant module pattern. Subsequent modules (services,
software, calendar, tasks, finance, invoices) will copy this structure.

Validation:
- lib/validation/customers.ts: Zod schema with Turkish messages, optional
  fields normalized to undefined.

Server actions (lib/appwrite/customer-actions.ts):
- createCustomerAction, updateCustomerAction, deleteCustomerAction
- All call requireTenant() guard, write team-scoped row permissions
  (read+update by team, delete by owner|admin), and emit audit log.
- Update/delete cross-check tenantId on the existing row before mutating
  (defense in depth even though row-level perms already enforce it).
- Field-level errors flattened from Zod for inline form display.

Server-side queries (lib/appwrite/customer-queries.ts):
- listCustomers(tenantId), getCustomer(tenantId, id) — admin SDK with
  Query.equal('tenantId',...) tenant scope.

UI:
- /customers page (server component): pulls active tenant context, lists
  customers, hands off to CustomersClient.
- CustomersClient: TanStack Table with global filter (name/email/phone/
  taxId), column sorting on name + createdAt, pagination (20/page),
  status badges, row actions (Edit/Delete dropdown), empty-state CTA.
- CustomerFormSheet: shadcn Sheet-based add/edit form with all fields,
  toast feedback (sonner), inline field errors. Reused for create + update
  by switching the action.
- DeleteCustomerDialog: confirmation modal with destructive button.

Infrastructure:
- Added sonner Toaster to root layout (richColors, closeButton).
- Updated metadata to 'İşletmem KovakCRM' and html lang='tr'.
- Renamed theme storage key to isletmem-ui-theme.
This commit is contained in:
kovakmedya
2026-04-30 05:44:00 +03:00
parent 643f2de29b
commit 94f2c92da1
10 changed files with 892 additions and 7 deletions
+7 -7
View File
@@ -2,12 +2,13 @@ import type { Metadata } from "next";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import { Toaster } from "@/components/ui/sonner";
import { SidebarConfigProvider } from "@/contexts/sidebar-context";
import { inter } from "@/lib/fonts";
export const metadata: Metadata = {
title: "Shadcn Dashboard",
description: "A dashboard built with Next.js and shadcn/ui",
title: "İşletmem KovakCRM",
description: "Multi-tenant CRM — KovakSoft",
};
export default function RootLayout({
@@ -16,13 +17,12 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="en" className={`${inter.variable} antialiased`}>
<html lang="tr" className={`${inter.variable} antialiased`}>
<body className={inter.className}>
<ThemeProvider defaultTheme="system" storageKey="nextjs-ui-theme">
<SidebarConfigProvider>
{children}
</SidebarConfigProvider>
<ThemeProvider defaultTheme="system" storageKey="isletmem-ui-theme">
<SidebarConfigProvider>{children}</SidebarConfigProvider>
</ThemeProvider>
<Toaster richColors closeButton />
</body>
</html>
);