feat(improvement): add pagination to endpoints and pages + sync navbar when data changes
This commit is contained in:
@@ -1,30 +1,49 @@
|
||||
import { API_BASE_URL } from "../config/constants";
|
||||
import { API_BASE_URL } from "../config/constants"
|
||||
|
||||
export const authFetch = async (endpoint: string, options: RequestInit = {}) => {
|
||||
const token = localStorage.getItem("accessToken");
|
||||
const isFormData = options.body instanceof FormData;
|
||||
const token = localStorage.getItem("accessToken")
|
||||
const isFormData = options.body instanceof FormData
|
||||
|
||||
const headers: HeadersInit = {
|
||||
...(!isFormData && { "Content-Type": "application/json" }),
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...options.headers,
|
||||
};
|
||||
}
|
||||
|
||||
// Safely join URLs preventing double slashes (e.g., "http://api.com//api/..." -> "http://api.com/api/...")
|
||||
const cleanBaseUrl = API_BASE_URL.replace(/\/+$/, "");
|
||||
const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
||||
const url = `${cleanBaseUrl}${cleanEndpoint}`;
|
||||
const cleanBaseUrl = API_BASE_URL.replace(/\/+$/, "")
|
||||
const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`
|
||||
const url = `${cleanBaseUrl}${cleanEndpoint}`
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
})
|
||||
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem("accessToken");
|
||||
localStorage.removeItem("refreshToken");
|
||||
window.location.href = "/auth";
|
||||
localStorage.removeItem("accessToken")
|
||||
localStorage.removeItem("refreshToken")
|
||||
window.location.href = "/auth"
|
||||
return response
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
const originalJson = response.json.bind(response)
|
||||
response.json = async () => {
|
||||
const data = await originalJson()
|
||||
|
||||
if (data && typeof data === "object" && "items" in data && "pages_count" in data) {
|
||||
return {
|
||||
count: data.total_items || 0,
|
||||
results: data.items || [],
|
||||
_meta: {
|
||||
pages_count: data.pages_count,
|
||||
items_per_page: data.items_per_page,
|
||||
current_page: data.current_page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { authFetch } from "./client";
|
||||
import { type PaginatedClientList } from "../types/client";
|
||||
|
||||
|
||||
export const getClients = async (workspaceId: string, search: string = "", ordering: string = "") => {
|
||||
const queryParams = new URLSearchParams({ workspace: workspaceId });
|
||||
export const getClients = async (
|
||||
workspaceId: string,
|
||||
search: string = "",
|
||||
ordering: string = "",
|
||||
limit: number = 10,
|
||||
offset: number = 0
|
||||
) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
workspace: workspaceId,
|
||||
limit: limit.toString(),
|
||||
offset: offset.toString()
|
||||
});
|
||||
|
||||
if (search) queryParams.append("search", search);
|
||||
if (ordering) queryParams.append("ordering", ordering);
|
||||
@@ -12,7 +20,7 @@ export const getClients = async (workspaceId: string, search: string = "", order
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch clients");
|
||||
}
|
||||
return response.json();
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const createClient = async (workspaceId: string, data: { name: string; notes: string }) => {
|
||||
@@ -54,7 +62,6 @@ export const deleteClient = async (id: string) => {
|
||||
throw new Error(errorData?.detail || errorData?.message || "Failed to delete client");
|
||||
}
|
||||
|
||||
// DELETE requests often return 204 No Content, which throws an error on .json()
|
||||
if (response.status === 204) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/api/workspaces.ts
|
||||
import { authFetch } from "./client";
|
||||
|
||||
export interface Workspace {
|
||||
@@ -10,7 +9,31 @@ export interface Workspace {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const fetchWorkspaces = async (params?: Record<string, string>): Promise<Workspace[]> => {
|
||||
export interface PaginatedResponse<T> {
|
||||
count: number;
|
||||
next: string | null;
|
||||
previous: string | null;
|
||||
results: T[];
|
||||
}
|
||||
|
||||
export interface WorkspaceMembership {
|
||||
id: string;
|
||||
workspace: string;
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
role: 'owner' | 'admin' | 'member' | 'guest';
|
||||
is_active: boolean;
|
||||
joined_at?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
export const fetchWorkspaces = async (params?: Record<string, string>): Promise<PaginatedResponse<Workspace>> => {
|
||||
const query = params ? new URLSearchParams(params).toString() : '';
|
||||
const url = `/api/workspaces/${query ? `?${query}` : ''}`;
|
||||
const response = await authFetch(url);
|
||||
@@ -20,7 +43,17 @@ export const fetchWorkspaces = async (params?: Record<string, string>): Promise<
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.results || data;
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return { count: data.length, next: null, previous: null, results: data };
|
||||
}
|
||||
|
||||
return {
|
||||
count: data.count || data.results?.length || 0,
|
||||
next: data.next || null,
|
||||
previous: data.previous || null,
|
||||
results: data.results || data
|
||||
};
|
||||
};
|
||||
|
||||
export const getWorkspace = async (id: string): Promise<Workspace> => {
|
||||
@@ -65,12 +98,24 @@ export const deleteWorkspace = async (id: string): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchWorkspaceMemberships = async (workspaceId: string) => {
|
||||
const response = await authFetch(`/api/workspace-memberships/?workspace=${workspaceId}`);
|
||||
export const fetchWorkspaceMemberships = async (params?: Record<string, string>): Promise<PaginatedResponse<WorkspaceMembership>> => {
|
||||
const queryParams = new URLSearchParams((params || {}));
|
||||
const response = await authFetch(`/api/workspace-memberships/?${queryParams.toString()}`);
|
||||
|
||||
if (!response.ok) throw new Error("Failed to fetch workspace memberships");
|
||||
|
||||
const data = await response.json();
|
||||
return data.results || data;
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return { count: data.length, next: null, previous: null, results: data };
|
||||
}
|
||||
|
||||
return {
|
||||
count: data.count || data.results?.length || 0,
|
||||
next: data.next || null,
|
||||
previous: data.previous || null,
|
||||
results: data.results || data
|
||||
};
|
||||
};
|
||||
|
||||
export const addWorkspaceMembership = async (data: { workspace: string; user: string; role: string }) => {
|
||||
|
||||
Reference in New Issue
Block a user