Skip to content

Commit b7da813

Browse files
authored
Merge pull request #13 from codersforcauses/Organization_Dashboard
final commit, pending users and bookings
2 parents 3fe6bcf + 4df9351 commit b7da813

27 files changed

Lines changed: 1588 additions & 103 deletions

client/src/components/ui/backend/organization_call_backend.ts

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @author sylee212
12
// src/hooks/organization_call_backend.ts
23
// Change this URL to match your backend API endpoint
34

@@ -7,14 +8,23 @@ import { generateRandomMockInventoryDetails } from "@/mocks/Inventory_Details_In
78
import { generateMockMember } from "@/mocks/Members_Details_Interface_Mocks";
89

910
// tha main URL
10-
export const BASE_URL = "http://localhost:8000/api/activities/";
11+
export const BASE_INVENTORY_URL = "http://localhost:8000/api/inventory/";
12+
export const WEEKLY_REPORT_BY_FIELD =
13+
"http://localhost:8000/api/inventory/weeklyReportByField/";
14+
15+
export interface django_count_response_interface {
16+
field: number;
17+
thisWeek: number;
18+
lastWeek: number;
19+
difference: number;
20+
}
1121

1222
// change when backend is ready
13-
const isDev: boolean = true;
23+
const isDev: boolean = false;
1424

