feat: RTL/i18n, AI translate+undo, no-refresh saves, settings perf

- RTL: force dir=rtl on LabelFilter, NotesViewToggle, LabelManagementDialog
- i18n: add missing keys (notifications, privacy, edit/preview, AI translate/undo)
- Settings pages: convert to Server Components (general, appearance) + loading skeleton
- AI menu: add Translate option (10 languages) + Undo AI button in toolbar
- Fix: saveInline uses REST API instead of Server Action → eliminates all implicit refreshes in list mode
- Fix: NotesTabsView notes sync effect preserves selected note on content changes
- Fix: auto-tag suggestions now filter already-assigned labels
- Fix: color change in card view uses local state (no refresh)
- Fix: nav links use <Link> for prefetching (Settings, Admin)
- Fix: suppress duplicate label suggestions already on note
- Route: add /api/ai/translate endpoint
This commit is contained in:
Sepehr Ramezani
2026-04-15 23:48:28 +02:00
parent 39671c6472
commit b6a548acd8
68 changed files with 5014 additions and 485 deletions

View File

@@ -64,21 +64,7 @@ export function AISettingsPanel({ initialSettings }: AISettingsPanelProps) {
}
}
const handleProviderChange = async (value: 'auto' | 'openai' | 'ollama') => {
setSettings(prev => ({ ...prev, aiProvider: value }))
try {
setIsPending(true)
await updateAISettings({ aiProvider: value })
toast.success(t('aiSettings.saved'))
} catch (error) {
console.error('Error updating provider:', error)
toast.error(t('aiSettings.error'))
setSettings(initialSettings)
} finally {
setIsPending(false)
}
}
const handleLanguageChange = async (value: 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl') => {
setSettings(prev => ({ ...prev, preferredLanguage: value }))
@@ -188,54 +174,7 @@ export function AISettingsPanel({ initialSettings }: AISettingsPanelProps) {
/>
</div>
{/* AI Provider Selection */}
<Card className="p-4">
<Label className="text-base font-medium mb-1">{t('aiSettings.provider')}</Label>
<p className="text-sm text-gray-500 mb-4">
{t('aiSettings.providerDesc')}
</p>
<RadioGroup
value={settings.aiProvider}
onValueChange={handleProviderChange}
>
<div className="flex items-start space-x-2 py-2">
<RadioGroupItem value="auto" id="auto" />
<div className="grid gap-1.5">
<Label htmlFor="auto" className="font-medium">
{t('aiSettings.providerAuto')}
</Label>
<p className="text-sm text-gray-500">
{t('aiSettings.providerAutoDesc')}
</p>
</div>
</div>
<div className="flex items-start space-x-2 py-2">
<RadioGroupItem value="ollama" id="ollama" />
<div className="grid gap-1.5">
<Label htmlFor="ollama" className="font-medium">
{t('aiSettings.providerOllama')}
</Label>
<p className="text-sm text-gray-500">
{t('aiSettings.providerOllamaDesc')}
</p>
</div>
</div>
<div className="flex items-start space-x-2 py-2">
<RadioGroupItem value="openai" id="openai" />
<div className="grid gap-1.5">
<Label htmlFor="openai" className="font-medium">
{t('aiSettings.providerOpenAI')}
</Label>
<p className="text-sm text-gray-500">
{t('aiSettings.providerOpenAIDesc')}
</p>
</div>
</div>
</RadioGroup>
</Card>
</div>
)
}