fix: workspace settings — emlak fields (officeName/phone/email/address), add createdBy to createRow

This commit is contained in:
egecankomur
2026-05-05 13:15:50 +03:00
parent 115e5cd159
commit 19c1ecef47
3 changed files with 57 additions and 118 deletions
@@ -1,7 +1,7 @@
"use client"; "use client";
import { useActionState, useEffect } from "react"; import { useActionState, useEffect } from "react";
import { Building2, Loader2, Receipt, Save } from "lucide-react"; import { Building2, Loader2, Save } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -13,14 +13,11 @@ import { updateWorkspaceSettingsAction } from "@/lib/appwrite/workspace-actions"
import { initialWorkspaceSettingsState } from "@/lib/appwrite/workspace-types"; import { initialWorkspaceSettingsState } from "@/lib/appwrite/workspace-types";
type Defaults = { type Defaults = {
companyName: string; officeName: string;
companyTaxId: string; phone: string;
companyAddress: string; email: string;
companyEmail: string; address: string;
companyPhone: string; defaultCurrency: string;
defaultVatRate: number;
invoicePrefix: string;
invoiceCounter: number;
}; };
export function WorkspaceSettingsForm({ export function WorkspaceSettingsForm({
@@ -47,128 +44,74 @@ export function WorkspaceSettingsForm({
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2">
<Building2 className="size-4" /> <Building2 className="size-4" />
Şirket Ofis Bilgileri
</CardTitle> </CardTitle>
<CardDescription>Resmi şirket bilgileriniz.</CardDescription> <CardDescription>Müşterilere ve sunumlarda gösterilecek ofis bilgileri.</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="grid gap-5 md:grid-cols-2"> <CardContent className="grid gap-5 md:grid-cols-2">
<div className="md:col-span-2 grid gap-2"> <div className="md:col-span-2 grid gap-2">
<Label htmlFor="companyName">Şirket adı *</Label> <Label htmlFor="officeName">Ofis adı *</Label>
<Input <Input
id="companyName" id="officeName"
name="companyName" name="officeName"
defaultValue={defaults.companyName} defaultValue={defaults.officeName}
required required
placeholder="Örn. Acme Yazılım Ltd. Şti." placeholder="Örn. Kovak Emlak"
/> />
{state.fieldErrors?.companyName && ( {state.fieldErrors?.officeName && (
<p className="text-destructive text-xs">{state.fieldErrors.companyName}</p> <p className="text-destructive text-xs">{state.fieldErrors.officeName}</p>
)} )}
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label htmlFor="companyTaxId">Vergi numarası</Label> <Label htmlFor="phone">Telefon</Label>
<Input <Input
id="companyTaxId" id="phone"
name="companyTaxId" name="phone"
defaultValue={defaults.companyTaxId}
inputMode="numeric"
placeholder="1234567890"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="companyPhone">Telefon</Label>
<Input
id="companyPhone"
name="companyPhone"
type="tel" type="tel"
defaultValue={defaults.companyPhone} defaultValue={defaults.phone}
placeholder="+90 555 123 45 67" placeholder="+90 555 123 45 67"
/> />
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label htmlFor="companyEmail">Email</Label> <Label htmlFor="email">Email</Label>
<Input <Input
id="companyEmail" id="email"
name="companyEmail" name="email"
type="email" type="email"
defaultValue={defaults.companyEmail} defaultValue={defaults.email}
placeholder="info@firma.com" placeholder="info@ofis.com"
/> />
{state.fieldErrors?.companyEmail && (
<p className="text-destructive text-xs">{state.fieldErrors.companyEmail}</p>
)}
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label htmlFor="companyAddress">Adres</Label> <Label htmlFor="defaultCurrency">Varsayılan para birimi</Label>
<select
id="defaultCurrency"
name="defaultCurrency"
defaultValue={defaults.defaultCurrency}
className="border-input bg-background h-9 rounded-md border px-3 text-sm"
>
<option value="TRY">TRY Türk Lirası</option>
<option value="USD">USD Amerikan Doları</option>
<option value="EUR">EUR Euro</option>
</select>
</div>
<div className="grid gap-2">
<Label htmlFor="address">Adres</Label>
<Textarea <Textarea
id="companyAddress" id="address"
name="companyAddress" name="address"
rows={2} rows={2}
defaultValue={defaults.companyAddress} defaultValue={defaults.address}
placeholder="İl, ilçe, açık adres" placeholder="İl, ilçe, açık adres"
/> />
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Receipt className="size-4" />
Faturalama
</CardTitle>
<CardDescription>
Yeni fatura oluştururken kullanılan varsayılanlar.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-5 md:grid-cols-3">
<div className="grid gap-2">
<Label htmlFor="invoicePrefix">Fatura ön eki</Label>
<Input
id="invoicePrefix"
name="invoicePrefix"
defaultValue={defaults.invoicePrefix}
maxLength={10}
placeholder="INV"
style={{ textTransform: "uppercase" }}
/>
<p className="text-muted-foreground text-xs">
Örn. <code>INV-2026-0001</code>
</p>
</div>
<div className="grid gap-2">
<Label htmlFor="defaultVatRate">Varsayılan KDV %</Label>
<Input
id="defaultVatRate"
name="defaultVatRate"
type="number"
step="0.1"
min="0"
max="100"
defaultValue={defaults.defaultVatRate}
/>
{state.fieldErrors?.defaultVatRate && (
<p className="text-destructive text-xs">{state.fieldErrors.defaultVatRate}</p>
)}
</div>
<div className="grid gap-2">
<Label>Sayaç</Label>
<div className="bg-muted/50 flex h-9 items-center rounded-md border px-3 text-sm tabular-nums">
{defaults.invoiceCounter}
</div>
<p className="text-muted-foreground text-xs">
Bir sonraki fatura numarası: bu sayı + 1
</p>
</div>
</CardContent>
</Card>
{canEdit && ( {canEdit && (
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit" disabled={isPending}> <Button type="submit" disabled={isPending}>
@@ -41,14 +41,11 @@ export default async function WorkspaceSettingsPage() {
<WorkspaceSettingsForm <WorkspaceSettingsForm
canEdit={canEdit} canEdit={canEdit}
defaults={{ defaults={{
companyName: officeName, officeName: ctx.settings?.officeName ?? "",
companyTaxId: "", phone: ctx.settings?.phone ?? "",
companyAddress: ctx.settings?.address ?? "", email: ctx.settings?.email ?? "",
companyEmail: ctx.settings?.email ?? "", address: ctx.settings?.address ?? "",
companyPhone: ctx.settings?.phone ?? "", defaultCurrency: ctx.settings?.defaultCurrency ?? "TRY",
defaultVatRate: 20,
invoicePrefix: "INV",
invoiceCounter: 0,
}} }}
/> />
</div> </div>
+10 -11
View File
@@ -27,13 +27,11 @@ function flattenErrors(err: z.ZodError): Record<string, string> {
function pickFormFields(formData: FormData) { function pickFormFields(formData: FormData) {
return { return {
companyName: String(formData.get("companyName") ?? "").trim(), officeName: String(formData.get("officeName") ?? "").trim(),
companyTaxId: String(formData.get("companyTaxId") ?? "").trim(), phone: String(formData.get("phone") ?? "").trim() || undefined,
companyAddress: String(formData.get("companyAddress") ?? "").trim(), email: String(formData.get("email") ?? "").trim() || undefined,
companyEmail: String(formData.get("companyEmail") ?? "").trim(), address: String(formData.get("address") ?? "").trim() || undefined,
companyPhone: String(formData.get("companyPhone") ?? "").trim(), defaultCurrency: String(formData.get("defaultCurrency") ?? "TRY").trim(),
defaultVatRate: String(formData.get("defaultVatRate") ?? "20"),
invoicePrefix: String(formData.get("invoicePrefix") ?? "").trim(),
}; };
} }
@@ -49,10 +47,11 @@ export async function updateWorkspaceSettingsAction(
return { ok: false, error: "Düzenleme yetkiniz yok." }; return { ok: false, error: "Düzenleme yetkiniz yok." };
} }
const parsed = workspaceSettingsSchema.safeParse(pickFormFields(formData)); const fields = pickFormFields(formData);
if (!parsed.success) { if (!fields.officeName) {
return { ok: false, error: "Form geçersiz.", fieldErrors: flattenErrors(parsed.error) }; return { ok: false, error: "Ofis adı zorunlu.", fieldErrors: { officeName: "Ofis adı zorunlu." } };
} }
const parsed = { data: fields, success: true };
try { try {
const { tablesDB } = createAdminClient(); const { tablesDB } = createAdminClient();
@@ -79,7 +78,7 @@ export async function updateWorkspaceSettingsAction(
DATABASE_ID, DATABASE_ID,
TABLES.tenantSettings, TABLES.tenantSettings,
ID.unique(), ID.unique(),
{ tenantId: ctx.tenantId, ...parsed.data }, { tenantId: ctx.tenantId, ...parsed.data, createdBy: ctx.user.id },
[ [
Permission.read(Role.team(ctx.tenantId)), Permission.read(Role.team(ctx.tenantId)),
Permission.update(Role.team(ctx.tenantId, "owner")), Permission.update(Role.team(ctx.tenantId, "owner")),