dashboard widgets

This commit is contained in:
JiriUhlir
2026-08-03 13:10:41 +02:00
parent 2a3d75c85e
commit bbc2236c0d
14 changed files with 1196 additions and 141 deletions
+90
View File
@@ -23,7 +23,15 @@ import {
findOperation,
providedFieldsFor,
} from '../data/connectors.js';
import {
getLayout,
hasCustomLayout,
resetLayout,
saveLayout,
validateLayout,
} from '../data/dashboardLayouts.js';
import { collectScopes } from '../data/flowScope.js';
import { widgets } from '../data/widgets.js';
import { listIncidents } from '../data/incidentStore.js';
import { getSummary } from '../data/mock.js';
import { findPersonByEmail, listPeople } from '../data/people.js';
@@ -78,6 +86,88 @@ dashboardRouter.get('/incidents', (_req, res) => {
res.json({ items: listIncidents() });
});
// ------------------------------------------------------- rozlozeni dashboardu
dashboardRouter.get('/widgets', (_req, res) => {
res.json({ items: widgets });
});
/**
* Rozlozeni je vzdy za konkretni firmu, i kdyz uzivatel kouka na pohled "vse".
* Jinak by clovek ve dvou firmach nemel kam ulozit dve ruzna nastaveni.
*/
function layoutTenant(req: Request, res: Response): string | null {
const access = accessFor(req.user!);
const requested = typeof req.query.tenantId === 'string' ? req.query.tenantId : undefined;
const tenantId = requested ?? access.defaultTenantId;
if (!tenantId) {
res.status(403).json({ error: 'no_tenant', message: 'Účet nepatří do žádné firmy.' });
return null;
}
if (!access.tenants.some((tenant) => tenant.id === tenantId)) {
console.warn(`[layout] ${req.user!.email}: pokus o firmu ${tenantId} bez clenstvi`);
res.status(404).json({ error: 'not_found', message: 'Firma neexistuje, nebo do ní nepatříte.' });
return null;
}
return tenantId;
}
dashboardRouter.get('/layout', (req, res) => {
const tenantId = layoutTenant(req, res);
if (!tenantId) return;
return res.json({
tenantId,
items: getLayout(req.user!.id, tenantId),
custom: hasCustomLayout(req.user!.id, tenantId),
});
});
const layoutSchema = z.object({
items: z.array(
z.object({
id: z.string().min(1),
widgetId: z.string().min(1),
size: z.enum(['third', 'half', 'full']),
}),
),
});
dashboardRouter.put('/layout', (req, res) => {
const tenantId = layoutTenant(req, res);
if (!tenantId) return;
const parsed = layoutSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({
error: 'validation_error',
message: parsed.error.issues[0]?.message ?? 'Neplatné rozložení.',
});
}
const problems = validateLayout(parsed.data.items);
if (problems.length > 0) {
console.warn(`[layout] ${req.user!.email}: neplatne rozlozeni - ${problems.join(' ')}`);
return res.status(400).json({ error: 'validation_error', message: problems[0] });
}
const items = saveLayout(req.user!.id, tenantId, parsed.data.items);
return res.json({ tenantId, items, custom: true });
});
/** Vraceni na vychozi. Zamerne DELETE - je to smazani ulozeneho nastaveni. */
dashboardRouter.delete('/layout', (req, res) => {
const tenantId = layoutTenant(req, res);
if (!tenantId) return;
return res.json({
tenantId,
items: resetLayout(req.user!.id, tenantId),
custom: false,
});
});
// ------------------------------------------------------------------- tickety
/** Resitele vybrane firmy. Klient je potrebuje do nabidky prirazeni i do prehledu. */