Files

96 lines
6.2 KiB
JavaScript

/* Shared record writes for forms and the Life OS terminal. */
(() => {
const board = () => {
const current = sys().tasksBoard;
return current && Array.isArray(current.goals) && Array.isArray(current.tasks) ? current : { goals: [], tasks: [] };
};
const validDate = value => !value || (/^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(`${value}T12:00:00`)) && new Date(`${value}T12:00:00`).getFullYear() === Number(value.slice(0, 4)) && new Date(`${value}T12:00:00`).getMonth() + 1 === Number(value.slice(5, 7)) && new Date(`${value}T12:00:00`).getDate() === Number(value.slice(8)));
const required = (value, max) => {
const result = String(value || '').trim();
if (!result || result.length > max) throw new Error('invalidTitle');
return result;
};
const persistBoard = data => { const state = sys(); state.tasksBoard = data; save('lifeos_system', state); renderAll(); };
window.LifeOSActions = {
board,
createGoal({ title, description = '', targetDate = '', showOnDesktop = false }) {
title = required(title, 100);
if (!validDate(targetDate)) throw new Error('invalidDate');
const goal = { id: `goal_${crypto.randomUUID()}`, title, description: String(description).trim().slice(0, 240), targetDate, showOnDesktop: Boolean(targetDate && showOnDesktop), done: false, createdAt: new Date().toISOString() };
const data = board(); data.goals.unshift(goal); persistBoard(data); return goal;
},
updateGoal(id, { title, description = '', targetDate = '', showOnDesktop = false }) {
title = required(title, 100);
if (!validDate(targetDate)) throw new Error('invalidDate');
const data = board(), goal = data.goals.find(item => item.id === id);
if (!goal) throw new Error('notFound');
Object.assign(goal, { title, description: String(description).trim().slice(0, 240), targetDate, showOnDesktop: Boolean(targetDate && showOnDesktop), updatedAt: new Date().toISOString() });
persistBoard(data); return goal;
},
createTask({ title, dueDate = '', goalId = '', priority = 'normal' }) {
title = required(title, 140);
if (!validDate(dueDate)) throw new Error('invalidDate');
if (!['normal', 'high', 'low'].includes(priority)) throw new Error('invalidPriority');
const data = board();
if (goalId && !data.goals.some(goal => goal.id === goalId)) throw new Error('notFound');
const task = { id: `task_${crypto.randomUUID()}`, title, dueDate, goalId, priority, done: false, createdAt: new Date().toISOString() };
data.tasks.push(task); persistBoard(data); return task;
},
updateTask(id, { title, dueDate = '', goalId = '', priority = 'normal' }) {
title = required(title, 140);
if (!validDate(dueDate)) throw new Error('invalidDate');
if (!['normal', 'high', 'low'].includes(priority)) throw new Error('invalidPriority');
const data = board(), task = data.tasks.find(item => item.id === id);
if (!task || (goalId && !data.goals.some(goal => goal.id === goalId))) throw new Error('notFound');
Object.assign(task, { title, dueDate, goalId, priority, updatedAt: new Date().toISOString() });
persistBoard(data); return task;
},
setTaskDone(id, done) {
const data = board(), task = data.tasks.find(item => item.id === id);
if (!task) throw new Error('notFound');
task.done = done; task.completedAt = done ? new Date().toISOString() : null;
persistBoard(data); return task;
},
setGoalDone(id, done) {
const data = board(), goal = data.goals.find(item => item.id === id);
if (!goal) throw new Error('notFound');
goal.done = done; goal.completedAt = done ? new Date().toISOString() : null;
persistBoard(data); return goal;
},
deleteTask(id) {
const data = board(), task = data.tasks.find(item => item.id === id);
if (!task) throw new Error('notFound');
data.tasks = data.tasks.filter(item => item.id !== id);
const all = mems();
if (all.some(item => item.taskId === id)) save('lifeos_memories', all.map(item => item.taskId === id ? { ...item, taskId: '', updatedAt: new Date().toISOString() } : item));
persistBoard(data); return task;
},
deleteGoal(id) {
const data = board(), goal = data.goals.find(item => item.id === id);
if (!goal) throw new Error('notFound');
data.goals = data.goals.filter(item => item.id !== id);
data.tasks.forEach(task => { if (task.goalId === id) task.goalId = ''; });
persistBoard(data); return goal;
},
createMemory({ date = day(), title, text = '', mood = 'cloudy', energy = 3, tags = [], location = '', taskId = '', replace = false }) {
title = String(title || '').trim();
if (title.length > 120) throw new Error('invalidTitle');
if (!validDate(date) || !date) throw new Error('invalidDate');
if (!['clear', 'light', 'cloudy', 'rain', 'storm'].includes(mood) || !Number.isInteger(Number(energy)) || Number(energy) < 1 || Number(energy) > 5) throw new Error('invalidValue');
if (taskId && !board().tasks.some(task => task.id === taskId)) throw new Error('notFound');
const all = mems(), old = all.find(item => item.date === date);
if (old && !replace) throw new Error('memoryExists');
const film = filmFor(mood, Number(energy)), now = new Date().toISOString();
const memory = { ...old, id: old?.id || `memory_${crypto.randomUUID()}`, date, title, text: String(text).trim(), mood, energy: Number(energy), tags: [...new Set(tags.map(tag => String(tag).trim()).filter(Boolean))].slice(0, 12), location: String(location).trim().slice(0, 100), taskId, filmTitle: film.filmTitle, filmSubtitle: film.filmSubtitle, filmLine: film.filmLine, createdAt: old?.createdAt || now, updatedAt: now };
save('lifeos_memories', [...all.filter(item => item.date !== date), memory].sort((a, b) => b.date.localeCompare(a.date))); renderAll(); return memory;
},
deleteMemory(id) {
const all = mems(), memory = all.find(item => item.id === id);
if (!memory) throw new Error('notFound');
save('lifeos_memories', all.filter(item => item.id !== id));
save('lifeos_museum', museum().filter(item => item.memoryId !== id));
renderAll(); return memory;
}
};
})();