210 lines
5.6 KiB
TypeScript
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,
|
|
};
|
|
}
|