Dagsplan: flytt/navn/deling + plan-badge, kategorier, feltmodus
Dagsplan kan navngis og flyttes til annen dato (slår sammen uten datatap).
Delbar som hovedregel (shared-flagg default true) med toggle for privat;
andres delte planer kan ses skrivebeskyttet via "Plan for"-velger.
getPlanFor håndhever tilgang. Per-oppgave plan-badge (dag + person) via
planIndex filtrert per viewer.
Egendefinerte kategorier (categories.json) i tillegg til de 12 innebygde;
dynamisk validering, deles via WS, addes fra kategori-nedtrekket.
Feltmodus (body.field-mode): større berøringsmål for mobil, auto på <640px.
plans.json migreres array -> {name,items,shared} ved oppstart (bakoverkompat).
26 integrasjonstester (6 nye), alle grønne. README + CLAUDE.md oppdatert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
be166034de
commit
bdeb6254b9
8 changed files with 724 additions and 147 deletions
|
|
@ -93,15 +93,6 @@ function createServer({ dataDir = path.join(__dirname, 'data') } = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
// Send kun til samme brukers tilkoblinger (dagsplan er privat, men skal synke
|
||||
// mellom f.eks. desktop og laptop hos samme bruker).
|
||||
function sendToUser(user, obj) {
|
||||
const msg = JSON.stringify(obj);
|
||||
for (const [ws, meta] of clients) {
|
||||
if (meta.user === user && ws.readyState === ws.OPEN) ws.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function onlineUsers() {
|
||||
const set = new Set();
|
||||
for (const meta of clients.values()) set.add(meta.user);
|
||||
|
|
@ -171,6 +162,7 @@ function createServer({ dataDir = path.join(__dirname, 'data') } = {}) {
|
|||
app.get('/api/state', (req, res) => {
|
||||
store.rememberUser(req.user);
|
||||
const today = todayStr();
|
||||
const plan = store.getPlan(req.user, today);
|
||||
res.json({
|
||||
user: req.user,
|
||||
canSetName: COOKIE_AUTH_ENABLED,
|
||||
|
|
@ -178,20 +170,66 @@ function createServer({ dataDir = path.join(__dirname, 'data') } = {}) {
|
|||
activity: store.activity,
|
||||
known_users: [...store.knownUsers].sort(),
|
||||
online: onlineUsers(),
|
||||
plan: { date: today, items: store.getPlan(req.user, today) },
|
||||
categories: store.categories(),
|
||||
plan: { date: today, user: req.user, name: plan.name, items: plan.items, shared: plan.shared },
|
||||
planIndex: store.planIndex(req.user),
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/plan', (req, res) => {
|
||||
const date = validDate(req.query.date) ? req.query.date : todayStr();
|
||||
res.json({ date, items: store.getPlan(req.user, date) });
|
||||
const owner = typeof req.query.user === 'string' && req.query.user ? sanitizeUser(req.query.user) : req.user;
|
||||
const plan = store.getPlanFor(req.user, owner, date);
|
||||
if (!plan) return res.status(403).json({ error: 'Planen er privat' });
|
||||
res.json({ date, user: owner, name: plan.name, items: plan.items, shared: plan.shared, readonly: owner !== req.user });
|
||||
});
|
||||
|
||||
// Plan-endringer påvirker per-oppgave-badges hos andre. Delte planer
|
||||
// broadcastes til alle; private sendes kun til eierens egne faner (andre får
|
||||
// en tom melding så de fjerner planen fra sin badge-indeks).
|
||||
function broadcastPlan(owner, date) {
|
||||
const plan = store.getPlan(owner, date);
|
||||
const ownerMsg = JSON.stringify({ type: 'plan', user: owner, date, name: plan.name, items: plan.items, shared: plan.shared });
|
||||
const othersMsg = plan.shared
|
||||
? ownerMsg
|
||||
: JSON.stringify({ type: 'plan', user: owner, date, name: '', items: [], shared: false });
|
||||
for (const [ws, meta] of clients) {
|
||||
if (ws.readyState !== ws.OPEN) continue;
|
||||
ws.send(meta.user === owner ? ownerMsg : othersMsg);
|
||||
}
|
||||
}
|
||||
|
||||
app.put('/api/plan', (req, res) => {
|
||||
const date = validDate(req.body && req.body.date) ? req.body.date : todayStr();
|
||||
const items = store.setPlan(req.user, date, req.body && req.body.items);
|
||||
sendToUser(req.user, { type: 'plan', date, items });
|
||||
res.json({ date, items });
|
||||
const name = req.body && typeof req.body.name === 'string' ? req.body.name : undefined;
|
||||
const shared = req.body && typeof req.body.shared === 'boolean' ? req.body.shared : undefined;
|
||||
const plan = store.setPlan(req.user, date, req.body && req.body.items, name, shared);
|
||||
broadcastPlan(req.user, date);
|
||||
res.json({ date, user: req.user, name: plan.name, items: plan.items, shared: plan.shared });
|
||||
});
|
||||
|
||||
app.put('/api/plan/share', (req, res) => {
|
||||
const date = validDate(req.body && req.body.date) ? req.body.date : todayStr();
|
||||
const shared = !!(req.body && req.body.shared);
|
||||
const plan = store.setPlanShared(req.user, date, shared);
|
||||
broadcastPlan(req.user, date);
|
||||
res.json({ date, user: req.user, name: plan.name, items: plan.items, shared: plan.shared });
|
||||
});
|
||||
|
||||
app.put('/api/plan/move', (req, res) => {
|
||||
const from = validDate(req.body && req.body.from) ? req.body.from : null;
|
||||
const to = validDate(req.body && req.body.to) ? req.body.to : null;
|
||||
if (!from || !to) return res.status(400).json({ error: 'Ugyldig dato (from/to må være YYYY-MM-DD)' });
|
||||
const plan = store.movePlan(req.user, from, to);
|
||||
broadcastPlan(req.user, from); // kildedatoen er nå tom
|
||||
broadcastPlan(req.user, to); // måldatoen har planen
|
||||
res.json({ date: to, user: req.user, name: plan.name, items: plan.items, shared: plan.shared });
|
||||
});
|
||||
|
||||
app.post('/api/categories', (req, res) => {
|
||||
const cat = store.addCategory({ name: req.body && req.body.name, color: req.body && req.body.color }, req.user);
|
||||
broadcast({ type: 'categories', categories: store.categories(), by: req.user });
|
||||
res.status(201).json({ category: cat });
|
||||
});
|
||||
|
||||
app.post('/api/login', (req, res) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue