Files
imperial-command-center/src/hooks/useTodo.ts

210 lines
5.6 KiB
TypeScript

import { useCallback, useState, useEffect } from 'react';
import { useEntity } from './useEntity';
import { haConnection } from '@/services/homeAssistant';
import { useSettingsStore } from '@/stores/settingsStore';
export interface TodoItem {
uid: string;
summary: string;
status: 'needs_action' | 'completed';
due?: string;
description?: string;
}
export function useTodo() {
const todoEntityId = useSettingsStore((state) => state.config.todoList);
const entity = useEntity(todoEntityId || '');
const [items, setItems] = useState<TodoItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchItems = useCallback(async () => {
if (!todoEntityId) return;
setIsLoading(true);
setError(null);
try {
const connection = haConnection.getConnection();
if (!connection) {
throw new Error('Not connected to Home Assistant');
}
// Use the todo.get_items service
const result = await connection.sendMessagePromise<Record<string, unknown>>({
type: 'call_service',
domain: 'todo',
service: 'get_items',
target: { entity_id: todoEntityId },
return_response: true,
});
// Extract items from response - handle different structures
let entityItems: TodoItem[] = [];
// Structure 1: { response: { "todo.entity": { items: [...] } } }
const respWrapper = result?.response as Record<string, { items?: TodoItem[] }> | undefined;
if (respWrapper?.[todoEntityId]?.items) {
entityItems = respWrapper[todoEntityId].items;
}
// Structure 2: { "todo.entity": { items: [...] } }
else if ((result?.[todoEntityId] as { items?: TodoItem[] })?.items) {
entityItems = (result[todoEntityId] as { items: TodoItem[] }).items;
}
setItems(entityItems);
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to fetch todo items';
setError(message);
console.error('Failed to fetch todo items:', err);
} finally {
setIsLoading(false);
}
}, [todoEntityId]);
// Fetch items when entity changes and poll every 30 seconds
useEffect(() => {
if (entity) {
fetchItems();
// Poll every 30 seconds for updates
const interval = setInterval(fetchItems, 30000);
return () => clearInterval(interval);
}
}, [entity, fetchItems]);
const addItem = useCallback(async (summary: string) => {
if (!todoEntityId) return;
setIsLoading(true);
setError(null);
try {
await haConnection.callService('todo', 'add_item', { item: summary }, { entity_id: todoEntityId });
// Refresh items
await fetchItems();
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to add todo item';
setError(message);
throw err;
} finally {
setIsLoading(false);
}
}, [todoEntityId, fetchItems]);
const completeItem = useCallback(async (uid: string) => {
if (!todoEntityId) return;
setIsLoading(true);
setError(null);
try {
// Find the item to get its summary
const item = items.find((i) => i.uid === uid);
if (!item) {
throw new Error('Item not found');
}
await haConnection.callService(
'todo',
'update_item',
{ item: item.summary, status: 'completed' },
{ entity_id: todoEntityId }
);
// Refresh items
await fetchItems();
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to complete todo item';
setError(message);
throw err;
} finally {
setIsLoading(false);
}
}, [todoEntityId, items, fetchItems]);
const uncompleteItem = useCallback(async (uid: string) => {
if (!todoEntityId) return;
setIsLoading(true);
setError(null);
try {
const item = items.find((i) => i.uid === uid);
if (!item) {
throw new Error('Item not found');
}
await haConnection.callService(
'todo',
'update_item',
{ item: item.summary, status: 'needs_action' },
{ entity_id: todoEntityId }
);
// Refresh items
await fetchItems();
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to uncomplete todo item';
setError(message);
throw err;
} finally {
setIsLoading(false);
}
}, [todoEntityId, items, fetchItems]);
const removeItem = useCallback(async (uid: string) => {
if (!todoEntityId) return;
setIsLoading(true);
setError(null);
try {
const item = items.find((i) => i.uid === uid);
if (!item) {
throw new Error('Item not found');
}
await haConnection.callService(
'todo',
'remove_item',
{ item: item.summary },
{ entity_id: todoEntityId }
);
// Refresh items
await fetchItems();
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to remove todo item';
setError(message);
throw err;
} finally {
setIsLoading(false);
}
}, [todoEntityId, items, fetchItems]);
const clearError = useCallback(() => setError(null), []);
// Computed values
const activeItems = items.filter((item) => item.status === 'needs_action');
const completedItems = items.filter((item) => item.status === 'completed');
return {
// State
items,
activeItems,
completedItems,
isLoading,
error,
// Actions
fetchItems,
addItem,
completeItem,
uncompleteItem,
removeItem,
clearError,
};
}