1525
// --- GET: Fetch all items ---
1626
export const getItems = async (
17-
filterType: string,
27+
URL: string,
1828
): Promise<Inventory_Details_Interface[]> => {
1929
if (isDev) {
2030
// Mock data for development
@@ -27,7 +37,8 @@ export const getItems = async (
2737
} else {
2838
// 1. Fetch from Django
2939
// fetch() is used to get the data from backend using URL
30-
const response = await fetch(`${BASE_URL}?type=${filterType}`);
40+
// `${}` is place holders for code, anything inside the curly braces is code
41+
const response = await fetch(`${URL}`);
3142

3243
// 2. Check if the request was successful
3344
if (!response.ok) throw new Error("Failed to fetch");
@@ -41,6 +52,22 @@ export const getItems = async (
4152
}
4253
};
4354

55+
export const getWeeklyReportByField = async (
56+
URL: string,
57+
): Promise<django_count_response_interface> => {
58+
const response = await fetch(`${URL}`);
59+
60+
// 2. Check if the request was successful
61+
if (!response.ok) throw new Error("Failed to fetch");
62+
63+
// 3. Parse the JSON data
64+
// because fetcah returns a response, it isnt the data yet,
65+
// its just the response header,
66+
// you will need to use .json() to get the data
67+
// it converts raw bytes to Javascript objects
68+
return await response.json(); // Returns the list from Django
69+
};
70+
4471
// --- POST: Create a new item ---
4572
// .stringify, flattens the object
4673
// headers: { 'Content-Type': 'application/json' } determine, how the data will be dealt with by the server
@@ -53,34 +80,43 @@ export const createItem = async (
5380
console.log("Mock create item called with data:", newData);
5481
return true; // Simulate successful creation
5582
} else {
56-
const response = await fetch(BASE_URL, {
83+
const response = await fetch(BASE_INVENTORY_URL, {
5784
method: "POST",
5885
headers: { "Content-Type": "application/json" },
5986
body: JSON.stringify(newData),
6087
});
6188

62-
// must return true when coding backend
63-
return await response.json();
89+
if (response.ok) return true;
90+
91+
// If it's not OK, let's see why
92+
const errorText = await response.text();
93+
console.error("Server Error Response:", errorText);
94+
return false;
6495
}
6596
};
6697

6798
// --- PATCH/PUT: Update an existing item ---
6899
export const updateItem = async (
69100
id: number,
70101
newData: Inventory_Details_Interface,
71-
) => {
72-
// Django usually expects a trailing slash after the ID
73-
const response = await fetch(`${BASE_URL}${id}/`, {
74-
method: "PATCH",
102+
): Promise<boolean> => {
103+
const response = await fetch(`${BASE_INVENTORY_URL}${id}/`, {
104+
method: "PATCH", // PATCH is better than PUT for Partial updates
75105
headers: { "Content-Type": "application/json" },
76106
body: JSON.stringify(newData),
77107
});
78-
return await response.json();
108+
109+
if (response.ok) return true;
110+
111+
// If it's not OK, let's see why
112+
const errorText = await response.text();
113+
console.error("Server Error Response:", errorText);
114+
return false;
79115
};
80116

81117
// --- DELETE: Remove an item ---
82118
export const deleteItem = async (id: number) => {
83-
const response = await fetch(`${BASE_URL}${id}/`, {
119+
const response = await fetch(`${BASE_INVENTORY_URL}${id}/`, {
84120
method: "DELETE",
85121
});
86122
// 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) => {
89125

90126
// MEMBERS SECTION
91127

128+
// PENDING
92129
export const getMembers = async (
93130
filterType: string,
94131
): Promise<Member_Details_Interface[]> => {
@@ -103,7 +140,7 @@ export const getMembers = async (
103140
} else {
104141
// 1. Fetch from Django
105142
// fetch() is used to get the data from backend using URL
106-
const response = await fetch(`${BASE_URL}?type=${filterType}`);
143+
const response = await fetch(`${BASE_INVENTORY_URL}?type=${filterType}`);
107144

108145
// 2. Check if the request was successful
109146
if (!response.ok) throw new Error("Failed to fetch");
@@ -124,7 +161,7 @@ export const createMember = async (
124161
console.log("Mock create Member called with data:", newData);
125162
return true; // Simulate successful creation
126163
} else {
127-
const response = await fetch(BASE_URL, {
164+
const response = await fetch(BASE_INVENTORY_URL, {
128165
method: "POST",
129166
headers: { "Content-Type": "application/json" },
130167
body: JSON.stringify(newData),
@@ -141,7 +178,7 @@ export const updateMember = async (
141178
newData: Member_Details_Interface,
142179
) => {
143180
// Django usually expects a trailing slash after the ID
144-
const response = await fetch(`${BASE_URL}${id}/`, {
181+
const response = await fetch(`${BASE_INVENTORY_URL}${id}/`, {
145182
method: "PATCH",
146183
headers: { "Content-Type": "application/json" },
147184
body: JSON.stringify(newData),
@@ -151,7 +188,7 @@ export const updateMember = async (
151188

152189
// --- DELETE: Remove an Member ---
153190
export const deleteMember = async (id: number) => {
154-
const response = await fetch(`${BASE_URL}${id}/`, {
191+
const response = await fetch(`${BASE_INVENTORY_URL}${id}/`, {
155192
method: "DELETE",
156193
});
157194
// DELETE usually returns a 204 No Content status, so we don't always .json() it

client/src/components/ui/backend/organization_clean_backend_calls.ts

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1+
// @author sylee212
12
// src/hooks/organization_clean_backend_calls.ts
2-
import SWR, { KeyedMutator } from "swr";
3+
import useSWR, { KeyedMutator } from "swr";
34

45
import { Member_Details_Interface } from "@/components/ui/card_organization_member_details_modal";
56

67
import type { Inventory_Details_Interface } from "../card_organization_inventory_details_modal";
78
import {
8-
BASE_URL,
9+
BASE_INVENTORY_URL,
910
createItem,
1011
createMember,
1112
deleteItem,
13+
django_count_response_interface,
1214
getItems,
1315
getMembers,
16+
getWeeklyReportByField,
1417
updateItem,
1518
updateMember,
1619
} from "./organization_call_backend";
@@ -45,6 +48,17 @@ export interface organization_clean_backend_calls_return_boolean_members_interfa
4548
refresh: KeyedMutator<boolean>; // Optional refresh function
4649
}
4750

51+
export interface organization_clean_backend_calls_return_count_interface {
52+
// data? means "if data exists". ?? 0 means "otherwise use 0"
53+
thisWeek: number;
54+
lastWeek: number;
55+
difference: number;
56+
loading: boolean;
57+
error: Error | null;
58+
isSuccess: boolean;
59+
refresh: KeyedMutator<django_count_response_interface>; // Optional refresh function
60+
}
61+
4862
/*
4963
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
5064
@@ -54,17 +68,15 @@ Remember, this will only be invoked once, when its mounted, if you want to call
5468
// 1. Define a simple fetcher function (standard for SWR)
5569
// const fetcher = (url: string) => fetch(url).then(res => res.json());
5670

57-
// this is the one that will work with the clean function from api call
58-
const fetcher = () => getItems("all");
59-
6071
/*
6172
Even if we are calling mocks, we need to SWR
6273
6374
filterType: d to add to backend url call
6475
isDev: true if we want to mock data
6576
*/
6677
export const useOrganizationBackendGetItems = (
67-
filterType: string,
78+
category: string,
79+
sortBy: string,
6880
): organization_clean_backend_calls_return_interface => {
6981
// 2. SWR handles the state, the effect, and the async logic
7082
// SWR(key, fetcher, options)
@@ -116,9 +128,27 @@ export const useOrganizationBackendGetItems = (
116128
// mutate? what is that, so lets say we do polling every 30seconds and
117129
// ther is an inventory update that happened in between the 30seconds
118130
// SWR will immediately
119-
const { data, error, isLoading, mutate } = SWR(
120-
[`${BASE_URL}`, filterType], // The "Key" (Unique identifier)
121-
fetcher, // The "Fetcher" (Your function)
131+
132+
// generate the URL
133+
// 1. GENERATE THE URL STRING
134+
// Instead of complex if/else, we use URLSearchParams
135+
const params = new URLSearchParams();
136+
if (category && category !== "") params.append("categories", category); // Django expects 'categories'
137+
if (sortBy && sortBy !== "") params.append("ordering", sortBy); // Django expects 'ordering'
138+
139+
// If params exist, add '?' and the params, otherwise just the base URL
140+
const queryString = params.toString();
141+
142+
// 2. Fix the URL construction
143+
// We need backticks ` ` to use ${}
144+
// We need to add 'items/' because the router is registered there
145+
const finalUrl = queryString
146+
? `${BASE_INVENTORY_URL}?${queryString}`
147+
: `${BASE_INVENTORY_URL}`;
148+
149+
const { data, error, isLoading, mutate } = useSWR(
150+
finalUrl, // The "Key" (Unique identifier)
151+
getItems, // The "Fetcher" (Your function)
122152
{
123153
refreshInterval: 30, // Poll every 30 seconds 30000ms
124154
revalidateOnFocus: true, // Refresh when r clicks back into the tab
@@ -136,6 +166,32 @@ export const useOrganizationBackendGetItems = (
136166
return res;
137167
};
138168

169+
export const useOrganizationBackendGetWeeklyReportByField = (
170+
URL: string,
171+
): organization_clean_backend_calls_return_count_interface => {
172+
const { data, error, isLoading, mutate } = useSWR(
173+
URL, // The "Key" (Unique identifier)
174+
getWeeklyReportByField, // The "Fetcher" (Your function)
175+
{
176+
refreshInterval: 30, // Poll every 30 seconds 30000ms
177+
revalidateOnFocus: true, // Refresh when r clicks back into the tab
178+
},
179+
);
180+
181+
const res: organization_clean_backend_calls_return_count_interface = {
182+
thisWeek: data?.thisWeek || 0,
183+
lastWeek: data?.lastWeek || 0,
184+
difference: data?.difference || 0,
185+
186+
loading: isLoading,
187+
error: error,
188+
isSuccess: !isLoading && !error,
189+
refresh: mutate, // SWR calls its refresh function "mutate"
190+
};
191+
192+
return res;
193+
};
194+
139195
// This is now a standard function, NOT a hook
140196
// if it is a hook, it will not work, a hook means swr
141197
export const createItemClean = async (
@@ -180,12 +236,13 @@ Even if we are calling mocks, we need to SWR
180236
181237
filterType: d to add to backend url call
182238
isDev: true if we want to mock data
239+
// PENDING
183240
*/
184241
export const useOrganizationBackendGetMembers = (
185242
filterType: string,
186243
): organization_clean_backend_calls_return_get_members_interface => {
187-
const { data, error, isLoading, mutate } = SWR(
188-
[`${BASE_URL}`, filterType], // The "Key" (Unique identifier)
244+
const { data, error, isLoading, mutate } = useSWR(
245+
[`${BASE_INVENTORY_URL}`, filterType], // The "Key" (Unique identifier)
189246
fetcher2, // The "Fetcher" (Your function)
190247
{
191248
refreshInterval: 30, // Poll every 30 seconds 30000ms
@@ -246,7 +303,7 @@ export const deleteMemberClean = async (memberId: number): Promise<boolean> => {
246303

247304
// // to SWR for PUT
248305
// const { data, error, isLoading, mutate } = SWR(
249-
// [`${BASE_URL}`, "newItem"],
306+
// [`${BASE_INVENTORY_URL}`, "newItem"],
250307
// () => createItem(itemData),
251308
// {
252309
// revalidateOnFocus: true,

client/src/components/ui/button_quick_actions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @author sylee212
12
// src/components/button_quick_actions.tsx
23

34
import Link from "next/link";

client/src/components/ui/card_organization_inventory_details_modal.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @author sylee212
12
// src/components/card_organization_inventory_details_modal.tsx
23

34
/*
@@ -27,7 +28,7 @@ interface Inventory_Details_Interface {
2728
name: string;
2829
details: string;
2930
categories?: string;
30-
availability?: string;
31+
availability?: boolean;
3132
organization?: string;
3233
collectionPoint: string;
3334
borrowerName?: string;
@@ -45,6 +46,11 @@ interface Inventory_Details_Modal_Interface {
4546
itemData: Inventory_Details_Interface | null; // Data of the item to display
4647
}
4748

49+
const deleteHelper = (id: number, onclose: () => void) => {
50+
deleteItemClean(id || 0);
51+
onclose();
52+
};
53+
4854
const Inventory_Details_Modal: React.FC<Inventory_Details_Modal_Interface> = ({
4955
isOpen,
5056
onClose,
@@ -108,7 +114,7 @@ const Inventory_Details_Modal: React.FC<Inventory_Details_Modal_Interface> = ({
108114
</Link>
109115

110116
<button
111-
onClick={() => deleteItemClean(itemData.id || 0)}
117+
onClick={() => deleteHelper(itemData.id || 0, onClose)}
112118
className="modal-action-button secondary"
113119
>
114120
Delete

client/src/components/ui/card_organization_inventory_status_card.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @author sylee212
12
// src/components/card_organization_inventory_status_card.tsx
23

34
/*

client/src/components/ui/card_organization_inventory_status_data.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @author sylee212
12
// src/components/ui/card_organization_inventory_status_data.tsx
23

34
/*

client/src/components/ui/card_organization_member_details_modal.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @author sylee212
12
// src/components/card_organization_inventory_details_modal.tsx
23

34
/*
@@ -123,6 +124,7 @@ export const Member_Details_Modal: React.FC<Member_Details_Modal_Interface> = ({
123124
<div className="modal-action-button secondary">Modify</div>
124125
</Link>
125126

127+
{/* // PENDING */}
126128
<button
127129
onClick={() => deleteItemClean(itemData.id || 0)}
128130
className="modal-action-button secondary"

client/src/components/ui/card_organization_member_management.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @author sylee212
12
import React from "react";
23

34
import { Member_Details_Interface } from "./card_organization_member_details_modal";

client/src/components/ui/card_organization_recent_activities_item.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @author sylee212
12
// src/components/card_organization_recent_activities_item.tsx
23

34
import React from "react";

client/src/components/ui/card_organization_recent_activities_panel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @author sylee212
12
// src/components/card_organization_recent_activities_panel.tsx
23

34
/*

0 commit comments

Comments
 (0)