Initial commit: Electron + React touchscreen kiosk dashboard for Home Assistant
This commit is contained in:
209
src/hooks/useTodo.ts
Normal file
209
src/hooks/useTodo.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user