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")}
-
+
- Bu Hafta
- Bu Ay
- Bu Yıl
+ {t("analytics.range.thisWeek")}
+ {t("analytics.range.thisMonth")}
+ {t("analytics.range.thisYear")}
@@ -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 })}
shiftMonth(-1)}>
@@ -186,7 +193,7 @@ export function CalendarClient({ events, clients, projects, tasks }: CalendarCli
{formatDateLabel(selectedDate)}
- {selectedEvents.length} etkinlik
+ {t("common.itemsCount", { count: selectedEvents.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({
{mode === "create" ? : }
- {mode === "create" ? "Etkinlik ekle" : "Düzenle"}
+ {mode === "create" ? t("calendar.actions.add") : t("calendar.form.editTitle")}
@@ -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 (
-
- Başlık
-
-
-
- Açıklama
-
-
+ {activeLocales.length > 1 ? (
+
+
+ {activeLocales.map((locale) => (
+ {locale.name}
+ ))}
+
+ {activeLocales.map((locale) => (
+
+
+ {t("calendar.form.title")} ({locale.code})
+
+
+
+ {t("calendar.form.description")} ({locale.code})
+
+
+
+ ))}
+
+ ) : (
+
+
+ {t("calendar.form.title")}
+
+
+
+ {t("calendar.form.description")}
+
+
+
+ )}
+
-
- Toplantı
- Odak
- Deadline
- Kişisel
- Finans
+
+ {t("calendar.types.meeting")}
+ {t("calendar.types.focus")}
+ {t("calendar.types.deadline")}
+ {t("calendar.types.personal")}
+ {t("calendar.types.finance")}
-
- Müşteri yok
+
+ {t("calendar.form.selectClient")}
{clients.map((client) => {client.name} )}
-
- Proje yok
+
+ {t("calendar.form.selectProject")}
{projects.map((project) => {project.name} )}
-
- Görev yok
+
+ {t("calendar.form.selectTask")}
{tasks.map((task) => {task.title} )}
diff --git a/app/(dashboard)/calendar/page.tsx b/app/(dashboard)/calendar/page.tsx
index bd40f28..489f666 100644
--- a/app/(dashboard)/calendar/page.tsx
+++ b/app/(dashboard)/calendar/page.tsx
@@ -1,8 +1,26 @@
import { CalendarClient, type CalendarEventItem, type CalendarRelationOption, type CalendarTaskOption } from "@/app/(dashboard)/calendar/calendar-client";
import { requireFreelancerBackend } from "@/server/web/freelancer";
+import { resolveFreelancerLocale } from "@/server/i18n/resolver";
+import { getClientI18nPayload } from "@/server/i18n/translator";
+import { I18nProvider } from "@/components/i18n/i18n-provider";
+import type { ContentTranslationRow } from "@/server/i18n/content";
+
+function buildTranslations(rows: ContentTranslationRow[] | undefined) {
+ if (!rows) return undefined;
+ const result: Record
> = {};
+ for (const row of rows) {
+ if (!result[row.locale]) result[row.locale] = {};
+ result[row.locale][row.field] = row.value;
+ }
+ return result;
+}
export default async function CalendarPage() {
- const { actor, service } = await requireFreelancerBackend();
+ const { context, actor, service } = await requireFreelancerBackend();
+ const resolvedLocale = await resolveFreelancerLocale(context);
+ const payload = getClientI18nPayload(resolvedLocale.locale, ["calendar"]);
+ const activeLocales = service.listLocales(actor).filter(l => l.status !== "archived").map(l => ({ code: l.code, name: l.nativeName }));
+
const eventRows = service.listCalendarEvents(actor);
const clientRows = service.listClients(actor);
const projectRows = service.listProjects(actor);
@@ -11,6 +29,8 @@ export default async function CalendarPage() {
const projects = new Map(projectRows.map((item) => [item.id, item.name]));
const tasks = new Map(taskRows.map((item) => [item.id, item.title]));
+ const translationsMap = service.contentTranslations.listBatch("calendar_event", eventRows.map(e => e.id));
+
const events: CalendarEventItem[] = eventRows.map((event) => ({
id: event.id,
title: event.title,
@@ -24,6 +44,7 @@ export default async function CalendarPage() {
clientName: event.clientId ? clients.get(event.clientId) ?? null : null,
projectName: event.projectId ? projects.get(event.projectId) ?? null : null,
taskTitle: event.taskId ? tasks.get(event.taskId) ?? null : null,
+ translations: buildTranslations(translationsMap.get(event.id)),
}));
const clientOptions: CalendarRelationOption[] = clientRows
.filter((item) => item.status !== "archived")
@@ -35,5 +56,9 @@ export default async function CalendarPage() {
.filter((item) => item.status !== "done" && item.status !== "cancelled")
.map(({ id, title }) => ({ id, title }));
- return ;
+ return (
+
+
+
+ );
}