diff --git a/app/(dashboard)/analytics/analytics-client.tsx b/app/(dashboard)/analytics/analytics-client.tsx index 6b29c0e..3fff6e0 100644 --- a/app/(dashboard)/analytics/analytics-client.tsx +++ b/app/(dashboard)/analytics/analytics-client.tsx @@ -1,6 +1,8 @@ "use client"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; +import { useTranslations } from "@/components/i18n/i18n-provider"; +import { getDocumentIntlLocale } from "@/lib/i18n/browser"; import { Card, CardContent } from "poyraz-ui/atoms"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "poyraz-ui/molecules"; import { @@ -24,6 +26,7 @@ type AnalyticsClientProps = { const COLORS = ["var(--poyraz-primary)", "var(--poyraz-destructive)", "#eab308", "#3b82f6", "#8b5cf6"]; export function AnalyticsClient({ data }: AnalyticsClientProps) { + const t = useTranslations(); const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); @@ -37,28 +40,36 @@ export function AnalyticsClient({ data }: AnalyticsClientProps) { const { projectIncomeData, completedTasks, activeTasks } = data.metrics; const taskStatusData = [ - { name: "Tamamlanan", value: completedTasks }, - { name: "Devam Eden", value: activeTasks } + { name: t("analytics.tasks.completed"), value: completedTasks }, + { name: t("analytics.tasks.ongoing"), value: activeTasks } ]; + const formatCurrency = (val: number) => { + return new Intl.NumberFormat(getDocumentIntlLocale(), { + style: "currency", + currency: "USD", + maximumFractionDigits: 0, + }).format(val); + }; + return (

- Performans ve Finans Analizi + {t("analytics.title")}

@@ -67,7 +78,7 @@ export function AnalyticsClient({ data }: AnalyticsClientProps) {
-

Proje Bazlı Gelir Dağılımı

+

{t("analytics.sections.incomeDistribution")}

{projectIncomeData.length > 0 ? ( @@ -86,7 +97,7 @@ export function AnalyticsClient({ data }: AnalyticsClientProps) { ))} `₺${Number(value ?? 0)}`} + formatter={(value) => formatCurrency(Number(value ?? 0))} contentStyle={{ backgroundColor: 'var(--poyraz-background)', borderColor: 'var(--poyraz-border)', @@ -97,7 +108,7 @@ export function AnalyticsClient({ data }: AnalyticsClientProps) { ) : ( -
Veri bulunamadı.
+
{t("analytics.empty")}
)}
@@ -105,7 +116,7 @@ export function AnalyticsClient({ data }: AnalyticsClientProps) { -

Görev Durumu Analizi

+

{t("analytics.sections.taskStatus")}

@@ -124,7 +135,7 @@ export function AnalyticsClient({ data }: AnalyticsClientProps) {
- Görev Sayısı + {t("analytics.tasks.count")}
{entry.value} diff --git a/app/(dashboard)/analytics/page.tsx b/app/(dashboard)/analytics/page.tsx index 26b63b6..a13174b 100644 --- a/app/(dashboard)/analytics/page.tsx +++ b/app/(dashboard)/analytics/page.tsx @@ -1,6 +1,10 @@ import { AnalyticsClient, type AnalyticsData } from "./analytics-client"; import { parseDashboardRange, resolveDashboardRange } from "@/server/services/analytics-range"; import { requireFreelancerBackend } from "@/server/web/freelancer"; +import { requireFreelancer } from "@/server/auth/session"; +import { resolveFreelancerLocale } from "@/server/i18n/resolver"; +import { getClientI18nPayload } from "@/server/i18n/translator"; +import { I18nProvider } from "@/components/i18n/i18n-provider"; export const metadata = { title: "Analizler" }; @@ -9,11 +13,19 @@ export default async function AnalyticsPage({ }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { + const context = await requireFreelancer(); + const resolvedLocale = await resolveFreelancerLocale(context); + const payload = getClientI18nPayload(resolvedLocale.locale, ["analytics"]); + const params = await searchParams; const range = parseDashboardRange(params.range); const { actor, service } = await requireFreelancerBackend(); const metrics = service.getFreelancerAnalytics(actor, resolveDashboardRange(range)); const data: AnalyticsData = { metrics, range }; - return ; + return ( + + + + ); } diff --git a/app/(dashboard)/calendar/actions.ts b/app/(dashboard)/calendar/actions.ts index 23780a1..0d8cf25 100644 --- a/app/(dashboard)/calendar/actions.ts +++ b/app/(dashboard)/calendar/actions.ts @@ -3,6 +3,7 @@ import { revalidatePath } from "next/cache"; import { cleanText, optionalDate, requiredText } from "@/server/web/form-data"; import { requireFreelancerBackend } from "@/server/web/freelancer"; +import { parseContentTranslationsFromFormData } from "@/server/i18n/content"; const EVENT_TYPES = ["meeting", "focus", "deadline", "personal", "finance"] as const; @@ -12,16 +13,26 @@ function eventType(value: FormDataEntryValue | null) { : "focus"; } -function payload(formData: FormData) { +function payload( + formData: FormData, + service: Awaited>["service"], + actor: Awaited>["actor"] +) { + const context = service.contentTranslations.getLocalizationContext(actor); + const translations = parseContentTranslationsFromFormData(formData, "calendar_event", context); + const defaultTitle = translations?.[context.defaultLocale]?.title ?? ""; + const defaultDesc = translations?.[context.defaultLocale]?.description ?? ""; + return { - title: requiredText(formData.get("title"), "Etkinlik başlığı zorunludur."), - description: cleanText(formData.get("description")), + title: defaultTitle || requiredText(formData.get("title"), "Etkinlik başlığı zorunludur."), + description: defaultDesc || cleanText(formData.get("description")), type: eventType(formData.get("type")), startsAt: optionalDate(formData.get("starts_at")), endsAt: optionalDate(formData.get("ends_at")), clientId: cleanText(formData.get("client_id")), projectId: cleanText(formData.get("project_id")), taskId: cleanText(formData.get("task_id")), + translations, }; } @@ -42,7 +53,7 @@ function completeRelations( export async function createCalendarEventRecord(formData: FormData) { const backend = await requireFreelancerBackend(); - const value = completeRelations(payload(formData), backend.service, backend.actor); + const value = completeRelations(payload(formData, backend.service, backend.actor), backend.service, backend.actor); if (!value.startsAt) throw new Error("Etkinlik başlangıç zamanı zorunludur."); backend.service.createCalendarEvent(backend.actor, value); revalidatePath("/calendar"); @@ -51,7 +62,7 @@ export async function createCalendarEventRecord(formData: FormData) { export async function updateCalendarEventRecord(formData: FormData) { const backend = await requireFreelancerBackend(); const id = requiredText(formData.get("id"), "Etkinlik kaydı bulunamadı."); - const value = completeRelations(payload(formData), backend.service, backend.actor); + const value = completeRelations(payload(formData, backend.service, backend.actor), backend.service, backend.actor); if (!value.startsAt) throw new Error("Etkinlik başlangıç zamanı zorunludur."); backend.service.updateCalendarEvent(backend.actor, id, value); revalidatePath("/calendar"); diff --git a/app/(dashboard)/calendar/calendar-client.tsx b/app/(dashboard)/calendar/calendar-client.tsx index 0118868..7cf000a 100644 --- a/app/(dashboard)/calendar/calendar-client.tsx +++ b/app/(dashboard)/calendar/calendar-client.tsx @@ -21,6 +21,10 @@ import { SelectItem, SelectTrigger, SelectValue, + Tabs, + TabsContent, + TabsList, + TabsTrigger, toast, } from "poyraz-ui/molecules"; import { Clock, Pencil, Plus, Trash2 } from "lucide-react"; @@ -49,6 +53,7 @@ export type CalendarEventItem = { clientName: string | null; projectName: string | null; taskTitle: string | null; + translations?: Record>; }; const typeLabels = { @@ -72,9 +77,10 @@ type CalendarClientProps = { clients: CalendarRelationOption[]; projects: CalendarRelationOption[]; tasks: CalendarTaskOption[]; + activeLocales: { code: string; name: string }[]; }; -export function CalendarClient({ events, clients, projects, tasks }: CalendarClientProps) { +export function CalendarClient({ events, clients, projects, tasks, activeLocales }: CalendarClientProps) { const t = useTranslations(); const [monthDate, setMonthDate] = useState(() => new Date()); const [selectedDate, setSelectedDate] = useState(() => toDateKey(new Date())); @@ -102,6 +108,7 @@ export function CalendarClient({ events, clients, projects, tasks }: CalendarCli clients={clients} projects={projects} tasks={tasks} + activeLocales={activeLocales} />
@@ -113,7 +120,7 @@ export function CalendarClient({ events, clients, projects, tasks }: CalendarCli

{formatMonth(monthDate)}

-

{events.length} etkinlik

+

{t("common.itemsCount", { count: events.length })}

- +

Yaklaşan etkinlikler

- +
@@ -217,14 +225,17 @@ function EventList({ clients, projects, tasks, + activeLocales, compact = false, }: { events: CalendarEventItem[]; clients: CalendarRelationOption[]; projects: CalendarRelationOption[]; tasks: CalendarTaskOption[]; + activeLocales: { code: string; name: string }[]; compact?: boolean; }) { + const t = useTranslations(); if (events.length === 0) { return

Etkinlik yok.

; } @@ -246,16 +257,16 @@ function EventList({
) : null}
- {typeLabels[event.type]} + {t(`calendar.types.${event.type}` as any)}
{!compact ? (
- +
@@ -273,6 +284,7 @@ function CalendarEventDialog({ clients, projects, tasks, + activeLocales, }: { mode: "create" | "edit"; event?: CalendarEventItem; @@ -280,7 +292,9 @@ function CalendarEventDialog({ clients: CalendarRelationOption[]; projects: CalendarRelationOption[]; tasks: CalendarTaskOption[]; + activeLocales: { code: string; name: string }[]; }) { + const t = useTranslations(); const [open, setOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const action = mode === "create" ? createCalendarEventRecord : updateCalendarEventRecord; @@ -290,12 +304,12 @@ function CalendarEventDialog({ try { await action(formData); setOpen(false); - toast.success(mode === "create" ? "Etkinlik eklendi." : "Etkinlik güncellendi."); + toast.success(mode === "create" ? t("calendar.messages.created") : t("calendar.messages.updated")); } catch (error) { toast.error( error instanceof Error ? error.message - : "Etkinlik kaydedilirken beklenmeyen bir hata oluştu.", + : t("calendar.errors.saveFailed"), ); } finally { setIsSubmitting(false); @@ -307,25 +321,25 @@ function CalendarEventDialog({
{event ? : null} - {mode === "create" ? "Yeni etkinlik" : "Etkinliği düzenle"} - Takvim etkinliğini proje, görev veya müşteriyle ilişkilendir. + {mode === "create" ? t("calendar.form.createTitle") : t("calendar.form.editTitle")} + {t("calendar.description")}
- +
@@ -340,55 +354,83 @@ function EventFormFields({ clients, projects, tasks, + activeLocales, }: { event?: CalendarEventItem; defaultDate?: string; clients: CalendarRelationOption[]; projects: CalendarRelationOption[]; tasks: CalendarTaskOption[]; + activeLocales: { code: string; name: string }[]; }) { + const t = useTranslations(); const startsAt = event?.starts_at ? toDateTimeLocal(event.starts_at) : `${defaultDate || toDateKey(new Date())}T09:00`; return (
-
- - -
-
- -