fix: workspace settings — emlak fields (officeName/phone/email/address), add createdBy to createRow
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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")),
|
||||||
|
|||||||
Reference in New Issue
Block a user