94f2c92da1
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.
43 lines
1014 B
TypeScript
43 lines
1014 B
TypeScript
import "server-only";
|
|
|
|
import { Query } from "node-appwrite";
|
|
|
|
import { createAdminClient } from "./server";
|
|
import { DATABASE_ID, TABLES, type Customer } from "./schema";
|
|
|
|
export async function listCustomers(tenantId: string): Promise<Customer[]> {
|
|
try {
|
|
const { tablesDB } = createAdminClient();
|
|
const result = await tablesDB.listRows({
|
|
databaseId: DATABASE_ID,
|
|
tableId: TABLES.customers,
|
|
queries: [
|
|
Query.equal("tenantId", tenantId),
|
|
Query.orderDesc("$createdAt"),
|
|
Query.limit(500),
|
|
],
|
|
});
|
|
return result.rows as unknown as Customer[];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function getCustomer(
|
|
tenantId: string,
|
|
id: string,
|
|
): Promise<Customer | null> {
|
|
try {
|
|
const { tablesDB } = createAdminClient();
|
|
const row = (await tablesDB.getRow(
|
|
DATABASE_ID,
|
|
TABLES.customers,
|
|
id,
|
|
)) as unknown as Customer;
|
|
if (row.tenantId !== tenantId) return null;
|
|
return row;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|