'use client' import { useEffect, useMemo, useState } from 'react' import Link from '@/framework/link' import { useParams } from '@/framework/navigation' import { Button, Container, Group, Paper, Select, Stack, Text, Textarea, Title, } from '@pikku/mantine/core' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { asI18n } from '@pikku/react' import type { BackendTranslationBundle, BackendTranslationNamespace, } from '@heygermany/sdk' import { invokeRPC } from '@/pikku/rpc' function toPrettyJson(value: Record) { return JSON.stringify(value, null, 2) } function isRecord(value: unknown): value is Record { return Boolean(value) && typeof value === 'object' && !Array.isArray(value) } export default function BackOfficeTranslationsPage() { const params = useParams() const lang = params.lang as string const queryClient = useQueryClient() const [selectedLocale, setSelectedLocale] = useState(null) const [selectedNamespace, setSelectedNamespace] = useState(null) const [editorValue, setEditorValue] = useState('') const [editorError, setEditorError] = useState(null) const query = useQuery({ queryKey: ['backoffice', 'translations'], queryFn: async () => await invokeRPC('getBackOfficeTranslations', {}), }) const rows = query.data ?? [] const localeOptions = useMemo( () => Array.from(new Set(rows.map((row) => row.locale))).map((locale) => ({ value: locale, label: locale, })), [rows] ) const namespaceOptions = useMemo( () => Array.from(new Set(rows.map((row) => row.namespace))).map((namespace) => ({ value: namespace, label: namespace, })), [rows] ) useEffect(() => { if (rows.length === 0) return if (!selectedLocale) { setSelectedLocale(rows.find((row) => Object.keys(row.content).length > 0)?.locale ?? rows[0].locale) } if (!selectedNamespace) { setSelectedNamespace( rows.find((row) => Object.keys(row.content).length > 0)?.namespace ?? rows[0].namespace ) } }, [rows, selectedLocale, selectedNamespace]) const selectedRow = useMemo( () => rows.find( (row) => row.locale === selectedLocale && row.namespace === selectedNamespace ) ?? null, [rows, selectedLocale, selectedNamespace] ) useEffect(() => { if (!selectedRow) return setEditorValue(toPrettyJson(selectedRow.content)) setEditorError(null) }, [selectedRow]) const saveMutation = useMutation({ mutationFn: async () => { if (!selectedLocale || !selectedNamespace) { throw new Error('Select a locale and namespace first') } const parsed = JSON.parse(editorValue) if (!isRecord(parsed)) { throw new Error('Translation bundle must be a JSON object') } return await invokeRPC('updateBackOfficeTranslation', { locale: selectedLocale, namespace: selectedNamespace, content: parsed, }) }, onSuccess: async () => { setEditorError(null) await queryClient.invalidateQueries({ queryKey: ['backoffice', 'translations'], }) }, onError: (error) => { setEditorError(error instanceof Error ? error.message : String(error)) }, }) return ( {asI18n('Backend translations')} {asI18n('Edit DB-backed candidate result copy. Changes apply live to backend result rendering.')} setSelectedNamespace(value as BackendTranslationNamespace | null) } />