|
|
|
|
@@ -220,28 +220,47 @@ function PanelProfile({ config, editProfile, profileForm, setProfileForm, setEdi
|
|
|
|
|
|
|
|
|
|
if (!profile) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<div className="empty-state">{t('config.loadingProfile')}</div>
|
|
|
|
|
<div className="config-profile-center">
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<div className="empty-state">{t('config.loadingProfile')}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const personalKeys = Object.entries(profile).filter(([k, v]) => k !== 'preferences' && typeof v !== 'object')
|
|
|
|
|
const personalObj = Object.fromEntries(personalKeys)
|
|
|
|
|
const preferences = profile.preferences || null
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<RenderFields obj={profile} path="" editing={editProfile} onChange={updateField} t={t} />
|
|
|
|
|
<div className="config-card-actions">
|
|
|
|
|
{editProfile ? (
|
|
|
|
|
<>
|
|
|
|
|
<button className="primary sm" onClick={handleSaveProfile}>{t('config.save')}</button>
|
|
|
|
|
<button className="ghost sm" onClick={() => setEditProfile(false)}>{t('config.cancel')}</button>
|
|
|
|
|
</>
|
|
|
|
|
<div className="config-profile-center">
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<div className="section-title">{t('config.profileInfo') || 'Informations personnelles'}</div>
|
|
|
|
|
<RenderFields obj={personalObj} path="" editing={editProfile} onChange={updateField} t={t} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<div className="section-title">{t('config.profilePrefs') || 'Préférences'}</div>
|
|
|
|
|
{preferences ? (
|
|
|
|
|
<RenderFields obj={preferences} path="preferences" editing={editProfile} onChange={updateField} t={t} />
|
|
|
|
|
) : (
|
|
|
|
|
<button className="primary sm" onClick={() => {
|
|
|
|
|
setProfileForm(config.profile ? JSON.parse(JSON.stringify(config.profile)) : {})
|
|
|
|
|
setEditProfile(true)
|
|
|
|
|
}}>{t('config.editProfile')}</button>
|
|
|
|
|
<div className="config-card-row"><span className="config-card-value" style={{ color: 'var(--text-disabled)' }}>—</span></div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<div className="config-card-actions" style={{ justifyContent: 'center' }}>
|
|
|
|
|
{editProfile ? (
|
|
|
|
|
<>
|
|
|
|
|
<button className="primary sm" onClick={handleSaveProfile}>{t('config.save')}</button>
|
|
|
|
|
<button className="ghost sm" onClick={() => setEditProfile(false)}>{t('config.cancel')}</button>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<button className="primary sm" onClick={() => {
|
|
|
|
|
setProfileForm(config.profile ? JSON.parse(JSON.stringify(config.profile)) : {})
|
|
|
|
|
setEditProfile(true)
|
|
|
|
|
}}>{t('config.editProfile')}</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
@@ -249,19 +268,10 @@ function PanelProfile({ config, editProfile, profileForm, setProfileForm, setEdi
|
|
|
|
|
function RenderFields({ obj, path, editing, onChange, t }) {
|
|
|
|
|
if (!obj || typeof obj !== 'object') return null
|
|
|
|
|
|
|
|
|
|
return Object.entries(obj).map(([key, value]) => {
|
|
|
|
|
return Object.entries(obj).filter(([, v]) => v === null || typeof v !== 'object').map(([key, value]) => {
|
|
|
|
|
const fieldPath = path ? `${path}.${key}` : key
|
|
|
|
|
const label = getFieldLabel(key, t)
|
|
|
|
|
|
|
|
|
|
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
|
|
|
return (
|
|
|
|
|
<div key={key} className="config-card-group">
|
|
|
|
|
<span className="config-card-group-label">{label}</span>
|
|
|
|
|
<RenderFields obj={value} path={fieldPath} editing={editing} onChange={onChange} t={t} />
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (editing) {
|
|
|
|
|
if (typeof value === 'boolean') {
|
|
|
|
|
return (
|
|
|
|
|
@@ -461,6 +471,10 @@ function PanelUpdates({ updates, checking, updating, needsUpdateCount, installed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function PanelLocale({ language, keyboard, layouts, api, t }) {
|
|
|
|
|
const { setLanguage, setKeyboard } = useI18n()
|
|
|
|
|
const [editLocale, setEditLocale] = useState(false)
|
|
|
|
|
const [draftLang, setDraftLang] = useState(language)
|
|
|
|
|
const [draftKbd, setDraftKbd] = useState(keyboard)
|
|
|
|
|
const [saving, setSaving] = useState(false)
|
|
|
|
|
const [toast, setToast] = useState(null)
|
|
|
|
|
|
|
|
|
|
@@ -472,7 +486,10 @@ function PanelLocale({ language, keyboard, layouts, api, t }) {
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
setSaving(true)
|
|
|
|
|
try {
|
|
|
|
|
await api.savePreferences({ language, keyboard_layout: keyboard })
|
|
|
|
|
await api.savePreferences({ language: draftLang, keyboard_layout: draftKbd })
|
|
|
|
|
setLanguage(draftLang)
|
|
|
|
|
setKeyboard(draftKbd)
|
|
|
|
|
setEditLocale(false)
|
|
|
|
|
showToast(t('config.saved'))
|
|
|
|
|
} catch (err) {
|
|
|
|
|
showToast(`${t('config.error')}: ${err.message}`)
|
|
|
|
|
@@ -480,41 +497,67 @@ function PanelLocale({ language, keyboard, layouts, api, t }) {
|
|
|
|
|
setSaving(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentLang = LANGUAGES.find(l => l.id === language)
|
|
|
|
|
const currentKbd = layouts.find(l => l.id === keyboard)
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<div className="config-profile-center">
|
|
|
|
|
{toast && <div className="config-toast">{toast}</div>}
|
|
|
|
|
<div className="config-card-group">
|
|
|
|
|
<span className="config-card-group-label">{t('config.language')}</span>
|
|
|
|
|
<div className="chip-row">
|
|
|
|
|
{LANGUAGES.map(lang => (
|
|
|
|
|
<div
|
|
|
|
|
key={lang.id}
|
|
|
|
|
className={`chip ${language === lang.id ? 'active' : ''}`}
|
|
|
|
|
onClick={() => setLanguage(lang.id)}
|
|
|
|
|
>
|
|
|
|
|
{lang.name}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<div className="config-card-row">
|
|
|
|
|
<span className="config-card-label">{t('config.language')}</span>
|
|
|
|
|
<span className="config-card-value">{currentLang?.name || language}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="config-card-row">
|
|
|
|
|
<span className="config-card-label">{t('config.keyboardLayout')}</span>
|
|
|
|
|
<span className="config-card-value">{currentKbd?.name || keyboard}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="config-card-group">
|
|
|
|
|
<span className="config-card-group-label">{t('config.keyboardLayout')}</span>
|
|
|
|
|
<div className="chip-row">
|
|
|
|
|
{layouts.map(l => (
|
|
|
|
|
<div
|
|
|
|
|
key={l.id}
|
|
|
|
|
className={`chip ${keyboard === l.id ? 'active' : ''}`}
|
|
|
|
|
onClick={() => setKeyboard(l.id)}
|
|
|
|
|
>
|
|
|
|
|
{l.name}
|
|
|
|
|
{editLocale && (
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<div className="config-card-group">
|
|
|
|
|
<span className="config-card-group-label">{t('config.language')}</span>
|
|
|
|
|
<div className="chip-row">
|
|
|
|
|
{LANGUAGES.map(lang => (
|
|
|
|
|
<div
|
|
|
|
|
key={lang.id}
|
|
|
|
|
className={`chip ${draftLang === lang.id ? 'active' : ''}`}
|
|
|
|
|
onClick={() => setDraftLang(lang.id)}
|
|
|
|
|
>
|
|
|
|
|
{lang.name}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="config-card-group">
|
|
|
|
|
<span className="config-card-group-label">{t('config.keyboardLayout')}</span>
|
|
|
|
|
<div className="chip-row">
|
|
|
|
|
{layouts.map(l => (
|
|
|
|
|
<div
|
|
|
|
|
key={l.id}
|
|
|
|
|
className={`chip ${draftKbd === l.id ? 'active' : ''}`}
|
|
|
|
|
onClick={() => setDraftKbd(l.id)}
|
|
|
|
|
>
|
|
|
|
|
{l.name}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className="config-card">
|
|
|
|
|
<div className="config-card-actions" style={{ justifyContent: 'center' }}>
|
|
|
|
|
{editLocale ? (
|
|
|
|
|
<>
|
|
|
|
|
<button className="primary sm" onClick={handleSave} disabled={saving}>
|
|
|
|
|
{saving ? t('config.saving') : t('config.save')}
|
|
|
|
|
</button>
|
|
|
|
|
<button className="ghost sm" onClick={() => setEditLocale(false)}>{t('config.cancel')}</button>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<button className="primary sm" onClick={() => { setDraftLang(language); setDraftKbd(keyboard); setEditLocale(true) }}>{t('config.editProfile')}</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="config-card-actions">
|
|
|
|
|
<button className="primary sm" onClick={handleSave} disabled={saving}>
|
|
|
|
|
{saving ? t('config.saving') : t('config.save')}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
|