import { useEffect, useMemo, useState, useDeferredValue } from "react"; import { View, Text, ScrollView, GestureResponderEvent } from "react-native"; import { useAuthContext } from "@/auth/AuthContext"; import { Todo } from "@/store/models/Todo.model"; import { LoadingSpinner } from "@/components/ui/LoadingSpinner"; import { NoData } from "@/components/ui/NoData"; import { useCreateTodoMutation, useDeleteTodoMutation, useGetTodosQuery, useUpdateTodoMutation, } from "@/store/api/todosApi"; import { TableSortDirection } from "@/constants/tableSortDirectionEnum"; import { AdminTodosTableControls } from "@/components/features/todos/AdminTodosTableControls"; import { TodoStatus } from "@/constants/todoStatusEnum"; import { TablePagination } from "@/components/shared/TablePagination"; import { useAppSelector } from "@/store/hooks"; import { selectIsMobile } from "@/store/screen/screenSizeSlice"; import { MobileAdminTodosTable } from "@/components/features/todos/MobileAdminTodosTable"; import { DesktopAdminTodosTable } from "@/components/features/todos/DesktopAdminTodosTable"; import { PriorityStatus } from "@/constants/priorityStatusEnum"; import { TodoModal } from "@/components/features/todos/TodoModal"; import { DeleteTodoModal } from "@/components/features/todos/DeleteTodoModal"; import { OperationMode } from "@/constants/operationModeEnum"; import { AppSnackbar } from "@/components/ui/AppSnackbar"; interface CreateTodoPayload { title: string; task: string; status: TodoStatus; priority: PriorityStatus; dueDate: Date; createdById: string; assigneeIds: string[]; } export const AdminTodosTable = ({ initialSearchQuery = "" }: { initialSearchQuery?: string }) => { const { user } = useAuthContext(); const [page, setPage] = useState(0); const [itemsPerPage, setItemsPerPage] = useState(25); const [searchQuery, setSearchQuery] = useState(""); const [selectedStatuses, setSelectedStatuses] = useState([]); const [selectedPriorities, setSelectedPriorities] = useState< PriorityStatus[] >([]); const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState( TableSortDirection.ASCENDING ); const deferredSearch = useDeferredValue(searchQuery); const [debouncedSearch, setDebouncedSearch] = useState(""); useEffect(() => { const t = setTimeout(() => setDebouncedSearch(deferredSearch.trim()), 450); return () => clearTimeout(t); }, [deferredSearch]); // Initialize search from route param if provided useEffect(() => { const q = (initialSearchQuery || "").trim(); if (q && q !== searchQuery) { setSearchQuery(q); setPage(0); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialSearchQuery]); const queryArgs = useMemo(() => { const allowedSorts = [ "title", "dueDate", "status", "priority", "createdAt", ] as const; const sortBy = (allowedSorts as readonly string[]).includes( sortColumn || "" ) ? (sortColumn as (typeof allowedSorts)[number]) : undefined; const sortDir = sortDirection; const searchParam = debouncedSearch && debouncedSearch.length >= 2 ? debouncedSearch : undefined; return { page: page + 1, limit: itemsPerPage, search: searchParam, statuses: selectedStatuses.length ? selectedStatuses : undefined, priorities: selectedPriorities.length ? selectedPriorities : undefined, sortBy, sortDir, }; }, [ page, itemsPerPage, debouncedSearch, selectedStatuses, selectedPriorities, sortColumn, sortDirection, ]); const { data: todosPage, isLoading, isFetching, error, refetch, } = useGetTodosQuery(queryArgs); const todos = todosPage?.items ?? []; const totalItems = todosPage?.total ?? 0; const [createTodo, { isLoading: isCreatingTodo }] = useCreateTodoMutation(); const [updateTodo, { isLoading: isUpdatingTodo }] = useUpdateTodoMutation(); const [deleteTodo, { isLoading: isDeletingTodo }] = useDeleteTodoMutation(); const [menuVisible, setMenuVisible] = useState(false); const [menuAnchor, setMenuAnchor] = useState<{ x: number; y: number }>({ x: 0, y: 0, }); const [addTodoModalVisible, setAddTodoModalVisible] = useState(false); const [editTodoModalVisible, setEditTodoModalVisible] = useState(false); const [deleteTodoModalVisible, setDeleteTodoModalVisible] = useState(false); const [currentTodo, setCurrentTodo] = useState(null); const [snackbarVisible, setSnackbarVisible] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(""); const showSnackbar = (message: string) => { setSnackbarMessage(message); setSnackbarVisible(true); }; const handleCreateTodo = async ( todo: Partial & { assigneeIds?: string[]; userId?: string } ) => { try { const unique = (arr: string[]) => Array.from(new Set(arr.filter(Boolean))); const payload: CreateTodoPayload = { title: todo.title || "", task: todo.task || "", status: todo.status || TodoStatus.NEW, priority: todo.priority || PriorityStatus.LOW_PRIORITY, dueDate: todo.dueDate || new Date(), createdById: user?.id || "", assigneeIds: unique([...(todo.assigneeIds || []), ...(todo.userId ? [todo.userId] : [])]), }; await createTodo(payload).unwrap(); showSnackbar("Todo created successfully"); setAddTodoModalVisible(false); refetch(); } catch { showSnackbar("Error creating todo"); } }; const handleUpdateTodo = async ( todo: Partial & { assigneeIds?: string[]; userId?: string } ) => { try { const body: any = { ...(todo.id ? { id: todo.id } : {}), ...(todo.title ? { title: todo.title } : {}), ...(todo.task ? { task: todo.task } : {}), ...(todo.status ? { status: todo.status } : {}), ...(todo.priority ? { priority: todo.priority } : {}), ...(todo.dueDate ? { dueDate: todo.dueDate } : {}), }; const combinedAssignees = Array.from( new Set([...(todo.assigneeIds || []), ...(todo.userId ? [todo.userId] : [])]) ); if (combinedAssignees.length) { body.assigneeIds = combinedAssignees; } await updateTodo(body).unwrap(); showSnackbar("Todo updated successfully"); setEditTodoModalVisible(false); refetch(); } catch { showSnackbar("Error updating todo"); } }; const handleDeleteTodo = async (todo: Todo) => { try { await deleteTodo(todo.id).unwrap(); refetch(); showSnackbar(`${todo.title} has been deleted successfully`); setDeleteTodoModalVisible(false); } catch { showSnackbar("Error deleting todo"); } }; const handleSort = (column: string) => { setSortColumn(column); setSortDirection( sortDirection === TableSortDirection.ASCENDING ? TableSortDirection.DESCENDING : TableSortDirection.ASCENDING ); setPage(0); }; const handleItemsPerPageChange = (value: number) => { setItemsPerPage(value); }; const handleSearch = (query: string) => { setSearchQuery(query); setPage(0); }; const handleEdit = (todo: Todo) => { setCurrentTodo(todo); setEditTodoModalVisible(true); }; const openDeleteModal = (todo: Todo) => { setCurrentTodo(todo); setDeleteTodoModalVisible(true); }; const isMobile = useAppSelector(selectIsMobile); const from = todos.length ? page * itemsPerPage + 1 : 0; const to = todos.length ? Math.min(from + todos.length - 1, totalItems) : 0; useEffect(() => { setPage(0); }, [itemsPerPage]); return ( <> Todos manager {isLoading ? ( Loading todos... ) : error ? ( ) : ( setMenuVisible(false)} onItemsPerPageChange={handleItemsPerPageChange} onSelectStatuses={setSelectedStatuses} onSelectPriorities={setSelectedPriorities} onAddTodo={() => setAddTodoModalVisible(true)} itemsPerPage={itemsPerPage} /> setAddTodoModalVisible(false)} onSubmit={handleCreateTodo} isLoading={isCreatingTodo} mode={OperationMode.ADD} /> setEditTodoModalVisible(false)} onSubmit={handleUpdateTodo} isLoading={isUpdatingTodo} mode={OperationMode.EDIT} /> setDeleteTodoModalVisible(false)} onDelete={handleDeleteTodo} isLoading={isDeletingTodo} /> {searchQuery && todos.length === 0 ? ( No todos match your search for "{searchQuery}" ) : searchQuery.length === 0 && todos.length === 0 ? ( No todos found ) : isMobile ? ( ) : ( )} { const { pageX, pageY } = event.nativeEvent; setMenuAnchor({ x: pageX, y: pageY }); setMenuVisible(true); }} /> )} setSnackbarVisible(false)} action={{ label: "Close", onPress: () => setSnackbarVisible(false) }} > {snackbarMessage} ); };