diff --git a/client/src/components/ui/backend/organization_call_backend.ts b/client/src/components/ui/backend/organization_call_backend.ts index 695ee3b..8a32e1a 100644 --- a/client/src/components/ui/backend/organization_call_backend.ts +++ b/client/src/components/ui/backend/organization_call_backend.ts @@ -1,3 +1,4 @@ +// @author sylee212 // src/hooks/organization_call_backend.ts // Change this URL to match your backend API endpoint @@ -7,14 +8,23 @@ import { generateRandomMockInventoryDetails } from "@/mocks/Inventory_Details_In import { generateMockMember } from "@/mocks/Members_Details_Interface_Mocks"; // tha main URL -export const BASE_URL = "http://localhost:8000/api/activities/"; +export const BASE_INVENTORY_URL = "http://localhost:8000/api/inventory/"; +export const WEEKLY_REPORT_BY_FIELD = + "http://localhost:8000/api/inventory/weeklyReportByField/"; + +export interface django_count_response_interface { + field: number; + thisWeek: number; + lastWeek: number; + difference: number; +} // change when backend is ready -const isDev: boolean = true; +const isDev: boolean = false; // --- GET: Fetch all items --- export const getItems = async ( - filterType: string, + URL: string, ): Promise => { if (isDev) { // Mock data for development @@ -27,7 +37,8 @@ export const getItems = async ( } else { // 1. Fetch from Django // fetch() is used to get the data from backend using URL - const response = await fetch(`${BASE_URL}?type=${filterType}`); + // `${}` is place holders for code, anything inside the curly braces is code + const response = await fetch(`${URL}`); // 2. Check if the request was successful if (!response.ok) throw new Error("Failed to fetch"); @@ -41,6 +52,22 @@ export const getItems = async ( } }; +export const getWeeklyReportByField = async ( + URL: string, +): Promise => { + const response = await fetch(`${URL}`); + + // 2. Check if the request was successful + if (!response.ok) throw new Error("Failed to fetch"); + + // 3. Parse the JSON data + // because fetcah returns a response, it isnt the data yet, + // its just the response header, + // you will need to use .json() to get the data + // it converts raw bytes to Javascript objects + return await response.json(); // Returns the list from Django +}; + // --- POST: Create a new item --- // .stringify, flattens the object // headers: { 'Content-Type': 'application/json' } determine, how the data will be dealt with by the server @@ -53,14 +80,18 @@ export const createItem = async ( console.log("Mock create item called with data:", newData); return true; // Simulate successful creation } else { - const response = await fetch(BASE_URL, { + const response = await fetch(BASE_INVENTORY_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newData), }); - // must return true when coding backend - return await response.json(); + if (response.ok) return true; + + // If it's not OK, let's see why + const errorText = await response.text(); + console.error("Server Error Response:", errorText); + return false; } }; @@ -68,19 +99,24 @@ export const createItem = async ( export const updateItem = async ( id: number, newData: Inventory_Details_Interface, -) => { - // Django usually expects a trailing slash after the ID - const response = await fetch(`${BASE_URL}${id}/`, { - method: "PATCH", +): Promise => { + const response = await fetch(`${BASE_INVENTORY_URL}${id}/`, { + method: "PATCH", // PATCH is better than PUT for Partial updates headers: { "Content-Type": "application/json" }, body: JSON.stringify(newData), }); - return await response.json(); + + if (response.ok) return true; + + // If it's not OK, let's see why + const errorText = await response.text(); + console.error("Server Error Response:", errorText); + return false; }; // --- DELETE: Remove an item --- export const deleteItem = async (id: number) => { - const response = await fetch(`${BASE_URL}${id}/`, { + const response = await fetch(`${BASE_INVENTORY_URL}${id}/`, { method: "DELETE", }); // DELETE usually returns a 204 No Content status, so we don't always .json() it @@ -89,6 +125,7 @@ export const deleteItem = async (id: number) => { // MEMBERS SECTION +// PENDING export const getMembers = async ( filterType: string, ): Promise => { @@ -103,7 +140,7 @@ export const getMembers = async ( } else { // 1. Fetch from Django // fetch() is used to get the data from backend using URL - const response = await fetch(`${BASE_URL}?type=${filterType}`); + const response = await fetch(`${BASE_INVENTORY_URL}?type=${filterType}`); // 2. Check if the request was successful if (!response.ok) throw new Error("Failed to fetch"); @@ -124,7 +161,7 @@ export const createMember = async ( console.log("Mock create Member called with data:", newData); return true; // Simulate successful creation } else { - const response = await fetch(BASE_URL, { + const response = await fetch(BASE_INVENTORY_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newData), @@ -141,7 +178,7 @@ export const updateMember = async ( newData: Member_Details_Interface, ) => { // Django usually expects a trailing slash after the ID - const response = await fetch(`${BASE_URL}${id}/`, { + const response = await fetch(`${BASE_INVENTORY_URL}${id}/`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newData), @@ -151,7 +188,7 @@ export const updateMember = async ( // --- DELETE: Remove an Member --- export const deleteMember = async (id: number) => { - const response = await fetch(`${BASE_URL}${id}/`, { + const response = await fetch(`${BASE_INVENTORY_URL}${id}/`, { method: "DELETE", }); // DELETE usually returns a 204 No Content status, so we don't always .json() it diff --git a/client/src/components/ui/backend/organization_clean_backend_calls.ts b/client/src/components/ui/backend/organization_clean_backend_calls.ts index efa1225..8f9043e 100644 --- a/client/src/components/ui/backend/organization_clean_backend_calls.ts +++ b/client/src/components/ui/backend/organization_clean_backend_calls.ts @@ -1,16 +1,19 @@ +// @author sylee212 // src/hooks/organization_clean_backend_calls.ts -import SWR, { KeyedMutator } from "swr"; +import useSWR, { KeyedMutator } from "swr"; import { Member_Details_Interface } from "@/components/ui/card_organization_member_details_modal"; import type { Inventory_Details_Interface } from "../card_organization_inventory_details_modal"; import { - BASE_URL, + BASE_INVENTORY_URL, createItem, createMember, deleteItem, + django_count_response_interface, getItems, getMembers, + getWeeklyReportByField, updateItem, updateMember, } from "./organization_call_backend"; @@ -45,6 +48,17 @@ export interface organization_clean_backend_calls_return_boolean_members_interfa refresh: KeyedMutator; // Optional refresh function } +export interface organization_clean_backend_calls_return_count_interface { + // data? means "if data exists". ?? 0 means "otherwise use 0" + thisWeek: number; + lastWeek: number; + difference: number; + loading: boolean; + error: Error | null; + isSuccess: boolean; + refresh: KeyedMutator; // Optional refresh function +} + /* This is the class that will call the backend to get the item data / set the item data / update the item data/ delete the item data @@ -54,9 +68,6 @@ Remember, this will only be invoked once, when its mounted, if you want to call // 1. Define a simple fetcher function (standard for SWR) // const fetcher = (url: string) => fetch(url).then(res => res.json()); -// this is the one that will work with the clean function from api call -const fetcher = () => getItems("all"); - /* Even if we are calling mocks, we need to SWR @@ -64,7 +75,8 @@ filterType: d to add to backend url call isDev: true if we want to mock data */ export const useOrganizationBackendGetItems = ( - filterType: string, + category: string, + sortBy: string, ): organization_clean_backend_calls_return_interface => { // 2. SWR handles the state, the effect, and the async logic // SWR(key, fetcher, options) @@ -116,9 +128,27 @@ export const useOrganizationBackendGetItems = ( // mutate? what is that, so lets say we do polling every 30seconds and // ther is an inventory update that happened in between the 30seconds // SWR will immediately - const { data, error, isLoading, mutate } = SWR( - [`${BASE_URL}`, filterType], // The "Key" (Unique identifier) - fetcher, // The "Fetcher" (Your function) + + // generate the URL + // 1. GENERATE THE URL STRING + // Instead of complex if/else, we use URLSearchParams + const params = new URLSearchParams(); + if (category && category !== "") params.append("categories", category); // Django expects 'categories' + if (sortBy && sortBy !== "") params.append("ordering", sortBy); // Django expects 'ordering' + + // If params exist, add '?' and the params, otherwise just the base URL + const queryString = params.toString(); + + // 2. Fix the URL construction + // We need backticks ` ` to use ${} + // We need to add 'items/' because the router is registered there + const finalUrl = queryString + ? `${BASE_INVENTORY_URL}?${queryString}` + : `${BASE_INVENTORY_URL}`; + + const { data, error, isLoading, mutate } = useSWR( + finalUrl, // The "Key" (Unique identifier) + getItems, // The "Fetcher" (Your function) { refreshInterval: 30, // Poll every 30 seconds 30000ms revalidateOnFocus: true, // Refresh when r clicks back into the tab @@ -136,6 +166,32 @@ export const useOrganizationBackendGetItems = ( return res; }; +export const useOrganizationBackendGetWeeklyReportByField = ( + URL: string, +): organization_clean_backend_calls_return_count_interface => { + const { data, error, isLoading, mutate } = useSWR( + URL, // The "Key" (Unique identifier) + getWeeklyReportByField, // The "Fetcher" (Your function) + { + refreshInterval: 30, // Poll every 30 seconds 30000ms + revalidateOnFocus: true, // Refresh when r clicks back into the tab + }, + ); + + const res: organization_clean_backend_calls_return_count_interface = { + thisWeek: data?.thisWeek || 0, + lastWeek: data?.lastWeek || 0, + difference: data?.difference || 0, + + loading: isLoading, + error: error, + isSuccess: !isLoading && !error, + refresh: mutate, // SWR calls its refresh function "mutate" + }; + + return res; +}; + // This is now a standard function, NOT a hook // if it is a hook, it will not work, a hook means swr export const createItemClean = async ( @@ -180,12 +236,13 @@ Even if we are calling mocks, we need to SWR filterType: d to add to backend url call isDev: true if we want to mock data + // PENDING */ export const useOrganizationBackendGetMembers = ( filterType: string, ): organization_clean_backend_calls_return_get_members_interface => { - const { data, error, isLoading, mutate } = SWR( - [`${BASE_URL}`, filterType], // The "Key" (Unique identifier) + const { data, error, isLoading, mutate } = useSWR( + [`${BASE_INVENTORY_URL}`, filterType], // The "Key" (Unique identifier) fetcher2, // The "Fetcher" (Your function) { refreshInterval: 30, // Poll every 30 seconds 30000ms @@ -246,7 +303,7 @@ export const deleteMemberClean = async (memberId: number): Promise => { // // to SWR for PUT // const { data, error, isLoading, mutate } = SWR( -// [`${BASE_URL}`, "newItem"], +// [`${BASE_INVENTORY_URL}`, "newItem"], // () => createItem(itemData), // { // revalidateOnFocus: true, diff --git a/client/src/components/ui/button_quick_actions.tsx b/client/src/components/ui/button_quick_actions.tsx index 1b7f8b4..3e550a8 100644 --- a/client/src/components/ui/button_quick_actions.tsx +++ b/client/src/components/ui/button_quick_actions.tsx @@ -1,3 +1,4 @@ +// @author sylee212 // src/components/button_quick_actions.tsx import Link from "next/link"; diff --git a/client/src/components/ui/card_organization_inventory_details_modal.tsx b/client/src/components/ui/card_organization_inventory_details_modal.tsx index ea39cec..c77dce7 100644 --- a/client/src/components/ui/card_organization_inventory_details_modal.tsx +++ b/client/src/components/ui/card_organization_inventory_details_modal.tsx @@ -1,3 +1,4 @@ +// @author sylee212 // src/components/card_organization_inventory_details_modal.tsx /* @@ -27,7 +28,7 @@ interface Inventory_Details_Interface { name: string; details: string; categories?: string; - availability?: string; + availability?: boolean; organization?: string; collectionPoint: string; borrowerName?: string; @@ -45,6 +46,11 @@ interface Inventory_Details_Modal_Interface { itemData: Inventory_Details_Interface | null; // Data of the item to display } +const deleteHelper = (id: number, onclose: () => void) => { + deleteItemClean(id || 0); + onclose(); +}; + const Inventory_Details_Modal: React.FC = ({ isOpen, onClose, @@ -108,7 +114,7 @@ const Inventory_Details_Modal: React.FC = ({