dashboards panel keyed (469 -> 445)
The layout templates are module-level data, so they carry nameKey and both consumers resolve it — the visible label with t(), and the preview's own mini-registry with panelName(), which is the same late-bound-thunk problem the app registry has. Cancel comes from ui.button.cancel; Delete is panel.common.delete since nothing in the shared components renders one.
This commit is contained in:
@@ -26,8 +26,10 @@ import {
|
||||
} from './constants';
|
||||
import { useNewDashboardDraft } from './useNewDashboardDraft';
|
||||
import { cwdToPath } from '../../components/Workspace';
|
||||
import { useTranslation } from '../../locales';
|
||||
|
||||
export const DashboardListApp = () => {
|
||||
const { t, tUi } = useTranslation();
|
||||
const location = useLocation();
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -105,7 +107,7 @@ export const DashboardListApp = () => {
|
||||
className="flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium bg-duck-teal/15 text-duck-teal"
|
||||
>
|
||||
<LayoutGrid className="h-4 w-4" />
|
||||
Dashboards
|
||||
{t('dashboards.title')}
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
@@ -113,7 +115,7 @@ export const DashboardListApp = () => {
|
||||
className="flex items-center justify-center gap-1 py-2 px-3 rounded-lg text-sm font-medium bg-duck-teal hover:bg-duck-teal/90 text-white transition-all cursor-pointer"
|
||||
>
|
||||
<Plus className="h-4 w-4 shrink-0" />
|
||||
New Dashboard
|
||||
{t('dashboards.new')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="relative px-3 pt-2">
|
||||
@@ -122,7 +124,7 @@ export const DashboardListApp = () => {
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={(ev) => setSearch(ev.target.value)}
|
||||
placeholder="Search dashboards"
|
||||
placeholder={t('dashboards.search')}
|
||||
className="w-full rounded-lg border border-duck-dark/15 bg-transparent py-1.5 pl-8 pr-3 text-sm text-white placeholder:text-gray-500 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 focus:border-duck-teal/40"
|
||||
/>
|
||||
</div>
|
||||
@@ -182,15 +184,15 @@ export const DashboardListApp = () => {
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete dashboard</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t('dashboards.delete')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete <strong>{deleting?.name}</strong>? This action cannot be undone.
|
||||
{t('dashboards.confirmDelete')} <strong>{deleting?.name}</strong>? This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogCancel>{tUi('button.cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={confirmDelete} className="bg-red-600 hover:bg-red-700">
|
||||
Delete
|
||||
{t('common.delete')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
||||
@@ -19,21 +19,23 @@ import {
|
||||
NEW_DASH_DESC_KEY,
|
||||
NEW_DASH_TEMPLATE_KEY,
|
||||
} from './constants';
|
||||
import type { PanelTranslationKey } from '../../locales';
|
||||
import { panelName, useTranslation } from '../../locales';
|
||||
|
||||
// --- Layout Templates ---
|
||||
|
||||
type LayoutTemplate = {
|
||||
name: string;
|
||||
nameKey: PanelTranslationKey;
|
||||
layout: () => LayoutNode;
|
||||
};
|
||||
|
||||
const templates: LayoutTemplate[] = [
|
||||
{
|
||||
name: 'Single',
|
||||
nameKey: 'dashboards.templates.single',
|
||||
layout: () => ({ type: 'panel', id: uid(), appType: null }),
|
||||
},
|
||||
{
|
||||
name: '2 Columns',
|
||||
nameKey: 'dashboards.templates.twoColumns',
|
||||
layout: () => ({
|
||||
type: 'group',
|
||||
id: uid(),
|
||||
@@ -45,7 +47,7 @@ const templates: LayoutTemplate[] = [
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'Main + Side',
|
||||
nameKey: 'dashboards.templates.mainSide',
|
||||
layout: () => ({
|
||||
type: 'group',
|
||||
id: uid(),
|
||||
@@ -68,7 +70,7 @@ const templates: LayoutTemplate[] = [
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'Sidebar',
|
||||
nameKey: 'dashboards.templates.sidebar',
|
||||
layout: () => ({
|
||||
type: 'group',
|
||||
id: uid(),
|
||||
@@ -80,7 +82,7 @@ const templates: LayoutTemplate[] = [
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: '2x2 Grid',
|
||||
nameKey: 'dashboards.templates.grid',
|
||||
layout: () => ({
|
||||
type: 'group',
|
||||
id: uid(),
|
||||
@@ -114,7 +116,7 @@ const templates: LayoutTemplate[] = [
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'Cols + Bottom',
|
||||
nameKey: 'dashboards.templates.colsBottom',
|
||||
layout: () => ({
|
||||
type: 'group',
|
||||
id: uid(),
|
||||
@@ -157,6 +159,7 @@ const ThumbnailNode = ({ node }: { node: LayoutNode }) => {
|
||||
};
|
||||
|
||||
const TemplateCell = ({ index }: { index: number }) => {
|
||||
const { t } = useTranslation();
|
||||
const tpl = templates[index]!;
|
||||
const [selected, setSelected] = useGlobal<number>(NEW_DASH_TEMPLATE_KEY, 0);
|
||||
const node = tpl.layout();
|
||||
@@ -174,7 +177,9 @@ const TemplateCell = ({ index }: { index: number }) => {
|
||||
<div className="w-full flex-1 min-h-0 rounded border border-duck-dark/20 overflow-hidden">
|
||||
<ThumbnailNode node={node} />
|
||||
</div>
|
||||
<span className={`text-xs font-medium ${isSelected ? 'text-duck-teal' : 'text-duck-dark/60'}`}>{tpl.name}</span>
|
||||
<span className={`text-xs font-medium ${isSelected ? 'text-duck-teal' : 'text-duck-dark/60'}`}>
|
||||
{t(tpl.nameKey)}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -219,7 +224,7 @@ const tplPanelLayout: LayoutNode = {
|
||||
const tplRegistry = Object.fromEntries(
|
||||
templates.map((tpl, i) => [
|
||||
`tpl-${i}`,
|
||||
{ name: tpl.name, icon: Layout, component: () => <TemplateCell index={i} /> },
|
||||
{ name: panelName(tpl.nameKey), icon: Layout, component: () => <TemplateCell index={i} /> },
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -230,6 +235,7 @@ const TemplatePanel = () => (
|
||||
// --- Panel: Name ---
|
||||
|
||||
const NamePanel = () => {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useGlobal<string>(NEW_DASH_NAME_KEY, '');
|
||||
const [description, setDescription] = useGlobal<string>(NEW_DASH_DESC_KEY, '');
|
||||
|
||||
@@ -239,13 +245,13 @@ const NamePanel = () => {
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-duck-dark/50 flex items-center gap-1.5">
|
||||
<Type className="h-3 w-3" />
|
||||
Name
|
||||
{t('dashboards.name')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(ev) => setName(ev.target.value)}
|
||||
placeholder="My Dashboard"
|
||||
placeholder={t('dashboards.namePlaceholder')}
|
||||
autoFocus
|
||||
className="rounded-lg border border-duck-dark/20 bg-background px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-2 focus:ring-duck-teal/30 focus:border-duck-teal/50"
|
||||
/>
|
||||
@@ -253,12 +259,12 @@ const NamePanel = () => {
|
||||
<div className="flex flex-1 min-h-0 flex-col gap-1.5">
|
||||
<label className="text-xs font-medium text-duck-dark/50 flex items-center gap-1.5">
|
||||
<FileText className="h-3 w-3" />
|
||||
Description
|
||||
{t('dashboards.description')}
|
||||
</label>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(ev) => setDescription(ev.target.value)}
|
||||
placeholder="What is this dashboard for?"
|
||||
placeholder={t('dashboards.descriptionPlaceholder')}
|
||||
className="flex-1 min-h-0 resize-none rounded-lg border border-duck-dark/20 bg-background px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-2 focus:ring-duck-teal/30 focus:border-duck-teal/50"
|
||||
/>
|
||||
</div>
|
||||
@@ -270,6 +276,7 @@ const NamePanel = () => {
|
||||
// --- Panel: Create ---
|
||||
|
||||
const CreatePanel = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const client = useClient();
|
||||
@@ -401,7 +408,7 @@ const CreatePanel = () => {
|
||||
<Button asChild variant="outline" className="cursor-pointer">
|
||||
<Link to={`/dashboards/${editingId}`}>
|
||||
<ArrowRight className="h-4 w-4 mr-1" />
|
||||
Open Dashboard
|
||||
{t('dashboards.open')}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
@@ -453,13 +460,14 @@ const newDashLayout: LayoutNode = {
|
||||
};
|
||||
|
||||
const NewDashboardForm = () => {
|
||||
const { t } = useTranslation();
|
||||
const { registry } = useAppRegistry();
|
||||
const newDashRegistry = {
|
||||
...registry,
|
||||
'file-browser-cwd': { name: 'File Browser', icon: FolderOpen, component: CwdFileBrowser },
|
||||
'new-ws-name': { name: 'Name', icon: Type, component: NamePanel },
|
||||
'new-ws-template': { name: 'Template', icon: Layout, component: TemplatePanel },
|
||||
'new-ws-create': { name: 'Create', icon: Rocket, component: CreatePanel },
|
||||
'file-browser-cwd': { name: panelName('panelName.fileBrowser'), icon: FolderOpen, component: CwdFileBrowser },
|
||||
'new-ws-name': { name: t('dashboards.name'), icon: Type, component: NamePanel },
|
||||
'new-ws-template': { name: t('dashboards.template'), icon: Layout, component: TemplatePanel },
|
||||
'new-ws-create': { name: t('dashboards.create'), icon: Rocket, component: CreatePanel },
|
||||
};
|
||||
|
||||
return <WorkspaceLayout layout={newDashLayout} onLayoutChange={() => {}} registry={newDashRegistry} noHeader />;
|
||||
@@ -470,6 +478,7 @@ const NewDashboardForm = () => {
|
||||
// Only reached when nothing is selected AND nothing is being created or edited — DashboardPreview checks
|
||||
// those first now, so this component no longer needs its own creating/editing branch.
|
||||
const DashboardPreviewEmpty = () => {
|
||||
const { t } = useTranslation();
|
||||
const [, setCreating] = useGlobal<boolean>(CREATING_DASHBOARD_KEY, false);
|
||||
const [, setName] = useGlobal<string>(NEW_DASH_NAME_KEY, '');
|
||||
const [, setDescription] = useGlobal<string>(NEW_DASH_DESC_KEY, '');
|
||||
@@ -488,10 +497,10 @@ const DashboardPreviewEmpty = () => {
|
||||
<div className="h-full w-full">
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-4">
|
||||
<LayoutGrid className="h-10 w-10 text-white/80" />
|
||||
<p className="text-sm text-white/80">Select a dashboard or create a new one</p>
|
||||
<p className="text-sm text-white/80">{t('dashboards.selectOne')}</p>
|
||||
<Button onClick={handleCreate} className="bg-duck-teal hover:bg-duck-teal/90 cursor-pointer">
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
New Dashboard
|
||||
{t('dashboards.new')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"common": {
|
||||
"save": "Save"
|
||||
"save": "Save",
|
||||
"delete": "Delete"
|
||||
},
|
||||
"minesweeper": {
|
||||
"notInstalled": "Minesweeper is not installed on this server.",
|
||||
@@ -136,5 +137,28 @@
|
||||
"hostTerminal": "Host Terminal",
|
||||
"runningShells": "Running Shells",
|
||||
"pm2Processes": "pm2 processes"
|
||||
},
|
||||
"dashboards": {
|
||||
"title": "Dashboards",
|
||||
"new": "New Dashboard",
|
||||
"delete": "Delete dashboard",
|
||||
"confirmDelete": "Are you sure you want to delete",
|
||||
"search": "Search dashboards",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"open": "Open Dashboard",
|
||||
"selectOne": "Select a dashboard or create a new one",
|
||||
"namePlaceholder": "My Dashboard",
|
||||
"descriptionPlaceholder": "What is this dashboard for?",
|
||||
"template": "Template",
|
||||
"create": "Create",
|
||||
"templates": {
|
||||
"single": "Single",
|
||||
"twoColumns": "2 Columns",
|
||||
"mainSide": "Main + Side",
|
||||
"sidebar": "Sidebar",
|
||||
"grid": "2x2 Grid",
|
||||
"colsBottom": "Cols + Bottom"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"common": {
|
||||
"save": "Guardar"
|
||||
"save": "Guardar",
|
||||
"delete": "Eliminar"
|
||||
},
|
||||
"minesweeper": {
|
||||
"notInstalled": "O Minesweeper não está instalado neste servidor.",
|
||||
@@ -136,5 +137,28 @@
|
||||
"hostTerminal": "Terminal do anfitrião",
|
||||
"runningShells": "Shells em execução",
|
||||
"pm2Processes": "processos pm2"
|
||||
},
|
||||
"dashboards": {
|
||||
"title": "Painéis",
|
||||
"new": "Novo painel",
|
||||
"delete": "Eliminar painel",
|
||||
"confirmDelete": "Tem a certeza de que quer eliminar",
|
||||
"search": "Pesquisar painéis",
|
||||
"name": "Nome",
|
||||
"description": "Descrição",
|
||||
"open": "Abrir painel",
|
||||
"selectOne": "Selecione um painel ou crie um novo",
|
||||
"namePlaceholder": "O meu painel",
|
||||
"descriptionPlaceholder": "Para que serve este painel?",
|
||||
"template": "Modelo",
|
||||
"create": "Criar",
|
||||
"templates": {
|
||||
"single": "Único",
|
||||
"twoColumns": "2 colunas",
|
||||
"mainSide": "Principal + lateral",
|
||||
"sidebar": "Barra lateral",
|
||||
"grid": "Grelha 2x2",
|
||||
"colsBottom": "Colunas + inferior"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user