mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-09 00:03:10 +00:00
feat: add CRM task search clients
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import type SearchTasks from "@/lib/api/models/app/crm/SearchTasks";
|
||||
import type TasksSearchResult from "@/lib/api/models/app/crm/TasksSearchResult";
|
||||
import Request from "../../../Request";
|
||||
|
||||
export default async function searchTasks(
|
||||
filters: SearchTasks,
|
||||
offset = 0,
|
||||
limit = 50,
|
||||
): Promise<TasksSearchResult> {
|
||||
const qs = new URLSearchParams();
|
||||
if (offset) qs.set("offset", String(offset));
|
||||
if (limit) qs.set("limit", String(limit));
|
||||
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
||||
|
||||
return await Request<TasksSearchResult>({
|
||||
method: "POST",
|
||||
url: `/crm/tasks/search${suffix}`,
|
||||
data: filters,
|
||||
authorization: true,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type SearchTasks from "@/lib/api/models/app/crm/SearchTasks";
|
||||
import type { TasksSummary } from "@/lib/api/models/app/crm/TasksSearchResult";
|
||||
import Request from "../../../Request";
|
||||
|
||||
export default async function tasksSummary(filters: SearchTasks): Promise<TasksSummary> {
|
||||
return await Request<TasksSummary>({
|
||||
method: "POST",
|
||||
url: "/crm/tasks/summary",
|
||||
data: filters,
|
||||
authorization: true,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useInfiniteQuery, type InfiniteData } from "@tanstack/react-query";
|
||||
import type SearchTasks from "@/lib/api/models/app/crm/SearchTasks";
|
||||
import type TasksSearchResult from "@/lib/api/models/app/crm/TasksSearchResult";
|
||||
import searchTasks from "@/lib/api/client/app/crm/tasks/searchTasks";
|
||||
|
||||
interface UseSearchTasksProps {
|
||||
filters: SearchTasks;
|
||||
limit?: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
// Server-driven tasks fetch that scales to thousands of rows. Offset-paginated
|
||||
// (the backend uses offset rather than a keyset cursor so nullable due-date
|
||||
// sorts don't drop rows), so the page param is the next offset. Pages flatten
|
||||
// into a single `tasks` list and `total` comes straight off the server so the
|
||||
// UI can show "N of M loaded".
|
||||
export default function useSearchTasks({ filters, limit = 50, enabled = true }: UseSearchTasksProps) {
|
||||
const queryResult = useInfiniteQuery<
|
||||
TasksSearchResult,
|
||||
Error,
|
||||
InfiniteData<TasksSearchResult, number>,
|
||||
[string, string, string, SearchTasks, number],
|
||||
number
|
||||
>({
|
||||
queryKey: ["crm", "tasks", "search", filters, limit],
|
||||
queryFn: async ({ pageParam }) => searchTasks(filters, pageParam, limit),
|
||||
initialPageParam: 0,
|
||||
getNextPageParam: (lastPage) =>
|
||||
lastPage.pagination.has_more ? (lastPage.pagination.next_offset ?? undefined) : undefined,
|
||||
staleTime: 30_000,
|
||||
enabled,
|
||||
});
|
||||
|
||||
const tasks = queryResult.data?.pages
|
||||
.flatMap((p) => p.data ?? [])
|
||||
.filter((t): t is NonNullable<typeof t> => t != null);
|
||||
|
||||
const total = queryResult.data?.pages[0]?.pagination.total ?? 0;
|
||||
|
||||
return { ...queryResult, tasks, total };
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type SearchTasks from "@/lib/api/models/app/crm/SearchTasks";
|
||||
import tasksSummary from "@/lib/api/client/app/crm/tasks/tasksSummary";
|
||||
|
||||
// Server-aggregated totals for the same filter the table renders. Kept as a
|
||||
// separate query (not folded into the list) so the header stats stay correct
|
||||
// over the whole set while the rows page in.
|
||||
export default function useTasksSummary(filters: SearchTasks, enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ["crm", "tasks", "summary", filters],
|
||||
queryFn: () => tasksSummary(filters),
|
||||
staleTime: 30_000,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Filter body for POST /crm/tasks/search and /crm/tasks/summary. Every field
|
||||
// is optional; an empty body matches every task in the org (the "All tasks"
|
||||
// default). The same body drives the rows and the summary totals, so a header
|
||||
// number always reflects the exact filter shown below it.
|
||||
|
||||
import type { CRMTaskPriority, CRMTaskStatus } from "@/lib/api/models/app/crm/CRMTask";
|
||||
|
||||
export type TaskSortBy =
|
||||
| "created_at"
|
||||
| "updated_at"
|
||||
| "due_date"
|
||||
| "priority"
|
||||
| "title";
|
||||
|
||||
export default interface SearchTasks {
|
||||
query: string;
|
||||
statuses: CRMTaskStatus[];
|
||||
priorities: CRMTaskPriority[];
|
||||
// Task type NAMEs (crm_tasks.type), e.g. ["Call", "Email"].
|
||||
types: string[];
|
||||
// User UUIDs (assigned_to). String-matched on the server.
|
||||
assigned_to: string[];
|
||||
contact_id?: string;
|
||||
deal_id?: string;
|
||||
due_after?: string;
|
||||
due_before?: string;
|
||||
// due_date < now() AND status NOT IN ('completed','cancelled').
|
||||
overdue?: boolean;
|
||||
sort_by: TaskSortBy;
|
||||
// false => DESC (default), true => ASC.
|
||||
reverse: boolean;
|
||||
}
|
||||
|
||||
export const EMPTY_TASK_SEARCH: SearchTasks = {
|
||||
query: "",
|
||||
statuses: [],
|
||||
priorities: [],
|
||||
types: [],
|
||||
assigned_to: [],
|
||||
sort_by: "created_at",
|
||||
reverse: false,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import type CRMTask from "@/lib/api/models/app/crm/CRMTask";
|
||||
|
||||
export interface TasksSearchPagination {
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
has_more: boolean;
|
||||
next_offset?: number | null;
|
||||
}
|
||||
|
||||
export default interface TasksSearchResult {
|
||||
data: CRMTask[];
|
||||
pagination: TasksSearchPagination;
|
||||
}
|
||||
|
||||
// Server-aggregated totals over a SearchTasks filter. Every number here is a
|
||||
// COUNT over the whole matching set — never a client reduce over a loaded
|
||||
// page — so the header stats stay honest at scale.
|
||||
export interface TasksSummary {
|
||||
total: number;
|
||||
pending_count: number;
|
||||
in_progress_count: number;
|
||||
completed_count: number;
|
||||
cancelled_count: number;
|
||||
// due_date < now() AND status NOT IN ('completed','cancelled').
|
||||
overdue_count: number;
|
||||
// priority IN ('high','urgent').
|
||||
high_priority_count: number;
|
||||
}
|
||||
Reference in New Issue
Block a user