d99e158a8c
- Create API client with auto-retry and token refresh support - Add comprehensive API endpoints for tasks, tags, projects, sync, and auth - Implement authStore for authentication state management - Implement tasksStore with optimistic updates and offline queue - Add derived stores for filtered task views (pending, completed, by project) - Implement syncStore for managing sync state and queue - Add client ID generation and persistence for sync tracking
163 lines
3.3 KiB
JavaScript
163 lines
3.3 KiB
JavaScript
import { writable, derived } from 'svelte/store';
|
|
import { tasks as tasksAPI } from '$lib/api/endpoints.js';
|
|
import { queueChange } from '$lib/utils/sync-queue.js';
|
|
|
|
/**
|
|
* @typedef {import('$lib/api/types.js').Task} Task
|
|
* @typedef {import('$lib/api/types.js').TaskFilters} TaskFilters
|
|
*/
|
|
|
|
/**
|
|
* Create tasks store
|
|
*/
|
|
function createTasksStore() {
|
|
const { subscribe, set, update } = writable(/** @type {Task[]} */ ([]));
|
|
|
|
return {
|
|
subscribe,
|
|
|
|
/**
|
|
* Load all tasks from API
|
|
* @param {TaskFilters} [filters]
|
|
*/
|
|
async load(filters = {}) {
|
|
try {
|
|
const tasks = await tasksAPI.list(filters);
|
|
set(tasks);
|
|
} catch (error) {
|
|
console.error('Failed to load tasks:', error);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add new task (optimistic update)
|
|
* @param {Partial<Task>} task
|
|
*/
|
|
async add(task) {
|
|
try {
|
|
const created = await tasksAPI.create(task);
|
|
update(tasks => [...tasks, created]);
|
|
return created;
|
|
} catch (error) {
|
|
// Queue for offline sync
|
|
queueChange({
|
|
type: 'create',
|
|
task_uuid: task.uuid,
|
|
data: task
|
|
});
|
|
|
|
// Still update UI optimistically
|
|
update(tasks => [...tasks, task]);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Update task (optimistic update)
|
|
* @param {string} uuid
|
|
* @param {Partial<Task>} updates
|
|
*/
|
|
async updateTask(uuid, updates) {
|
|
// Optimistic update
|
|
update(tasks => {
|
|
const index = tasks.findIndex(t => t.uuid === uuid);
|
|
if (index >= 0) {
|
|
tasks[index] = { ...tasks[index], ...updates, modified: Date.now() / 1000 };
|
|
}
|
|
return tasks;
|
|
});
|
|
|
|
try {
|
|
const updated = await tasksAPI.update(uuid, updates);
|
|
// Sync with server response
|
|
update(tasks => {
|
|
const index = tasks.findIndex(t => t.uuid === uuid);
|
|
if (index >= 0) {
|
|
tasks[index] = updated;
|
|
}
|
|
return tasks;
|
|
});
|
|
} catch (error) {
|
|
// Queue for offline sync
|
|
queueChange({
|
|
type: 'update',
|
|
task_uuid: uuid,
|
|
data: updates
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Delete task
|
|
* @param {string} uuid
|
|
*/
|
|
async deleteTask(uuid) {
|
|
// Optimistic removal
|
|
update(tasks => tasks.filter(t => t.uuid !== uuid));
|
|
|
|
try {
|
|
await tasksAPI.delete(uuid);
|
|
} catch (error) {
|
|
queueChange({
|
|
type: 'delete',
|
|
task_uuid: uuid,
|
|
data: {}
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Complete task
|
|
* @param {string} uuid
|
|
*/
|
|
async complete(uuid) {
|
|
try {
|
|
const completed = await tasksAPI.complete(uuid);
|
|
update(tasks => {
|
|
const index = tasks.findIndex(t => t.uuid === uuid);
|
|
if (index >= 0) {
|
|
tasks[index] = completed;
|
|
}
|
|
return tasks;
|
|
});
|
|
} catch (error) {
|
|
queueChange({
|
|
type: 'update',
|
|
task_uuid: uuid,
|
|
data: { status: 'C', end: Date.now() / 1000 }
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
export const tasksStore = createTasksStore();
|
|
|
|
// Derived stores for filtered views
|
|
export const pendingTasks = derived(
|
|
tasksStore,
|
|
$tasks => $tasks.filter(t => t.status === 'P')
|
|
);
|
|
|
|
export const completedTasks = derived(
|
|
tasksStore,
|
|
$tasks => $tasks.filter(t => t.status === 'C')
|
|
);
|
|
|
|
export const tasksByProject = derived(
|
|
tasksStore,
|
|
$tasks => {
|
|
const grouped = {};
|
|
$tasks.forEach(task => {
|
|
const project = task.project || 'No Project';
|
|
if (!grouped[project]) grouped[project] = [];
|
|
grouped[project].push(task);
|
|
});
|
|
return grouped;
|
|
}
|
|
);
|