summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--makima/frontend/src/hooks/useDogs.ts112
-rw-r--r--makima/frontend/src/lib/api.ts84
2 files changed, 196 insertions, 0 deletions
diff --git a/makima/frontend/src/hooks/useDogs.ts b/makima/frontend/src/hooks/useDogs.ts
new file mode 100644
index 0000000..819219e
--- /dev/null
+++ b/makima/frontend/src/hooks/useDogs.ts
@@ -0,0 +1,112 @@
+import { useState, useEffect, useCallback } from "react";
+import {
+ type DirectiveOrderGroup,
+ type CreateDOGRequest,
+ type UpdateDOGRequest,
+ listDogs,
+ createDog,
+ getDog,
+ updateDog as updateDogApi,
+ deleteDog as deleteDogApi,
+ pickUpDogOrders as pickUpDogOrdersApi,
+} from "../lib/api";
+
+export function useDogs(directiveId: string | undefined) {
+ const [dogs, setDogs] = useState<DirectiveOrderGroup[]>([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);
+
+ const refresh = useCallback(async () => {
+ if (!directiveId) return;
+ try {
+ setLoading(true);
+ setError(null);
+ const res = await listDogs(directiveId);
+ setDogs(res.dogs);
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Failed to load DOGs");
+ } finally {
+ setLoading(false);
+ }
+ }, [directiveId]);
+
+ useEffect(() => {
+ refresh();
+ }, [refresh]);
+
+ const create = useCallback(async (req: CreateDOGRequest) => {
+ if (!directiveId) return null;
+ const dog = await createDog(directiveId, req);
+ await refresh();
+ return dog;
+ }, [directiveId, refresh]);
+
+ const update = useCallback(async (dogId: string, req: UpdateDOGRequest) => {
+ if (!directiveId) return;
+ await updateDogApi(directiveId, dogId, req);
+ await refresh();
+ }, [directiveId, refresh]);
+
+ const remove = useCallback(async (dogId: string) => {
+ if (!directiveId) return;
+ await deleteDogApi(directiveId, dogId);
+ await refresh();
+ }, [directiveId, refresh]);
+
+ const pickUpOrders = useCallback(async (dogId: string) => {
+ if (!directiveId) return null;
+ const result = await pickUpDogOrdersApi(directiveId, dogId);
+ await refresh();
+ return result;
+ }, [directiveId, refresh]);
+
+ return { dogs, loading, error, refresh, create, update, remove, pickUpOrders };
+}
+
+export function useDog(directiveId: string | undefined, dogId: string | undefined) {
+ const [dog, setDog] = useState<DirectiveOrderGroup | null>(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);
+
+ const refresh = useCallback(async () => {
+ if (!directiveId || !dogId) return;
+ try {
+ setLoading(true);
+ setError(null);
+ const d = await getDog(directiveId, dogId);
+ setDog(d);
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Failed to load DOG");
+ } finally {
+ setLoading(false);
+ }
+ }, [directiveId, dogId]);
+
+ useEffect(() => {
+ setDog(null);
+ setError(null);
+ setLoading(true);
+ refresh();
+ }, [directiveId, dogId]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ const update = useCallback(async (req: UpdateDOGRequest) => {
+ if (!directiveId || !dogId) return;
+ const d = await updateDogApi(directiveId, dogId, req);
+ setDog(d);
+ return d;
+ }, [directiveId, dogId]);
+
+ const remove = useCallback(async () => {
+ if (!directiveId || !dogId) return;
+ await deleteDogApi(directiveId, dogId);
+ }, [directiveId, dogId]);
+
+ const pickUpOrders = useCallback(async () => {
+ if (!directiveId || !dogId) return null;
+ const result = await pickUpDogOrdersApi(directiveId, dogId);
+ await refresh();
+ return result;
+ }, [directiveId, dogId, refresh]);
+
+ return { dog, loading, error, refresh, update, remove, pickUpOrders };
+}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 4923c1d..7968583 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -3387,6 +3387,85 @@ export async function pickUpOrders(directiveId: string): Promise<PickUpOrdersRes
}
// =============================================================================
+// Directive Order Groups (DOGs) API
+// =============================================================================
+
+export type DOGStatus = "open" | "in_progress" | "done" | "archived";
+
+export interface DirectiveOrderGroup {
+ id: string;
+ directiveId: string;
+ ownerId: string;
+ name: string;
+ description: string | null;
+ status: DOGStatus;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface DOGListResponse {
+ dogs: DirectiveOrderGroup[];
+}
+
+export interface CreateDOGRequest {
+ name: string;
+ description?: string | null;
+}
+
+export interface UpdateDOGRequest {
+ name?: string;
+ description?: string | null;
+ status?: DOGStatus;
+}
+
+export async function listDogs(directiveId: string): Promise<DOGListResponse> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs`);
+ if (!res.ok) throw new Error(`Failed to list DOGs: ${res.statusText}`);
+ return res.json();
+}
+
+export async function createDog(directiveId: string, req: CreateDOGRequest): Promise<DirectiveOrderGroup> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(req),
+ });
+ if (!res.ok) throw new Error(`Failed to create DOG: ${res.statusText}`);
+ return res.json();
+}
+
+export async function getDog(directiveId: string, dogId: string): Promise<DirectiveOrderGroup> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs/${dogId}`);
+ if (!res.ok) throw new Error(`Failed to get DOG: ${res.statusText}`);
+ return res.json();
+}
+
+export async function updateDog(directiveId: string, dogId: string, req: UpdateDOGRequest): Promise<DirectiveOrderGroup> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs/${dogId}`, {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(req),
+ });
+ if (!res.ok) throw new Error(`Failed to update DOG: ${res.statusText}`);
+ return res.json();
+}
+
+export async function deleteDog(directiveId: string, dogId: string): Promise<void> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs/${dogId}`, {
+ method: "DELETE",
+ });
+ if (!res.ok) throw new Error(`Failed to delete DOG: ${res.statusText}`);
+}
+
+export async function pickUpDogOrders(directiveId: string, dogId: string): Promise<PickUpOrdersResponse> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs/${dogId}/pick-up-orders`, {
+ method: "POST",
+ });
+ if (!res.ok) throw new Error(`Failed to pick up DOG orders: ${res.statusText}`);
+ return res.json();
+}
+
+// =============================================================================
// Orders API
// =============================================================================
@@ -3406,6 +3485,7 @@ export interface Order {
directiveId: string | null;
directiveStepId: string | null;
directiveName: string | null;
+ dogId: string | null;
repositoryUrl: string | null;
createdAt: string;
updatedAt: string;
@@ -3424,6 +3504,7 @@ export interface CreateOrderRequest {
orderType?: OrderType;
labels?: string[];
directiveId: string;
+ dogId?: string | null;
repositoryUrl?: string | null;
}
@@ -3436,6 +3517,7 @@ export interface UpdateOrderRequest {
labels?: string[];
directiveId?: string | null;
directiveStepId?: string | null;
+ dogId?: string | null;
repositoryUrl?: string | null;
}
@@ -3445,6 +3527,7 @@ export async function listOrders(
priority?: OrderPriority,
directiveId?: string,
search?: string,
+ dogId?: string,
): Promise<OrderListResponse> {
const params = new URLSearchParams();
if (status) params.set("status", status);
@@ -3452,6 +3535,7 @@ export async function listOrders(
if (priority) params.set("priority", priority);
if (directiveId) params.set("directiveId", directiveId);
if (search) params.set("search", search);
+ if (dogId) params.set("dogId", dogId);
const qs = params.toString();
const res = await authFetch(`${API_BASE}/api/v1/orders${qs ? `?${qs}` : ""}`);
if (!res.ok) throw new Error(`Failed to list orders: ${res.statusText}`);