From 7ada4237de720ede167767bf45e2240dfc05c5a0 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Sun, 14 Dec 2025 14:25:40 +0800 Subject: [PATCH 01/54] created the navbar --- .../src/components/ui/navbar_organization.tsx | 36 ++++++ client/src/pages/_app.tsx | 1 + client/src/pages/organization_dashboard.tsx | 8 +- client/src/styles/organization.css | 104 ++++++++++++++++++ 4 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 client/src/components/ui/navbar_organization.tsx create mode 100644 client/src/styles/organization.css diff --git a/client/src/components/ui/navbar_organization.tsx b/client/src/components/ui/navbar_organization.tsx new file mode 100644 index 0000000..634ca74 --- /dev/null +++ b/client/src/components/ui/navbar_organization.tsx @@ -0,0 +1,36 @@ +// src/components/Header.tsx (No changes needed, just a review) + +const Header = () => { + return ( +
+ {" "} + {/* <--- Class 1 */} + {/* Left side: Logo */} +
+ {" "} + {/* <--- Class 3 */} + Logo +
+ {/* Center: Navigation Links */} + + {/* Right side: User Profile */} +
+ {" "} + {/* <--- Class 4 */} + {/* User avatar and name */} +
{/* <--- Class 5 */} + User +
+
+ ); +}; + +export default Header; diff --git a/client/src/pages/_app.tsx b/client/src/pages/_app.tsx index 628e9f2..f54dcc9 100644 --- a/client/src/pages/_app.tsx +++ b/client/src/pages/_app.tsx @@ -1,4 +1,5 @@ import "@/styles/globals.css"; +import "../styles/organization.css"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index 97f3674..0a393c8 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -1,19 +1,15 @@ // src/pages/organization_dashboard.tsx (Update the import path) -import Head from "next/head"; // The Header component is now one directory level up from 'pages', // so the path is '../components/Header' -//import Header from '../components/Header'; +import Header from "../components/ui/navbar_organization"; const DashboardPage = () => { return ( <> - - Inventory Dashboard -
{/* 1. Navigation Bar */} - {/*
*/} +
{/* 2. Main content wrapper */}
diff --git a/client/src/styles/organization.css b/client/src/styles/organization.css new file mode 100644 index 0000000..717393a --- /dev/null +++ b/client/src/styles/organization.css @@ -0,0 +1,104 @@ +/* --- HEADER STYLES --- */ + +/* +Remember to add in pages/_app.tsx +import '../styles/organization.css'; + +syntax +- for readability +example: .header-nav ul {} +this means to the class that is applying header-nav, the ul tag will have these styles + +: for special states +example: .header-nav li:hover +other possible states :hover :focus :active + +. for selector +example: .app-header +this is to apply the style to all html elemetns that apply this class ( Class Selector ) + +# for selector +example: #main-header ( in css ) nav-id="main-header" ( in html ) +this is to apply the style to the html element that has this specific id ( ID Selector ) +*/ + +/* +This is for the header tag +flex: makes the children align in a row +justify-content: space-between: spaces out the children to the edges +align-items: center: vertically centers the children +padding: adds space inside the header +*/ +.app-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 40px; + border-bottom: 1px solid #e0e0e0; + background-color: #c0c0c0; +} + +/* +This is for the header navigation links +list-style: none: removes the bullet points +padding: 0: removes the padding +margin: 0: removes the margin +display: flex: makes the children align in a row +gap: 30px: adds space between the children +*/ +.header-nav ul { + list-style: none; + padding: 0; + margin: 0; + display: flex; + gap: 30px; + font-size: 18px; + font-weight: 500; +} + +/* +This is for the individual navigation links +cursor: pointer: changes the cursor to a pointer on hover +color: #444444: sets the color to gray +*/ +.header-nav li { + cursor: pointer; + color: #444444; + transition: color 0.2s; +} + +/* +This is to make the navigation links change color on hover +*/ +.header-nav li:hover { + color: #964545; +} + +/* +This is for the logo section +*/ +.header-logo { + font-size: 20px; + font-weight: bold; + min-width: 150px; +} + +/* + +*/ +.header-user { + display: flex; + align-items: center; + gap: 8px; + min-width: 150px; + justify-content: flex-end; +} + +.user-avatar-placeholder { + width: 30px; + height: 30px; + border-radius: 50%; + background-color: #4a77e5; + border: 2px solid #ffffff; + box-shadow: 0 0 0 2px #4a77e5; +} \ No newline at end of file From f47f2423e338adee12d96761917d72a80194990e Mon Sep 17 00:00:00 2001 From: sylee212 Date: Sun, 14 Dec 2025 16:17:21 +0800 Subject: [PATCH 02/54] created navbar with user profile click drop down menu --- .../src/components/ui/navbar_organization.tsx | 82 ++++++++++++++++--- client/src/styles/organization.css | 72 ++++++++++++++++ 2 files changed, 141 insertions(+), 13 deletions(-) diff --git a/client/src/components/ui/navbar_organization.tsx b/client/src/components/ui/navbar_organization.tsx index 634ca74..dbfeecb 100644 --- a/client/src/components/ui/navbar_organization.tsx +++ b/client/src/components/ui/navbar_organization.tsx @@ -1,33 +1,89 @@ -// src/components/Header.tsx (No changes needed, just a review) +// src/components/Header.tsx + +/* +How does the dropdown meny work? +1. the menu is hidden (isDropdownOpen = false) +2. when the user clicks on the profile area, the toggleDropdown function is called, which toggles the state to true, +making the menu visible by setting isDropdownOpen to true. + +Common issues: +1. The menu closes immediately when clicking on it. because you use onClick instead of MouseDown + user starts a click with OnClick and the browser processes the entire click event, which makes the wrapper in focus state, + and since the menu exist now ( on True ), it triggers the onBlur event, closing the menu. + +useState() +- const [isDropdownOpen, setIsDropdownOpen] = useState(false); + ^ variable ^ setter function ^ initial state +- used to add states in functional components. + +onBlur +- An event that occurs when an element loses focus or not the main thing the user will interact with. +- Here, it is used to close the dropdown menu when the user clicks outside of it. + +onMouseDown vs onClick +- onMouseDown: Triggered when the mouse button is pressed down. +- onClick: Triggered when the mouse button is pressed and released. +- In this case, onMouseDown is used to toggle the dropdown menu to ensure it opens before any blur event can occur. + +*/ + +// this is for the dropdown menu from user profile side +import React, { useState } from "react"; // <-- Import useState const Header = () => { + // 1. Initialize state for the dropdown visibility + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + // Helper functions to control the state + const toggleDropdown = () => setIsDropdownOpen((prev) => !prev); + + // We keep the handleBlur logic simple (for when the user clicks *outside* the entire element) + const handleBlur = () => { + // A small delay is still necessary to ensure the menu's buttons are clickable + // before the menu hides. + setTimeout(() => { + setIsDropdownOpen(false); + }, 100); + }; + return (
- {" "} - {/* <--- Class 1 */} {/* Left side: Logo */}
- {" "} - {/* <--- Class 3 */} Logo
{/* Center: Navigation Links */} + {/* Right side: User Profile */} -
- {" "} - {/* <--- Class 4 */} - {/* User avatar and name */} -
{/* <--- Class 5 */} - User +
+
+
+ User +
+ + {isDropdownOpen && ( +
e.preventDefault()} + > + + +
+ )}
); diff --git a/client/src/styles/organization.css b/client/src/styles/organization.css index 717393a..4da5948 100644 --- a/client/src/styles/organization.css +++ b/client/src/styles/organization.css @@ -22,6 +22,19 @@ example: #main-header ( in css ) nav-id="main-header" ( in html ) this is to apply the style to the html element that has this specific id ( ID Selector ) */ +.dashboard-container { + display: flex; + flex-direction: column; + height: 100vh; +} + +.dashboard-content { + flex: 1; + padding: 20px 40px; + background-color: #f9f9f9; + overflow-y: auto; +} + /* This is for the header tag flex: makes the children align in a row @@ -87,6 +100,7 @@ This is for the logo section */ .header-user { + cursor: pointer; display: flex; align-items: center; gap: 8px; @@ -101,4 +115,62 @@ This is for the logo section background-color: #4a77e5; border: 2px solid #ffffff; box-shadow: 0 0 0 2px #4a77e5; +} + +/* 1. Wrapper for Relative Positioning */ +.header-user-wrapper { + /* This makes all ABSOLUTELY positioned children (like the dropdown) + position themselves relative to THIS container. */ + position: relative; + cursor: pointer; + /* Reset tabIndex focus outline */ + outline: none; +} + +/* 2. The Dropdown Menu */ +.user-dropdown-menu { + /* Take the menu out of the normal document flow and position it */ + position: absolute; + + /* Position it below the user icon (adjust 'top' as needed) */ + top: 50px; + right: 0; /* Align the right edge of the dropdown with the right edge of the wrapper */ + + /* Styling */ + min-width: 180px; + background-color: #ffffff; + border: 1px solid #e0e0e0; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + z-index: 1000; /* Ensure it appears above other content */ + padding: 8px 0; + + /* Display the buttons vertically */ + display: flex; + flex-direction: column; +} + +/* 3. The Buttons/Items inside the menu */ +.dropdown-item { + background: none; /* Remove default button background */ + border: none; /* Remove default button border */ + text-align: left; + padding: 10px 15px; + cursor: pointer; + font-size: 14px; + color: #333; + transition: background-color 0.15s; +} + +.dropdown-item:hover { + /* Highlight the item when the user hovers over it */ + background-color: #f0f0f0; +} + +/* Ensure the original header-user is still aligned correctly inside the wrapper */ +.header-user { + /* ... (Your original styles for the user icon/name remain here) ... */ + /* You might need to adjust the min-width/justify-content + if the layout seems off, but the original styles should still work. */ + display: flex; } \ No newline at end of file From 20ef6a955c6ff39dbb71b8ee297a9dbf50d4e117 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Sun, 14 Dec 2025 19:48:14 +0800 Subject: [PATCH 03/54] finished dashboard 5 stats --- .../ui/card_organization_statistics.tsx | 67 +++++++++++++++++ client/src/pages/organization_dashboard.tsx | 37 ++++++++- client/src/styles/organization.css | 75 ++++++++++++++++++- 3 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 client/src/components/ui/card_organization_statistics.tsx diff --git a/client/src/components/ui/card_organization_statistics.tsx b/client/src/components/ui/card_organization_statistics.tsx new file mode 100644 index 0000000..1d021d0 --- /dev/null +++ b/client/src/components/ui/card_organization_statistics.tsx @@ -0,0 +1,67 @@ +// src/components/ui/StatisticsCard.tsx + +import React from "react"; + +/* +Interface +This component represents a single statistics card used in the organization dashboard. +So when you use this interaface, you can put in this data + +example usage: + +*/ +interface StatisticsCardProps { + title: string; // The card title (e.g., "Total Inventory") + value: number; // The main numerical value (e.g., 30) + delta: string; // The change string (e.g., "+3 this week") + status: "up" | "down"; // Controls the color and direction of the arrow +} + +/* +React.FC +- React.FC<> stands for React Functional Component + +{ title, value, delta, status } +- Destructuring the props object to directly access title, value, delta, and status +*/ +const StatisticsCard: React.FC = ({ + title, + value, + delta, + status, +}) => { + // Logic to determine the arrow character and color + const arrow = status === "up" ? "↑" : "↓"; + // Using CSS variables for colors defined in organization.css + // var() is a native function for css + const deltaColor = + status === "up" ? "var(--color-success)" : "var(--color-error)"; + + return ( + // The main gray box container +
+ {/* Arrow Indicator at the top right */} + {/* why {{ instead of {} }}, that is because, first is to go in to js script, second is that style only accepts js objects*/} +
+ {arrow} +
+ + {/* Main Content */} +
+

{title}

+

{value}

+ {/* why {{ instead of {} }}, that is because, first is to go in to js script, second is that style only accepts js objects*/} +

+ {delta} +

+
+
+ ); +}; + +export default StatisticsCard; diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index 0a393c8..6460ba1 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -2,6 +2,7 @@ // The Header component is now one directory level up from 'pages', // so the path is '../components/Header' +import StatisticsCard from "../components/ui/card_organization_statistics"; import Header from "../components/ui/navbar_organization"; const DashboardPage = () => { @@ -15,7 +16,41 @@ const DashboardPage = () => {
{/* Components for Statistics, Recent Activity, and Quick Actions */}

Welcome to the Dashboard!

{" "} - {/* Add a temporary header to confirm it works */} + {/* NEW SECTION: Statistics Cards */} +
+ {" "} + {/* This class will control the layout of the 5 cards */} + + + + + +
diff --git a/client/src/styles/organization.css b/client/src/styles/organization.css index 4da5948..8fc9dec 100644 --- a/client/src/styles/organization.css +++ b/client/src/styles/organization.css @@ -22,6 +22,21 @@ example: #main-header ( in css ) nav-id="main-header" ( in html ) this is to apply the style to the html element that has this specific id ( ID Selector ) */ +/* +Custom Variables (Good Practice for colors) + +color-success: #198754; Green for up/positive trends +color-error: #dc3545; Red for down/negative trends +color-card-background: #eeeeee; Light gray for the card body +color-text-dark: #333333; + */ +:root { + --color-success: #198754; /* Green for up/positive trends */ + --color-error: #dc3545; /* Red for down/negative trends */ + --color-card-background: #eeeeee; /* Light gray for the card body */ + --color-text-dark: #333333; +} + .dashboard-container { display: flex; flex-direction: column; @@ -96,9 +111,6 @@ This is for the logo section min-width: 150px; } -/* - -*/ .header-user { cursor: pointer; display: flex; @@ -173,4 +185,59 @@ This is for the logo section /* You might need to adjust the min-width/justify-content if the layout seems off, but the original styles should still work. */ display: flex; -} \ No newline at end of file +} + +/* This is for the 5 statistics card in the dashboard */ +/* --- STATISTICS CARD STYLES --- */ +.stat-card { + /* The main wrapper for the gray box */ + background-color: var(--color-card-background); + border-radius: 12px; /* Rounded corners */ + padding: 20px; + position: relative; /* Essential for positioning the arrow absolutely */ + min-height: 120px; + display: flex; /* Helps ensure content is aligned */ + flex-direction: column; + justify-content: flex-end; /* Pushes content to the bottom of the card */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */ +} + +.stat-indicator { + /* Styles for the large arrow (Green or Red) */ + position: absolute; /* Positioned relative to the .stat-card parent */ + top: 15px; + right: 20px; + font-size: 36px; /* Large arrow size */ + font-weight: bold; + line-height: 1; /* Ensures the arrow sits nicely */ +} + +.stat-title { + font-size: 14px; + color: var(--color-text-dark); + margin: 0; + line-height: 1.2; +} + +.stat-value { + font-size: 30px; + font-weight: 700; + color: #000; + margin: 5px 0 10px 0; +} + +.stat-delta { + font-size: 14px; + margin: 0; + font-weight: 500; +} + +/* --- Layout for the 5 Stats Cards --- */ +.stats-grid { + /* Use CSS Grid to display 5 columns of equal width */ + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 20px; /* Space between the cards */ + margin-bottom: 25px; /* Space below the stats row */ + padding: 0 40px; /* Keep padding consistent with header/main content */ +} From 982c18983a72e120b1664b6a153d776fe0857899 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Mon, 15 Dec 2025 14:11:27 +0800 Subject: [PATCH 04/54] pending the recent activities for dashboard --- .../components/ui/button_quick_actions.tsx | 32 +++++++ client/src/pages/organization_dashboard.tsx | 24 +++++ client/src/styles/organization.css | 94 +++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 client/src/components/ui/button_quick_actions.tsx diff --git a/client/src/components/ui/button_quick_actions.tsx b/client/src/components/ui/button_quick_actions.tsx new file mode 100644 index 0000000..b6539fe --- /dev/null +++ b/client/src/components/ui/button_quick_actions.tsx @@ -0,0 +1,32 @@ +// src/components/button_quick_actions.tsx + +import React from "react"; + +const QuickActions = () => { + return ( +
+

Quick Actions

+ +
+ {/* 2. RIGHT COLUMN: Quick Actions */} + +
+
+ ); +}; + +export default QuickActions; diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index 6460ba1..49c463d 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -2,6 +2,7 @@ // The Header component is now one directory level up from 'pages', // so the path is '../components/Header' +import QuickActions from "../components/ui/button_quick_actions"; import StatisticsCard from "../components/ui/card_organization_statistics"; import Header from "../components/ui/navbar_organization"; @@ -51,6 +52,29 @@ const DashboardPage = () => { status="down" /> + {/* NEW: Two-Column Layout for Activity and Actions */} +
+ {/* 1. LEFT COLUMN: Recent Activity */} +
+ {/* This is where the RecentActivity component will go */} +

Recent Activity

+ {/* Temporary placeholder to see the layout */} +
+ Activity List Placeholder +
+
+ + {/* 2. RIGHT COLUMN: Quick Actions */} + +
diff --git a/client/src/styles/organization.css b/client/src/styles/organization.css index 8fc9dec..22e5305 100644 --- a/client/src/styles/organization.css +++ b/client/src/styles/organization.css @@ -241,3 +241,97 @@ This is for the logo section margin-bottom: 25px; /* Space below the stats row */ padding: 0 40px; /* Keep padding consistent with header/main content */ } + +/* --- MAIN DASHBOARD CONTENT LAYOUT --- */ +.dashboard-content-layout { + /* Use CSS Grid for the two-column structure */ + display: grid; + /* Define the columns: a large column (3 parts) and a smaller column (1 part) */ + grid-template-columns: 3fr 1fr; + gap: 30px; /* Space between the two columns */ + padding: 0 40px 40px 40px; /* Consistent padding around the main content */ +} + +/* --- QUICK ACTIONS STYLES (Blue Panel) --- */ +.quick-actions-card { + /* Main blue background */ + background-color: #4a77e5; /* The prominent blue color */ + color: white; + border-radius: 12px; + padding: 20px; + /* Use Flexbox to stack content (title and button group) */ + display: flex; + flex-direction: column; + height: 100%; /* Important: makes the panel stretch to match activity height */ +} + +.quick-actions-title { + font-size: 18px; + font-weight: 600; + margin-top: 0; + margin-bottom: 20px; +} + +.action-button-group { + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + gap: 15px; /* Space between the two buttons */ +} + +.action-button { + /* Base button styling */ + display: flex; + align-items: center; + justify-content: center; + padding: 15px 10px; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; + position: relative; +} + +/* Specific style for the 'Add Product' button */ +.action-button.primary { + background-color: rgba(255, 255, 255, 0.15); /* Slightly transparent white on blue */ + color: white; +} + +/* Specific style for the 'View Whole Inventory' button */ +.action-button.secondary { + background-color: #ffffff; /* White background */ + color: #333; /* Dark text */ +} + +/* Icon styling */ +.icon-plus { + margin-right: 10px; + font-size: 20px; +} + +.icon-box { + margin-right: 10px; + font-size: 20px; +} + +/* Badge (the 'B' circle) */ +.badge { + position: absolute; + bottom: -10px; + right: 10px; + background-color: #dc3545; /* Red color for visibility */ + color: white; + width: 25px; + height: 25px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: bold; + border: 2px solid white; +} From b7a1d8f50acf7e523c709a550a84d3a6ee07df7b Mon Sep 17 00:00:00 2001 From: sylee212 Date: Mon, 15 Dec 2025 14:45:04 +0800 Subject: [PATCH 05/54] finished dashboard frontend --- ...rd_organization_recent_activities_item.tsx | 56 +++++++ ...d_organization_recent_activities_panel.tsx | 151 ++++++++++++++++++ client/src/pages/organization_dashboard.tsx | 24 +-- client/src/styles/organization.css | 134 ++++++++++++++-- 4 files changed, 335 insertions(+), 30 deletions(-) create mode 100644 client/src/components/ui/card_organization_recent_activities_item.tsx create mode 100644 client/src/components/ui/card_organization_recent_activities_panel.tsx diff --git a/client/src/components/ui/card_organization_recent_activities_item.tsx b/client/src/components/ui/card_organization_recent_activities_item.tsx new file mode 100644 index 0000000..3fbaab7 --- /dev/null +++ b/client/src/components/ui/card_organization_recent_activities_item.tsx @@ -0,0 +1,56 @@ +// src/components/card_organization_recent_activities_item.tsx + +import React from "react"; + +// Define the shape of the data for a single activity item +interface ActivityItemProps { + type: "increase" | "decrease" | "member"; // e.g., Inventory increase, Lost Member + status: "up" | "down"; // Green or Red arrow + title: string; // Main action (e.g., "New Member", "Inventory increase") + detail: string; // The item or person involved (e.g., "Cody", "Cool Potato") + quantity: number; // The numerical change (e.g., +1, -1) + time: string; // The time elapsed (e.g., "5 minutes ago") +} + +const RecentActivityItem: React.FC = ({ + status, + title, + detail, + quantity, + time, +}) => { + // Logic to determine arrow symbol and color + const arrow = status === "up" ? "↑" : "↓"; + + // Use CSS Variables defined in organization.css + const statusColor = + status === "up" ? "var(--color-success)" : "var(--color-error)"; + + // Format quantity to include + or - sign + const formattedQuantity = `${quantity > 0 ? "+" : ""}${quantity}`; + + return ( +
+ {/* 1. Status Arrow */} +
+ {arrow} +
+ + {/* 2. Title and Detail (Stacked) */} +
+

{title}

+

{detail}

+
+ + {/* 3. Time and Quantity (Aligned Right) */} +
+ + {formattedQuantity} + + {time} +
+
+ ); +}; + +export default RecentActivityItem; diff --git a/client/src/components/ui/card_organization_recent_activities_panel.tsx b/client/src/components/ui/card_organization_recent_activities_panel.tsx new file mode 100644 index 0000000..9e9844f --- /dev/null +++ b/client/src/components/ui/card_organization_recent_activities_panel.tsx @@ -0,0 +1,151 @@ +// src/components/RecentActivityPanel.tsx + +import Link from "next/link"; // For the 'View All' link +import React from "react"; + +import RecentActivityItem from "./card_organization_recent_activities_item"; // Import the item component + +// Example data structure that the component might use (or receive as props later) +const mockActivityData = [ + { + status: "up" as const, + title: "New Member", + detail: "Cody", + quantity: 1, + time: "5 minutes ago", + }, + { + status: "up" as const, + title: "Inventory increase", + detail: "Cool Potato", + quantity: 1, + time: "5 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + // Add more items here... +]; + +const RecentActivityPanel = () => { + return ( +
+ {/* Header with Title and View All Link */} +
+

Recent Activity

+ + View All + +
+ + {/* The Activity List */} +
+ { + // Use the JavaScript map function to render one RecentActivityItem for each data entry + mockActivityData.map((item, index) => ( + + )) + } +
+
+ ); +}; + +export default RecentActivityPanel; diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index 49c463d..dcf0dac 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -3,6 +3,7 @@ // The Header component is now one directory level up from 'pages', // so the path is '../components/Header' import QuickActions from "../components/ui/button_quick_actions"; +import RecentActivityPanel from "../components/ui/card_organization_recent_activities_panel"; import StatisticsCard from "../components/ui/card_organization_statistics"; import Header from "../components/ui/navbar_organization"; @@ -16,8 +17,7 @@ const DashboardPage = () => { {/* 2. Main content wrapper */}
{/* Components for Statistics, Recent Activity, and Quick Actions */} -

Welcome to the Dashboard!

{" "} - {/* NEW SECTION: Statistics Cards */} + {/* Statistics Cards */}
{" "} {/* This class will control the layout of the 5 cards */} @@ -52,25 +52,15 @@ const DashboardPage = () => { status="down" />
- {/* NEW: Two-Column Layout for Activity and Actions */} + + {/* Two-Column Layout for Activity and Actions */}
- {/* 1. LEFT COLUMN: Recent Activity */} + {/* LEFT COLUMN: Recent Activity, used section here to group some assets */}
- {/* This is where the RecentActivity component will go */} -

Recent Activity

- {/* Temporary placeholder to see the layout */} -
- Activity List Placeholder -
+
- {/* 2. RIGHT COLUMN: Quick Actions */} + {/* 2. RIGHT COLUMN: Quick Actions, aside here is used for accessibility, it does not make it appear on the right*/} diff --git a/client/src/styles/organization.css b/client/src/styles/organization.css index 22e5305..6afb692 100644 --- a/client/src/styles/organization.css +++ b/client/src/styles/organization.css @@ -243,6 +243,12 @@ This is for the logo section } /* --- MAIN DASHBOARD CONTENT LAYOUT --- */ +/* +This is how we create the two-column layout for Recent Activity and Quick Actions +display: grid: enables CSS Grid layout +grid-template-columns: 3fr 1fr: defines the two columns, 3fr ( fractional units ) for the first column and 1fr for the second column +gap: 30px: adds space between the two columns +*/ .dashboard-content-layout { /* Use CSS Grid for the two-column structure */ display: grid; @@ -318,20 +324,122 @@ This is for the logo section font-size: 20px; } -/* Badge (the 'B' circle) */ -.badge { - position: absolute; - bottom: -10px; - right: 10px; - background-color: #dc3545; /* Red color for visibility */ - color: white; - width: 25px; - height: 25px; - border-radius: 50%; + + + +/* --- RECENT ACTIVITY PANEL --- */ +.recent-activity-card { + background-color: #ffffff; /* White background for the card */ + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + padding: 20px; + /* REMOVE min-height: 300px; - We will control height on the list itself */ + height: 100%; /* Keep this to fill the parent grid cell height */ + display: block; /* Changed from flex to block to avoid conflicts */ + flex-direction: column; +} + +.activity-card-header { + display: flex; + justify-content: space-between; + align-items: baseline; /* Align text baselines */ + margin-bottom: 20px; + border-bottom: 1px solid #f0f0f0; /* Separator line */ + padding-bottom: 10px; + flex-shrink: 0; /* Prevent shrinking */ +} + +.activity-card-title { + font-size: 20px; + font-weight: 600; + margin: 0; +} + +.activity-view-all { + color: #4a77e5; /* Blue color */ + text-decoration: none; + font-size: 14px; + font-weight: 500; + cursor: pointer; +} + +/* --- SCROLLABLE RECENT ACTIVITY LIST CONTAINER --- */ +.activity-list { + /* Critical: Set a maximum height relative to the parent card's height, + and tell the browser to show a vertical scrollbar if content overflows. */ + overflow-y: scroll; + + /* Optional: Improves aesthetics by hiding the scrollbar on some browsers, + but keeping scroll functionality (e.g., Safari/Firefox on Mac). */ + overflow-x: hidden; + + /* Adds internal padding to make sure scrollbar doesn't stick to the content edge */ + padding-right: 15px; + padding-top: 10px; + + max-height: 50vh; /* vh is for viewport height, you need to have a fixed height, or no use */ + + /* Keep these: */ + overflow-y: scroll; + overflow-x: hidden; + padding-right: 15px; +} + + +/* --- INDIVIDUAL ACTIVITY ITEM --- */ +.activity-item { display: flex; align-items: center; - justify-content: center; - font-size: 12px; + padding: 10px 0; + border-bottom: 1px solid #f0f0f0; /* Separator between list items */ +} + +.activity-item:last-child { + border-bottom: none; /* Remove border from the last item */ +} + +.activity-status { + /* Arrow container */ + font-size: 20px; font-weight: bold; - border: 2px solid white; + margin-right: 15px; + line-height: 1; +} + +.activity-info { + /* Title and Detail (left-aligned) */ + flex-grow: 1; /* Allows this section to take up all available space */ +} + +.activity-title { + font-size: 15px; + font-weight: 500; + margin: 0; + line-height: 1.2; +} + +.activity-detail { + font-size: 13px; + color: #888; + margin: 0; + line-height: 1.2; +} + +.activity-meta { + /* Quantity and Time (right-aligned) */ + display: flex; + flex-direction: column; + align-items: flex-end; + text-align: right; + width: 100px; /* Gives a fixed width to ensure right alignment */ +} + +.activity-quantity { + font-size: 15px; + font-weight: 600; +} + +.activity-time { + font-size: 12px; + color: #888; } From f4cf4227d50f6a2035bb05b7179d98c636fafbda Mon Sep 17 00:00:00 2001 From: sylee212 Date: Mon, 15 Dec 2025 20:45:42 +0800 Subject: [PATCH 06/54] made changes to their hyperlinks --- .../card_organization_recent_activities_panel.tsx | 14 ++++++++++++-- client/src/components/ui/navbar_organization.tsx | 13 ++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/client/src/components/ui/card_organization_recent_activities_panel.tsx b/client/src/components/ui/card_organization_recent_activities_panel.tsx index 9e9844f..2ea6d57 100644 --- a/client/src/components/ui/card_organization_recent_activities_panel.tsx +++ b/client/src/components/ui/card_organization_recent_activities_panel.tsx @@ -1,4 +1,14 @@ -// src/components/RecentActivityPanel.tsx +// src/components/card_organization_recent_activities_panel.tsx + +/* +How does it show all the components in the list? +1. pass the list of data from the mockActivityData array +2. use the map function to iterate over each item in the array +3. for each item, render a RecentActivityItem component, passing the relevant props + +so what happens is, it will run the map and iterate over every item and then create a RecentActivityItem component +for each one, passing in the data and placing it under the div with class activity-list +*/ import Link from "next/link"; // For the 'View All' link import React from "react"; @@ -121,7 +131,7 @@ const RecentActivityPanel = () => { {/* Header with Title and View All Link */}

Recent Activity

- + View All
diff --git a/client/src/components/ui/navbar_organization.tsx b/client/src/components/ui/navbar_organization.tsx index dbfeecb..20dae1c 100644 --- a/client/src/components/ui/navbar_organization.tsx +++ b/client/src/components/ui/navbar_organization.tsx @@ -28,6 +28,7 @@ onMouseDown vs onClick */ // this is for the dropdown menu from user profile side +import Link from "next/dist/client/link"; import React, { useState } from "react"; // <-- Import useState const Header = () => { @@ -55,9 +56,15 @@ const Header = () => { {/* Center: Navigation Links */} From a01f1365ae85084a722bae63fa6379aa435792d9 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Mon, 15 Dec 2025 20:50:33 +0800 Subject: [PATCH 07/54] organization activity page almost done, pending overlays --- ...ard_organization_inventory_status_card.tsx | 45 +++++++ ...ard_organization_inventory_status_data.tsx | 23 ++++ client/src/pages/organization_activity.tsx | 120 ++++++++++++++++++ client/src/styles/organization.css | 58 +++++++++ 4 files changed, 246 insertions(+) create mode 100644 client/src/components/ui/card_organization_inventory_status_card.tsx create mode 100644 client/src/components/ui/card_organization_inventory_status_data.tsx create mode 100644 client/src/pages/organization_activity.tsx diff --git a/client/src/components/ui/card_organization_inventory_status_card.tsx b/client/src/components/ui/card_organization_inventory_status_card.tsx new file mode 100644 index 0000000..1df866c --- /dev/null +++ b/client/src/components/ui/card_organization_inventory_status_card.tsx @@ -0,0 +1,45 @@ +// src/components/InventoryStatusCard.tsx + +import Link from "next/link"; +import React from "react"; + +import InventoryStatusItem, { + InventoryItemProps, +} from "./card_organization_inventory_status_data"; + +interface InventoryStatusCardProps { + title: string; // e.g., "Expiring Inventory" + viewAllHref: string; // The link for the View All button + items: InventoryItemProps[]; // Array of the items to display (using the InventoryItemProps interface) +} + +const InventoryStatusCard: React.FC = ({ + title, + viewAllHref, + items, +}) => { + return ( +
+ {/* Header with Title and View All Link */} +
+

{title}

+ + View All + +
+ + {/* Item List */} +
+ {items.map((item, index) => ( + + ))} +
+
+ ); +}; + +export default InventoryStatusCard; diff --git a/client/src/components/ui/card_organization_inventory_status_data.tsx b/client/src/components/ui/card_organization_inventory_status_data.tsx new file mode 100644 index 0000000..ef02d99 --- /dev/null +++ b/client/src/components/ui/card_organization_inventory_status_data.tsx @@ -0,0 +1,23 @@ +// src/components/InventoryStatusItem.tsx + +import React from "react"; + +interface InventoryItemProps { + itemName: string; // e.g., "Cool Potato" + statusDetail: string; // e.g., "In 2 days" or "5 mins ago" +} + +const InventoryStatusItem: React.FC = ({ + itemName, + statusDetail, +}) => { + return ( +
+ {itemName} + {statusDetail} +
+ ); +}; + +export type { InventoryItemProps }; +export default InventoryStatusItem; diff --git a/client/src/pages/organization_activity.tsx b/client/src/pages/organization_activity.tsx new file mode 100644 index 0000000..bbcce23 --- /dev/null +++ b/client/src/pages/organization_activity.tsx @@ -0,0 +1,120 @@ +// src/pages/organization_activity.tsx + +import Head from "next/head"; + +import QuickActions from "@/components/ui/button_quick_actions"; +import InventoryStatusCard from "@/components/ui/card_organization_inventory_status_card"; +import RecentActivityPanel from "@/components/ui/card_organization_recent_activities_panel"; + +import NavbarOrganization from "../components/ui/navbar_organization"; + +// --- MOCK DATA FOR THE SUMMARY CARDS --- +const mockInventoryItems = [ + { itemName: "Cool Potato", statusDetail: "In 2 days" }, + { itemName: "Cool Potato", statusDetail: "In 2 days" }, + { itemName: "Cool Potato", statusDetail: "In 2 days" }, +]; + +const mockBorrowedItems = [ + { itemName: "Cool Potato", statusDetail: "5 mins ago" }, + { itemName: "Cool Potato", statusDetail: "5 mins ago" }, + { itemName: "Cool Potato", statusDetail: "5 mins ago" }, +]; + +const ActivityPage = () => { + return ( + <> + + Activity Log - Full View + + +
+ + +
+ {/* NEW SECTION: Inventory Status Cards (Summary/Filters) */} +
+ + + + +
+ + {/* Two-Column Layout for Activity and Actions */} +
+ {/* LEFT COLUMN: Recent Activity, used section here to group some assets */} +
+ +
+ + {/* 2. RIGHT COLUMN: Quick Actions, aside here is used for accessibility, it does not make it appear on the right*/} + +
+
+
+ + ); +}; + +export default ActivityPage; + +/* + 1. TOP SECTION: Filters and Search +
+ Temporary Filter/Search Placeholders + + + +
+ + 2. MAIN SECTION: Activity List +
+
+ { + mockFullActivityData.map((item, index) => ( + // Reusing the modular component + + )) + } +
+
+ + + 3. BOTTOM SECTION: Pagination +
+ + Page 1 of 10 + + +
+*/ diff --git a/client/src/styles/organization.css b/client/src/styles/organization.css index 6afb692..9893427 100644 --- a/client/src/styles/organization.css +++ b/client/src/styles/organization.css @@ -443,3 +443,61 @@ gap: 30px: adds space between the two columns font-size: 12px; color: #888; } + +/* --- INVENTORY STATUS CARDS GRID --- */ +.inv-cards-grid { + /* Use CSS Grid to display 4 columns of equal width */ + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 20px; /* Space between the cards */ + margin: 25px 40px; /* Top/Bottom margin, Left/Right padding */ +} + +/* --- INDIVIDUAL INVENTORY STATUS CARD --- */ +.inv-status-card { + background-color: #f0f0f0; /* Light gray background */ + border-radius: 12px; + padding: 15px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.inv-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.inv-card-title { + font-size: 16px; + font-weight: 600; + margin: 0; +} + +.inv-view-all { + color: #4a77e5; /* Blue color */ + text-decoration: none; + font-size: 14px; + font-weight: 500; +} + +/* --- INVENTORY STATUS ITEM STYLES --- */ +.inv-status-item { + background-color: #ffffff; /* White background for the item box */ + border-radius: 8px; + padding: 10px 15px; + margin-bottom: 8px; + display: flex; + justify-content: space-between; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.inv-item-name { + font-weight: 500; + color: var(--color-text-dark); +} + +.inv-item-detail { + font-weight: 400; + color: #4a77e5; /* Make the status detail blue for visibility */ +} \ No newline at end of file From 5f15013f4d5bbe82e2cdf82bd610e8ee53a49e8e Mon Sep 17 00:00:00 2001 From: sylee212 Date: Mon, 15 Dec 2025 20:59:09 +0800 Subject: [PATCH 08/54] just added more comments --- .../ui/card_organization_inventory_status_card.tsx | 9 ++++++++- .../ui/card_organization_inventory_status_data.tsx | 12 +++++++++++- client/src/pages/organization_activity.tsx | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/client/src/components/ui/card_organization_inventory_status_card.tsx b/client/src/components/ui/card_organization_inventory_status_card.tsx index 1df866c..4ef6963 100644 --- a/client/src/components/ui/card_organization_inventory_status_card.tsx +++ b/client/src/components/ui/card_organization_inventory_status_card.tsx @@ -1,4 +1,11 @@ -// src/components/InventoryStatusCard.tsx +// src/components/card_organization_inventory_status_card.tsx + +/* +This is to hold the inventory status summary cards like Expiring Inventory, Inventory Due, Borrowed Items, Returned Items +- Each card shows a list of items with their status details +- it also requires a list of items to display, passed as props +- Also includes a "View All" link to navigate to the full list page +*/ import Link from "next/link"; import React from "react"; diff --git a/client/src/components/ui/card_organization_inventory_status_data.tsx b/client/src/components/ui/card_organization_inventory_status_data.tsx index ef02d99..b4de71c 100644 --- a/client/src/components/ui/card_organization_inventory_status_data.tsx +++ b/client/src/components/ui/card_organization_inventory_status_data.tsx @@ -1,4 +1,14 @@ -// src/components/InventoryStatusItem.tsx +// src/components/ui/card_organization_inventory_status_data.tsx + +/* +Idea for this how the recent activities like Recently Borrowed Items will be displayed +- This component represents a single item in the inventory status list, showing the item name and its status detail. + + +card_organization_inventory_status_card.tsx +- uses mulitple of this component to show the list +- the card also is just to hold the data and the View All link +*/ import React from "react"; diff --git a/client/src/pages/organization_activity.tsx b/client/src/pages/organization_activity.tsx index bbcce23..1e12882 100644 --- a/client/src/pages/organization_activity.tsx +++ b/client/src/pages/organization_activity.tsx @@ -31,6 +31,7 @@ const ActivityPage = () => {
+ {/* 2. @See card_organization_inventory_status_data to understand how it works */}
{/* NEW SECTION: Inventory Status Cards (Summary/Filters) */}
From d0957a0c7fb65f4d44116a22de41e47c6139f4cf Mon Sep 17 00:00:00 2001 From: sylee212 Date: Thu, 18 Dec 2025 18:34:36 +0800 Subject: [PATCH 09/54] an example of simple overlay --- client/src/components/ui/simple_modals.tsx | 91 ++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 client/src/components/ui/simple_modals.tsx diff --git a/client/src/components/ui/simple_modals.tsx b/client/src/components/ui/simple_modals.tsx new file mode 100644 index 0000000..cb5d150 --- /dev/null +++ b/client/src/components/ui/simple_modals.tsx @@ -0,0 +1,91 @@ +// src/components/Simple_Modal.tsx +import React from "react"; + +/* +3 parts to an overlay + +part 1) +The idea is this component act as the overlay +- So this overlay will only be rendered if the switch is ON (isOpen = true) +- It will have a button inside it that will call the onClose function to turn the switch OFF + +part 2.1) +The page will have the switch state (isOpen) and the function to turn it OFF (onClose) with useState +- it will have a div with an onClick + +part 2.2) +The page will also have a div, with the onClick that will turn on and off the swtich +- it will also have +*/ + +interface Simple_Modal_Props_Interface { + isOpen: boolean; + onClose: () => void; // This is the function to flip the state back to false +} + +const Simple_Modal: React.FC = ({ + isOpen, + onClose, +}) => { + // If isOpen is false, the code below this line never runs + if (!isOpen) return null; + + return ( +
+
+

Study Overlay

+

This is a manual test of the overlay system.

+ + {/* The X Button */} + +
+
+ ); +}; + +export default Simple_Modal; + +/* +Use this code + + + +// src/pages/activity/all.tsx +import React, { useState } from 'react'; +import SimpleModal from '../../components/SimpleModal'; + +const ActivityPage = () => { + // 1. Define the "Light Switch" state. Default is 'false' (closed). + const [showStudyModal, setShowStudyModal] = useState(false); + + return ( +
+

Activity Page

+ + 2. The Button that turns the switch ON + + + 3. Place the Modal component here. + We pass the current state (showStudyModal) + and the function to turn it OFF (setShowStudyModal) + setShowStudyModal(false)} + /> +
+ ); +}; + + + +*/ From 3eb966621b1fff406f29e964ee334a4148cd1482 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Fri, 19 Dec 2025 00:44:40 +0800 Subject: [PATCH 10/54] rough version of a overlay, pending refinement and data transfer to show correct data --- ...d_organization_inventory_details_modal.tsx | 68 +++++++++++++++++++ ...rd_organization_recent_activities_item.tsx | 34 ++++++---- ...d_organization_recent_activities_panel.tsx | 63 +++++++++++++++-- client/src/pages/organization_dashboard.tsx | 42 +++++++++++- 4 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 client/src/components/ui/card_organization_inventory_details_modal.tsx diff --git a/client/src/components/ui/card_organization_inventory_details_modal.tsx b/client/src/components/ui/card_organization_inventory_details_modal.tsx new file mode 100644 index 0000000..5767038 --- /dev/null +++ b/client/src/components/ui/card_organization_inventory_details_modal.tsx @@ -0,0 +1,68 @@ +// src/components/card_organization_inventory_details_modal.tsx + +/* +Simple modal is overlays + +How does the overlay work? +1. The item itself: which is the trigger, when clicked, it sets the state to open the modal and passes the ID to the panel +2. The panel itself: which just passes the state change and ID to the page +3. The page receives the state sent by panel and sends to the overlay, which since its true, it returns somethign to the page which is the overlay + +Key components +1. item itself which will be clicked on +1.1. A panel to hold the item +2. The page to manage the state and render the overlay +3. The overlay component itself +*/ + +import React from "react"; + +// Define the shape of the data this modal expects +interface Inventory_Details_Interface { + name: string; + details: string; + categories: string; + availability: string; + organization: string; + borrowLocation: string; + borrowerName: string; + borrowedOn: string; + returnedOn: string; + dueOn: string; + expiryDate: string; +} + +// to control the overlay modal visibility and data +interface Inventory_Details_Modal_Interface { + isOpen: boolean; + onClose: () => void; + itemData: Inventory_Details_Interface | null; // Data of the item to display +} + +const Inventory_Details_Modal: React.FC = ({ + isOpen, + onClose, + itemData, +}) => { + // 1. CONTROL: If it's not open or data is missing, render nothing. + if (!isOpen || !itemData) return null; + + return ( + // 2. RENDER: When 'isOpen' is true, this entire structure is placed on the screen. +
+
+ {/* ... Modal content rendered using itemData.name, itemData.details, etc. ... */} +

{itemData.name}

+ + {/* 3. CLOSING: When the close button is clicked, it calls the 'onClose' function + which updates the state in the ActivityPage (Step 1) */} + +
+
+ ); +}; + +export type { Inventory_Details_Interface }; // Exporting the interface for use in other files +export default Inventory_Details_Modal; diff --git a/client/src/components/ui/card_organization_recent_activities_item.tsx b/client/src/components/ui/card_organization_recent_activities_item.tsx index 3fbaab7..ed24730 100644 --- a/client/src/components/ui/card_organization_recent_activities_item.tsx +++ b/client/src/components/ui/card_organization_recent_activities_item.tsx @@ -3,21 +3,26 @@ import React from "react"; // Define the shape of the data for a single activity item -interface ActivityItemProps { - type: "increase" | "decrease" | "member"; // e.g., Inventory increase, Lost Member - status: "up" | "down"; // Green or Red arrow - title: string; // Main action (e.g., "New Member", "Inventory increase") - detail: string; // The item or person involved (e.g., "Cody", "Cool Potato") - quantity: number; // The numerical change (e.g., +1, -1) - time: string; // The time elapsed (e.g., "5 minutes ago") +interface Recent_Activities_Item_Interface { + id: number; // Ensure id is part of the interface + type: "increase" | "decrease" | "member" | null; + status: "up" | "down"; + title: string; + detail: string; + quantity: number; + time: string; + onItemClick: (id: number) => void; // Click handler is mandatory here to work } -const RecentActivityItem: React.FC = ({ +const Recent_Activities_Item: React.FC = ({ + id, // You must destructure 'id' here to use it below + type, status, title, detail, quantity, time, + onItemClick, }) => { // Logic to determine arrow symbol and color const arrow = status === "up" ? "↑" : "↓"; @@ -30,19 +35,22 @@ const RecentActivityItem: React.FC = ({ const formattedQuantity = `${quantity > 0 ? "+" : ""}${quantity}`; return ( -
- {/* 1. Status Arrow */} + /* When clicked, it sends its specific 'id' back up the chain to the Page */ +
onItemClick(id)} + style={{ cursor: "pointer" }} + >
{arrow}
- {/* 2. Title and Detail (Stacked) */}

{title}

{detail}

+

{type}

- {/* 3. Time and Quantity (Aligned Right) */}
{formattedQuantity} @@ -53,4 +61,4 @@ const RecentActivityItem: React.FC = ({ ); }; -export default RecentActivityItem; +export default Recent_Activities_Item; diff --git a/client/src/components/ui/card_organization_recent_activities_panel.tsx b/client/src/components/ui/card_organization_recent_activities_panel.tsx index 2ea6d57..75c6651 100644 --- a/client/src/components/ui/card_organization_recent_activities_panel.tsx +++ b/client/src/components/ui/card_organization_recent_activities_panel.tsx @@ -13,11 +13,13 @@ for each one, passing in the data and placing it under the div with class activi import Link from "next/link"; // For the 'View All' link import React from "react"; -import RecentActivityItem from "./card_organization_recent_activities_item"; // Import the item component +import Recent_Activities_Item from "./card_organization_recent_activities_item"; // Example data structure that the component might use (or receive as props later) const mockActivityData = [ { + id: 1, + type: "member" as const, status: "up" as const, title: "New Member", detail: "Cody", @@ -25,6 +27,8 @@ const mockActivityData = [ time: "5 minutes ago", }, { + id: 2, + type: "member" as const, status: "up" as const, title: "Inventory increase", detail: "Cool Potato", @@ -32,6 +36,8 @@ const mockActivityData = [ time: "5 minutes ago", }, { + id: 3, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -39,6 +45,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 4, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -46,6 +54,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 5, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -53,6 +63,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 6, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -60,6 +72,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 7, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -67,6 +81,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 8, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -74,6 +90,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 9, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -81,6 +99,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 10, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -88,6 +108,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 11, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -95,6 +117,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 12, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -102,6 +126,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 13, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -109,6 +135,8 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 14, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -116,6 +144,17 @@ const mockActivityData = [ time: "3 minutes ago", }, { + id: 15, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 16, + type: "member" as const, status: "down" as const, title: "Lost Member", detail: "Cody", @@ -125,7 +164,14 @@ const mockActivityData = [ // Add more items here... ]; -const RecentActivityPanel = () => { +// Define the interface for the Panel so it knows it receives onItemClick +interface Recent_Activity_Panel_Props { + onItemClick: (id: number) => void; +} + +const Recent_Activity_Panel: React.FC = ({ + onItemClick, +}) => { return (
{/* Header with Title and View All Link */} @@ -137,19 +183,22 @@ const RecentActivityPanel = () => {
{/* The Activity List */} + {/* react requires a key for each item in list */}
{ // Use the JavaScript map function to render one RecentActivityItem for each data entry mockActivityData.map((item, index) => ( - )) } @@ -158,4 +207,4 @@ const RecentActivityPanel = () => { ); }; -export default RecentActivityPanel; +export default Recent_Activity_Panel; diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index dcf0dac..12325c9 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -2,12 +2,46 @@ // The Header component is now one directory level up from 'pages', // so the path is '../components/Header' +import { useState } from "react"; + +import Inventory_Details_Modal, { Inventory_Details_Interface } from "@/components/ui/card_organization_inventory_details_modal"; + import QuickActions from "../components/ui/button_quick_actions"; import RecentActivityPanel from "../components/ui/card_organization_recent_activities_panel"; import StatisticsCard from "../components/ui/card_organization_statistics"; import Header from "../components/ui/navbar_organization"; const DashboardPage = () => { + // --- MOVE HOOKS INSIDE THE FUNCTION BODY --- + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedItemData, setSelectedItemData] = + useState(null); + + // Mock data for the overlay fields + const mockItemDetails = { + name: "Cool Potato", + details: "A cool potato", + categories: "Food item", + availability: "Available", + organization: "Coders For Cause", + borrowLocation: "UWA Crawley", + borrowerName: "Arush", + borrowedOn: "10 Nov 2025", + returnedOn: "10 Nov 2025", + dueOn: "10 Nov 2026", + expiryDate: "11 Nov 2026", + }; + + // --- MOVE LOGIC FUNCTIONS INSIDE TOO --- + const handleItemClick = (itemId: number) => { + console.log("Item clicked with ID:", itemId); + setSelectedItemData(mockItemDetails); + setIsModalOpen(true); + + // PENDING, remove after figuring out how to pass the correct data + console.log("Selected Item Data:", selectedItemData); + }; + return ( <>
@@ -57,7 +91,7 @@ const DashboardPage = () => {
{/* LEFT COLUMN: Recent Activity, used section here to group some assets */}
- +
{/* 2. RIGHT COLUMN: Quick Actions, aside here is used for accessibility, it does not make it appear on the right*/} @@ -67,6 +101,12 @@ const DashboardPage = () => {
+ {/* RENDER THE MODAL HERE */} + setIsModalOpen(false)} + itemData={mockItemDetails} + /> ); }; From c748c9707c24163f4308c670bb50d77705e80f89 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Fri, 19 Dec 2025 22:41:32 +0800 Subject: [PATCH 11/54] Created a mock generator and modified the code to use the mock generator --- .../Inventory_Details_Interface_Mocks.tsx | 80 +++++++++++++++++++ client/src/pages/organization_dashboard.tsx | 30 +++---- 2 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 client/src/mocks/Inventory_Details_Interface_Mocks.tsx diff --git a/client/src/mocks/Inventory_Details_Interface_Mocks.tsx b/client/src/mocks/Inventory_Details_Interface_Mocks.tsx new file mode 100644 index 0000000..fbf9dcb --- /dev/null +++ b/client/src/mocks/Inventory_Details_Interface_Mocks.tsx @@ -0,0 +1,80 @@ +import { Inventory_Details_Interface } from "../components/ui/card_organization_inventory_details_modal"; + +const names = [ + "Cool Potato", + "Heavy Duty Drill", + "First Aid Kit", + "Projector B", + "Foldable Chair", +]; +const details = [ + "A very cool potato", + "High-speed masonry drill", + "Fully stocked medical kit", + "4K Office projector", + "Standard seating", +]; +const categories = [ + "Food item", + "Power Tools", + "Safety", + "Electronics", + "Furniture", +]; +const locations = [ + "UWA Crawley", + "Guild Storage", + "Reid Library", + "Engineering Block", +]; +const users = ["Arush", "Cody", "Jane Doe", "Alex Smith", "System"]; + +/* + Generates a random Inventory_Details_Interface object + */ + +// 1. Helper Function: Generates a random date string +const getRandomDate = (start: Date, end: Date): string => { + const date = new Date( + start.getTime() + Math.random() * (end.getTime() - start.getTime()), + ); + + // Formats to "DD MMM YYYY" (e.g., 10 Nov 2025) + return date.toLocaleDateString("en-GB", { + day: "2-digit", + month: "short", + year: "numeric", + }); +}; + +// 2. Mock data for the overlay fields +const now = new Date(); +const lastMonth = new Date( + now.getFullYear(), + now.getMonth() - 1, + now.getDate(), +); +const nextYear = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate()); + +export const generateRandomMockInventoryDetails = + (): Inventory_Details_Interface => { + // Helper to pick a random item from an array + const getRandom = (arr: string[]) => + arr[Math.floor(Math.random() * arr.length)]; + + return { + name: getRandom(names), + details: getRandom(details), + categories: getRandom(categories), + availability: Math.random() > 0.5 ? "Available" : "Borrowed", + organization: "Coders For Cause", + borrowLocation: getRandom(locations), + borrowerName: getRandom(users), + + // RANDOMLY GENERATED DATES: + borrowedOn: getRandomDate(lastMonth, now), // Somewhere in the last 30 days + returnedOn: getRandomDate(now, now), // Today + dueOn: getRandomDate(now, nextYear), // Somewhere in the next year + expiryDate: getRandomDate(now, nextYear), // Somewhere in the next year + }; + }; diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index 12325c9..ed0d156 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -4,7 +4,10 @@ // so the path is '../components/Header' import { useState } from "react"; -import Inventory_Details_Modal, { Inventory_Details_Interface } from "@/components/ui/card_organization_inventory_details_modal"; +import Inventory_Details_Modal, { + Inventory_Details_Interface, +} from "@/components/ui/card_organization_inventory_details_modal"; +import { generateRandomMockInventoryDetails } from "@/mocks/Inventory_Details_Interface_Mocks"; import QuickActions from "../components/ui/button_quick_actions"; import RecentActivityPanel from "../components/ui/card_organization_recent_activities_panel"; @@ -17,25 +20,12 @@ const DashboardPage = () => { const [selectedItemData, setSelectedItemData] = useState(null); - // Mock data for the overlay fields - const mockItemDetails = { - name: "Cool Potato", - details: "A cool potato", - categories: "Food item", - availability: "Available", - organization: "Coders For Cause", - borrowLocation: "UWA Crawley", - borrowerName: "Arush", - borrowedOn: "10 Nov 2025", - returnedOn: "10 Nov 2025", - dueOn: "10 Nov 2026", - expiryDate: "11 Nov 2026", - }; - - // --- MOVE LOGIC FUNCTIONS INSIDE TOO --- + /* + We will need to have a function here as well to handle the getting of the data + */ const handleItemClick = (itemId: number) => { console.log("Item clicked with ID:", itemId); - setSelectedItemData(mockItemDetails); + setSelectedItemData(generateRandomMockInventoryDetails()); setIsModalOpen(true); // PENDING, remove after figuring out how to pass the correct data @@ -101,11 +91,11 @@ const DashboardPage = () => {
- {/* RENDER THE MODAL HERE */} + {/* RENDER THE MODAL HERE, Remember to send the data of the item here as well*/} setIsModalOpen(false)} - itemData={mockItemDetails} + itemData={selectedItemData} /> ); From 9aec133502b30b44c8bc6f6a4b28731964146e91 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Fri, 19 Dec 2025 22:48:06 +0800 Subject: [PATCH 12/54] added id field to the mocks --- client/src/mocks/Inventory_Details_Interface_Mocks.tsx | 1 + client/src/pages/organization_dashboard.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/mocks/Inventory_Details_Interface_Mocks.tsx b/client/src/mocks/Inventory_Details_Interface_Mocks.tsx index fbf9dcb..1f2d006 100644 --- a/client/src/mocks/Inventory_Details_Interface_Mocks.tsx +++ b/client/src/mocks/Inventory_Details_Interface_Mocks.tsx @@ -63,6 +63,7 @@ export const generateRandomMockInventoryDetails = arr[Math.floor(Math.random() * arr.length)]; return { + id: Math.floor(Math.random() * 10000), // Random ID between 0-9999 name: getRandom(names), details: getRandom(details), categories: getRandom(categories), diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index ed0d156..4334770 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -15,7 +15,7 @@ import StatisticsCard from "../components/ui/card_organization_statistics"; import Header from "../components/ui/navbar_organization"; const DashboardPage = () => { - // --- MOVE HOOKS INSIDE THE FUNCTION BODY --- + // This is for the overlay modal const [isModalOpen, setIsModalOpen] = useState(false); const [selectedItemData, setSelectedItemData] = useState(null); From a62923bd07f38d50e24b50214394b7c069b0b2ec Mon Sep 17 00:00:00 2001 From: sylee212 Date: Fri, 19 Dec 2025 23:01:53 +0800 Subject: [PATCH 13/54] Rename the component for dashboard --- client/src/pages/organization_dashboard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index 4334770..beb8256 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -1,4 +1,4 @@ -// src/pages/organization_dashboard.tsx (Update the import path) +// src/pages/organization_dashboard.tsx // The Header component is now one directory level up from 'pages', // so the path is '../components/Header' @@ -14,7 +14,7 @@ import RecentActivityPanel from "../components/ui/card_organization_recent_activ import StatisticsCard from "../components/ui/card_organization_statistics"; import Header from "../components/ui/navbar_organization"; -const DashboardPage = () => { +const Organization_Dashboard = () => { // This is for the overlay modal const [isModalOpen, setIsModalOpen] = useState(false); const [selectedItemData, setSelectedItemData] = @@ -102,4 +102,4 @@ const DashboardPage = () => { }; // export to make the function available to other parts of the app -export default DashboardPage; +export default Organization_Dashboard; From 7c32b05cace782884f9f7e03392bbb171f818d62 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Sat, 20 Dec 2025 03:00:23 +0800 Subject: [PATCH 14/54] added SWR for polling from backend --- client/package-lock.json | 32 ++++++++++++++++++++++++++++++++ client/package.json | 1 + 2 files changed, 33 insertions(+) diff --git a/client/package-lock.json b/client/package-lock.json index 46f7ac1..4df6af7 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -20,6 +20,7 @@ "next": "15.4.7", "react": "19.1.0", "react-dom": "19.1.0", + "swr": "^2.3.8", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7" }, @@ -2574,6 +2575,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -6592,6 +6602,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.8.tgz", + "integrity": "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/tailwind-merge": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", @@ -6937,6 +6960,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/client/package.json b/client/package.json index ce70203..ad72587 100644 --- a/client/package.json +++ b/client/package.json @@ -27,6 +27,7 @@ "next": "15.4.7", "react": "19.1.0", "react-dom": "19.1.0", + "swr": "^2.3.8", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7" }, From aafb49536972ec8ec7bedae5172867a84f680be4 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Sat, 27 Dec 2025 21:44:57 +0800 Subject: [PATCH 15/54] manage to get the mock data, now working on transferring the data to other components like panel and item itself --- client/src/hooks/organization_call_backend.ts | 77 +++++ .../hooks/organization_clean_backend_calls.ts | 278 ++++++++++++++++++ client/src/pages/organization_dashboard.tsx | 18 ++ 3 files changed, 373 insertions(+) create mode 100644 client/src/hooks/organization_call_backend.ts create mode 100644 client/src/hooks/organization_clean_backend_calls.ts diff --git a/client/src/hooks/organization_call_backend.ts b/client/src/hooks/organization_call_backend.ts new file mode 100644 index 0000000..5369438 --- /dev/null +++ b/client/src/hooks/organization_call_backend.ts @@ -0,0 +1,77 @@ +// src/hooks/organization_call_backend.ts +// Change this URL to match your backend API endpoint + +import { Inventory_Details_Interface } from "@/components/ui/card_organization_inventory_details_modal"; +import { generateRandomMockInventoryDetails } from "@/mocks/Inventory_Details_Interface_Mocks"; + +// tha main URL +export const BASE_URL = "http://localhost:8000/api/activities/"; + +// change when backend is ready +const isDev: boolean = true; + +// --- GET: Fetch all items --- +export const getItems = async ( + filterType: string, +): Promise => { + if (isDev) { + // Mock data for development + const mockData: Inventory_Details_Interface[] = []; + for (let i = 0; i < 10; i++) { + mockData.push(generateRandomMockInventoryDetails()); + } + console.log("Mock data generated:", mockData); + return mockData; + } else { + // 1. Fetch from Django + // fetch() is used to get the data from backend using URL + const response = await fetch(`${BASE_URL}?type=${filterType}`); + + // 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 +// possible types: text/plain, text/html application/json, images/jpeg, application/x-www-form-urlencoded ( form data ), +// if you dont have this, might get error 400 +export const createItem = async (newData: Inventory_Details_Interface) => { + const response = await fetch(BASE_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(newData), + }); + return await response.json(); +}; + +// --- PATCH/PUT: Update an existing item --- +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", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(newData), + }); + return await response.json(); +}; + +// --- DELETE: Remove an item --- +export const deleteItem = async (id: number) => { + const response = await fetch(`${BASE_URL}${id}/`, { + method: "DELETE", + }); + // DELETE usually returns a 204 No Content status, so we don't always .json() it + return response.ok; +}; diff --git a/client/src/hooks/organization_clean_backend_calls.ts b/client/src/hooks/organization_clean_backend_calls.ts new file mode 100644 index 0000000..274ecb6 --- /dev/null +++ b/client/src/hooks/organization_clean_backend_calls.ts @@ -0,0 +1,278 @@ +// src/hooks/organization_clean_backend_calls.ts +import useSWR, { KeyedMutator } from "swr"; + +import type { Inventory_Details_Interface } from "../components/ui/card_organization_inventory_details_modal"; +import { BASE_URL,getItems } from "./organization_call_backend"; + +// remember to +// cd intermediate_team_4 +// cd client +// npm install swr + +// the interface for the return +export interface organization_clean_backend_calls_return_interface { + data: Inventory_Details_Interface[]; + loading: boolean; + error: Error | null; + 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 + +Remember, this will only be invoked once, when its mounted, if you want to call it peridocally, you need to set up a timer to call it periodically +*/ + +// 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 use useSWR + +filterType: used to add to backend url call +isDev: true if we want to use mock data +*/ +export const useRecentActivities = ( + filterType: string, +): organization_clean_backend_calls_return_interface => { + // 2. SWR handles the state, the effect, and the async logic + // useSWR(key, fetcher, options) + // why use it? shows cache data while it fetches new data + // key: API url + // fetcher: a function that returns a promise + // options: common options are: refreshInterval, revalidateOnFocus, revalidateOnReconnect, dedumpingInterval + + // more on fetcher + // This is your fetcher normally + // const fetcher = (url) => fetch(url).then(res => res.json()); + // useSWR is like UberEats app + // fetcher is delivery driver that fetches the data + // const { data } = useSWR('http://localhost:8000/api/data/', fetcher); + + // returns: data, error, isLoading, isValidating, mutate + // data: the data returned by the fetcher function + // error: the error returned by the fetcher function + // isLoading: true if the data is being fetched, false otherwise + // isValidating: true if the data is being validated, false otherwise + // mutate: a function that triggers a new fetch + + /* + // old + const { data, error, isLoading } = useSWR( + `/api/data?type=${filterType}`, + fetcher, + { refreshInterval: 10000 } // This handles the "polling" automatically! + ); + + return { + data: data || [], + loading: isLoading, + error + }; + */ + + // SWR syntax: useSWR(key, fetcher, options) + // why do we need an array ? and not just use filter type? + // because this array will become a key ( cache collision prevention ) + // lets say we dont have the base URL + // and we have data and inventory in the same filter type which is Electronics + // const { data } = useSWR('Electronics', () => getActivities('Electronics')); + // const { data } = useSWR('Electronics', () => getInventory('Electronics')); + // lets say i pull data from data first, it will cache Electronics data + // SWR will think oh, the keys are the same, so lets just use the cache + // this is cache collission + + // 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 } = useSWR( + [`${BASE_URL}`, filterType], // The "Key" (Unique identifier) + fetcher, // The "Fetcher" (Your function) + { + refreshInterval: 3, // Poll every 30 seconds 30000ms + revalidateOnFocus: true, // Refresh when user clicks back into the tab + }, + ); + + const res: organization_clean_backend_calls_return_interface = { + data: data || [], + loading: isLoading, + error: error, + refresh: mutate, // SWR calls its refresh function "mutate" + }; + + return res; +}; + +/* to put inside the _app.js, it will set a global fetch + +import { SWRConfig } from 'swr'; + +function MyApp({ Component, pageProps }) { + return ( + fetch(resource, init).then(res => res.json()), + refreshInterval: 10000, // Optional: Poll for new data every 10 seconds globally + }} + > + + + ); +} + +*/ + +/* + +// filterType: used to add to backend url call +// isDev: true if we want to use mock data + +export const getRecentActivities = (filterType: string, mode: string ) => { + + if (isDev) + { + + } + else + { + // 2. SWR handles the state, the effect, and the async logic + // useSWR(key, fetcher, options) + // why use it? shows cache data while it fetches new data + // key: API url + // fetcher: a function that returns a promise + // options: common options are: refreshInterval, revalidateOnFocus, revalidateOnReconnect, dedumpingInterval + + // more on fetcher + // This is your fetcher normally + // const fetcher = (url) => fetch(url).then(res => res.json()); + // useSWR is like UberEats app + // fetcher is delivery driver that fetches the data + // const { data } = useSWR('http://localhost:8000/api/data/', fetcher); + + // returns: data, error, isLoading, isValidating, mutate + // data: the data returned by the fetcher function + // error: the error returned by the fetcher function + // isLoading: true if the data is being fetched, false otherwise + // isValidating: true if the data is being validated, false otherwise + // mutate: a function that triggers a new fetch + + const { data, error, isLoading } = useSWR( + `/api/data?type=${filterType}`, + fetcher, + { refreshInterval: 10000 } // This handles the "polling" automatically! + ); + + return { + data: data || [], + loading: isLoading, + error + }; + } +}; +*/ + +// // 2. "Cleaning" and "Operations" (Sorting/Formatting) +// const cleanedData = json.map((item: any) => ({ +// id: item.id, +// title: item.action_name, // Mapping Django snake_case to Frontend camelCase +// detail: item.target_object, +// time: formatMyDate(item.timestamp), // Formatting logic +// status: item.change_type === 'increase' ? 'up' : 'down' +// })); + +// setData(cleanedData); + +/* How to use swr original without global fetch + +import useSWR from 'swr'; + +// 1. Define a simple fetcher function (standard for SWR) +const fetcher = (url: string) => fetch(url).then(res => res.json()); + +export const useActivities = (filterType: string) => { + // 2. SWR handles the state, the effect, and the async logic + // useSWR(key, fetcher, options) + // why use it? shows cache data while it fetches new data + // key: API url + // fetcher: a function that returns a promise + // options: common options are: refreshInterval, revalidateOnFocus, revalidateOnReconnect, dedumpingInterval + + // more on fetcher + // This is your fetcher normally + // const fetcher = (url) => fetch(url).then(res => res.json()); + // useSWR is like UberEats app + // fetcher is delivery driver that fetches the data + // const { data } = useSWR('http://localhost:8000/api/data/', fetcher); + + // returns: data, error, isLoading, isValidating, mutate + // data: the data returned by the fetcher function + // error: the error returned by the fetcher function + // isLoading: true if the data is being fetched, false otherwise + // isValidating: true if the data is being validated, false otherwise + // mutate: a function that triggers a new fetch + + const { data, error, isLoading } = useSWR( + `/api/data?type=${filterType}`, + fetcher, + { refreshInterval: 10000 } // This handles the "polling" automatically! + ); + + return { + data: data || [], + loading: isLoading, + error + }; +}; + + + + + + +//This function is used to invoke the get/post/put/delete calls to the backend for the data data +const useActivities = (filterType: string) => { + // this is the data from the backend + const [data, setData] = useState([]); + + // this is to update the state of the data + const [loading, setLoading] = useState(true); + + // runs code on the backend as a side effect + // the idea is, to useEffect, is used to decide on the timing + // bridge between react rendering and calling backend + // makes sure that the code rendering is not blocked by the backend call + // it will rerun if the second parameter changes useEffect(() => {}, [second parameter]) + // it can also stop backend task + useEffect(() => { + + // async and await + // tells js how to handle task that takes time to finish + // so how does the 2 pair? + // when the filterType ( second param ) changes, triggers useEffect + // useEffect calls fetchData + // fetchData is async, so it starts the task and moves on + // when fetchData finishes, it updates the state of data and loading + const fetchData = async () => { + setLoading(true); + + // 1. Fetch from Django + // const response = await fetch(`http://localhost:8000/api/data/?type=${filterType}`); + // const json = await response.json(); + + // setData(cleanedData); + // setLoading(false); + + + setLoading(false); + }; + + fetchData(); + }, [filterType]); // Re-run if the filter changes + + return { data, loading }; +}; +*/ diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index beb8256..6e60c72 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -7,6 +7,10 @@ import { useState } from "react"; import Inventory_Details_Modal, { Inventory_Details_Interface, } from "@/components/ui/card_organization_inventory_details_modal"; +import { + organization_clean_backend_calls_return_interface, + useRecentActivities, +} from "@/hooks/organization_clean_backend_calls"; import { generateRandomMockInventoryDetails } from "@/mocks/Inventory_Details_Interface_Mocks"; import QuickActions from "../components/ui/button_quick_actions"; @@ -15,6 +19,19 @@ import StatisticsCard from "../components/ui/card_organization_statistics"; import Header from "../components/ui/navbar_organization"; const Organization_Dashboard = () => { + // this is for calling the data for the modal + const { + data, + loading, + error, + refresh, + }: organization_clean_backend_calls_return_interface = + useRecentActivities("all"); + console.log("Data from useRecentActivities:", data); + console.log("Loading state:", loading); + console.log("Error state:", error); + console.log("Refresh function:", refresh); + // This is for the overlay modal const [isModalOpen, setIsModalOpen] = useState(false); const [selectedItemData, setSelectedItemData] = @@ -39,6 +56,7 @@ const Organization_Dashboard = () => {
{/* 2. Main content wrapper */} + {/*

{JSON.stringify(data)}

*/}
{/* Components for Statistics, Recent Activity, and Quick Actions */} {/* Statistics Cards */} From 214baada69f6fa339289dfd5fdcb9cccd3f7ccdd Mon Sep 17 00:00:00 2001 From: sylee212 Date: Sat, 27 Dec 2025 21:49:37 +0800 Subject: [PATCH 16/54] fixed some error with the type in the interface --- client/src/hooks/organization_clean_backend_calls.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/hooks/organization_clean_backend_calls.ts b/client/src/hooks/organization_clean_backend_calls.ts index 274ecb6..8447707 100644 --- a/client/src/hooks/organization_clean_backend_calls.ts +++ b/client/src/hooks/organization_clean_backend_calls.ts @@ -2,7 +2,7 @@ import useSWR, { KeyedMutator } from "swr"; import type { Inventory_Details_Interface } from "../components/ui/card_organization_inventory_details_modal"; -import { BASE_URL,getItems } from "./organization_call_backend"; +import { BASE_URL, getItems } from "./organization_call_backend"; // remember to // cd intermediate_team_4 @@ -14,7 +14,7 @@ export interface organization_clean_backend_calls_return_interface { data: Inventory_Details_Interface[]; loading: boolean; error: Error | null; - refresh: () => KeyedMutator; // Optional refresh function + refresh: KeyedMutator; // Optional refresh function } /* From 87c3474cceceedb9d01e381661e9764f90192243 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Sun, 28 Dec 2025 19:55:16 +0800 Subject: [PATCH 17/54] managed to produce data, send the data to the overlays and works, also changed the timer in the clean backend call, pending is the time calc --- ...rd_organization_recent_activities_item.tsx | 35 ++-- ...d_organization_recent_activities_panel.tsx | 171 ++---------------- .../hooks/organization_clean_backend_calls.ts | 2 +- .../Inventory_Details_Interface_Mocks.tsx | 151 ++++++++++++++++ client/src/pages/organization_dashboard.tsx | 14 +- client/src/styles/organization.css | 116 ++++++++++++ 6 files changed, 307 insertions(+), 182 deletions(-) diff --git a/client/src/components/ui/card_organization_recent_activities_item.tsx b/client/src/components/ui/card_organization_recent_activities_item.tsx index ed24730..999215c 100644 --- a/client/src/components/ui/card_organization_recent_activities_item.tsx +++ b/client/src/components/ui/card_organization_recent_activities_item.tsx @@ -2,26 +2,25 @@ import React from "react"; +import { Inventory_Details_Interface } from "./card_organization_inventory_details_modal"; + // Define the shape of the data for a single activity item interface Recent_Activities_Item_Interface { - id: number; // Ensure id is part of the interface type: "increase" | "decrease" | "member" | null; status: "up" | "down"; - title: string; - detail: string; - quantity: number; time: string; - onItemClick: (id: number) => void; // Click handler is mandatory here to work + + data: Inventory_Details_Interface; + + onItemClick: (data: Inventory_Details_Interface) => void; // Click handler is mandatory here to work } const Recent_Activities_Item: React.FC = ({ - id, // You must destructure 'id' here to use it below type, status, - title, - detail, - quantity, time, + data, + onItemClick, }) => { // Logic to determine arrow symbol and color @@ -32,13 +31,13 @@ const Recent_Activities_Item: React.FC = ({ status === "up" ? "var(--color-success)" : "var(--color-error)"; // Format quantity to include + or - sign - const formattedQuantity = `${quantity > 0 ? "+" : ""}${quantity}`; + // const formattedQuantity = `${quantity > 0 ? "+" : ""}${quantity}`; return ( /* When clicked, it sends its specific 'id' back up the chain to the Page */
onItemClick(id)} + onClick={() => onItemClick(data)} style={{ cursor: "pointer" }} >
@@ -46,15 +45,12 @@ const Recent_Activities_Item: React.FC = ({
-

{title}

-

{detail}

+

{data.name}

+

{data.details}

{type}

- - {formattedQuantity} - {time}
@@ -62,3 +58,10 @@ const Recent_Activities_Item: React.FC = ({ }; export default Recent_Activities_Item; + +/* + + + {formattedQuantity} + +*/ diff --git a/client/src/components/ui/card_organization_recent_activities_panel.tsx b/client/src/components/ui/card_organization_recent_activities_panel.tsx index 75c6651..9c964bb 100644 --- a/client/src/components/ui/card_organization_recent_activities_panel.tsx +++ b/client/src/components/ui/card_organization_recent_activities_panel.tsx @@ -13,164 +13,18 @@ for each one, passing in the data and placing it under the div with class activi import Link from "next/link"; // For the 'View All' link import React from "react"; +import { Inventory_Details_Interface } from "./card_organization_inventory_details_modal"; import Recent_Activities_Item from "./card_organization_recent_activities_item"; -// Example data structure that the component might use (or receive as props later) -const mockActivityData = [ - { - id: 1, - type: "member" as const, - status: "up" as const, - title: "New Member", - detail: "Cody", - quantity: 1, - time: "5 minutes ago", - }, - { - id: 2, - type: "member" as const, - status: "up" as const, - title: "Inventory increase", - detail: "Cool Potato", - quantity: 1, - time: "5 minutes ago", - }, - { - id: 3, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 4, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 5, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 6, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 7, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 8, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 9, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 10, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 11, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 12, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 13, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 14, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 15, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - { - id: 16, - type: "member" as const, - status: "down" as const, - title: "Lost Member", - detail: "Cody", - quantity: -1, - time: "3 minutes ago", - }, - // Add more items here... -]; - // Define the interface for the Panel so it knows it receives onItemClick -interface Recent_Activity_Panel_Props { - onItemClick: (id: number) => void; +interface Recent_Activity_Panel_Interface { + onItemClick: (data: Inventory_Details_Interface) => void; + data: Inventory_Details_Interface[]; } -const Recent_Activity_Panel: React.FC = ({ +const Recent_Activity_Panel: React.FC = ({ onItemClick, + data, }) => { return (
@@ -187,16 +41,13 @@ const Recent_Activity_Panel: React.FC = ({
{ // Use the JavaScript map function to render one RecentActivityItem for each data entry - mockActivityData.map((item, index) => ( + data.map((item, index) => ( 0.5 ? "up" : "down"} // Random status for demo + time="Just now" // Placeholder, adjust as needed + data={item} // PASS THE HANDLER DOWN TO THE ITEM onItemClick={onItemClick} /> diff --git a/client/src/hooks/organization_clean_backend_calls.ts b/client/src/hooks/organization_clean_backend_calls.ts index 8447707..d062ec3 100644 --- a/client/src/hooks/organization_clean_backend_calls.ts +++ b/client/src/hooks/organization_clean_backend_calls.ts @@ -92,7 +92,7 @@ export const useRecentActivities = ( [`${BASE_URL}`, filterType], // The "Key" (Unique identifier) fetcher, // The "Fetcher" (Your function) { - refreshInterval: 3, // Poll every 30 seconds 30000ms + refreshInterval: 30000, // Poll every 30 seconds 30000ms revalidateOnFocus: true, // Refresh when user clicks back into the tab }, ); diff --git a/client/src/mocks/Inventory_Details_Interface_Mocks.tsx b/client/src/mocks/Inventory_Details_Interface_Mocks.tsx index 1f2d006..7656869 100644 --- a/client/src/mocks/Inventory_Details_Interface_Mocks.tsx +++ b/client/src/mocks/Inventory_Details_Interface_Mocks.tsx @@ -79,3 +79,154 @@ export const generateRandomMockInventoryDetails = expiryDate: getRandomDate(now, nextYear), // Somewhere in the next year }; }; + +/* + // Example data structure that the component might use (or receive as props later) +const mockActivityData = [ + { + id: 1, + type: "member" as const, + status: "up" as const, + title: "New Member", + detail: "Cody", + quantity: 1, + time: "5 minutes ago", + }, + { + id: 2, + type: "member" as const, + status: "up" as const, + title: "Inventory increase", + detail: "Cool Potato", + quantity: 1, + time: "5 minutes ago", + }, + { + id: 3, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 4, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 5, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 6, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 7, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 8, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 9, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 10, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 11, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 12, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 13, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 14, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 15, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + { + id: 16, + type: "member" as const, + status: "down" as const, + title: "Lost Member", + detail: "Cody", + quantity: -1, + time: "3 minutes ago", + }, + // Add more items here... +]; +*/ diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index 6e60c72..41df4ab 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -11,7 +11,6 @@ import { organization_clean_backend_calls_return_interface, useRecentActivities, } from "@/hooks/organization_clean_backend_calls"; -import { generateRandomMockInventoryDetails } from "@/mocks/Inventory_Details_Interface_Mocks"; import QuickActions from "../components/ui/button_quick_actions"; import RecentActivityPanel from "../components/ui/card_organization_recent_activities_panel"; @@ -39,10 +38,15 @@ const Organization_Dashboard = () => { /* We will need to have a function here as well to handle the getting of the data + the onCLick handler is at this level because the modal is here */ - const handleItemClick = (itemId: number) => { - console.log("Item clicked with ID:", itemId); - setSelectedItemData(generateRandomMockInventoryDetails()); + const handleItemClick = (data: Inventory_Details_Interface) => { + console.log("Item clicked: ", data); + + // setSelectedItemData(generateRandomMockInventoryDetails()); + setSelectedItemData(data); + + // to open the modal setIsModalOpen(true); // PENDING, remove after figuring out how to pass the correct data @@ -99,7 +103,7 @@ const Organization_Dashboard = () => {
{/* LEFT COLUMN: Recent Activity, used section here to group some assets */}
- +
{/* 2. RIGHT COLUMN: Quick Actions, aside here is used for accessibility, it does not make it appear on the right*/} diff --git a/client/src/styles/organization.css b/client/src/styles/organization.css index 9893427..0c84d0a 100644 --- a/client/src/styles/organization.css +++ b/client/src/styles/organization.css @@ -425,6 +425,13 @@ gap: 30px: adds space between the two columns line-height: 1.2; } +.activity-type { + font-size: 13px; + color: #888; + margin: 0; + line-height: 1.2; +} + .activity-meta { /* Quantity and Time (right-aligned) */ display: flex; @@ -500,4 +507,113 @@ gap: 30px: adds space between the two columns .inv-item-detail { font-weight: 400; color: #4a77e5; /* Make the status detail blue for visibility */ +} + +/* src/styles/organization.css */ +/* Overlay (The dark background covering the whole screen) */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.6); /* Semi-transparent black */ + display: flex; + justify-content: center; + align-items: center; + z-index: 2000; /* Ensure it's above everything else */ +} + +/* Modal Content (The white card) */ +.modal-content { + background-color: #ffffff; + border-radius: 12px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); + width: 90%; + max-width: 500px; /* Limit the width for a comfortable read */ + padding: 20px; + position: relative; + font-family: sans-serif; +} + +/* Header and Close Button */ +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.modal-title { + font-size: 24px; + font-weight: 700; + margin: 0; +} + +.modal-close-button { + background: none; + border: none; + cursor: pointer; + padding: 0; + font-size: 24px; + font-weight: 900; + width: 30px; + height: 30px; + background-color: #dc3545; /* Red background for the X */ + color: white; + border-radius: 4px; +} + +/* Details Rows (Key-Value pairs) */ +.modal-details-section { + line-height: 1.4; + padding-bottom: 20px; +} + +.modal-detail-row { + display: flex; + margin-bottom: 8px; + font-size: 16px; +} + +.modal-detail-label { + font-weight: 600; + min-width: 150px; /* Align the values vertically */ + color: #555; +} + +.modal-detail-value { + font-weight: 400; + color: #333; +} + +/* 5. Action Buttons */ +.modal-actions { + display: flex; + justify-content: space-between; + gap: 15px; + padding-top: 15px; + border-top: 1px solid #eee; +} + +.modal-action-button { + flex-grow: 1; + padding: 15px 10px; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; + text-align: center; +} + +.returned-button { + background-color: #e0e0e0; + color: #333; +} + +.modify-button { + background-color: #4a77e5; + color: white; } \ No newline at end of file From 0a3a224a935041c490182dc60d8d84758b2b362a Mon Sep 17 00:00:00 2001 From: sylee212 Date: Tue, 30 Dec 2025 20:11:04 +0800 Subject: [PATCH 18/54] finished testing the minutes ago --- ...d_organization_recent_activities_panel.tsx | 19 +++++++++- .../hooks/organization_clean_backend_calls.ts | 2 +- .../Inventory_Details_Interface_Mocks.tsx | 37 +++++++++++++++---- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/client/src/components/ui/card_organization_recent_activities_panel.tsx b/client/src/components/ui/card_organization_recent_activities_panel.tsx index 9c964bb..edda59e 100644 --- a/client/src/components/ui/card_organization_recent_activities_panel.tsx +++ b/client/src/components/ui/card_organization_recent_activities_panel.tsx @@ -8,6 +8,10 @@ How does it show all the components in the list? so what happens is, it will run the map and iterate over every item and then create a RecentActivityItem component for each one, passing in the data and placing it under the div with class activity-list + +Note: +the data must already be sorted from the backend before passing the data here. this componenet is just for display. no processing is done here + */ import Link from "next/link"; // For the 'View All' link @@ -22,6 +26,19 @@ interface Recent_Activity_Panel_Interface { data: Inventory_Details_Interface[]; } +const calcTimeAgo = (dateString: string): string => { + const date = new Date(dateString); + const now = new Date(); + const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); + if (diffInSeconds < 60) return `${diffInSeconds} seconds ago`; + const diffInMinutes = Math.floor(diffInSeconds / 60); + if (diffInMinutes < 60) return `${diffInMinutes} minutes ago`; + const diffInHours = Math.floor(diffInMinutes / 60); + if (diffInHours < 24) return `${diffInHours} hours ago`; + const diffInDays = Math.floor(diffInHours / 24); + return `${diffInDays} days ago`; +}; + const Recent_Activity_Panel: React.FC = ({ onItemClick, data, @@ -46,7 +63,7 @@ const Recent_Activity_Panel: React.FC = ({ key={index} type={null} // Placeholder, adjust as needed status={Math.random() > 0.5 ? "up" : "down"} // Random status for demo - time="Just now" // Placeholder, adjust as needed + time={calcTimeAgo(item.dateAdded)} // Placeholder, adjust as needed data={item} // PASS THE HANDLER DOWN TO THE ITEM onItemClick={onItemClick} diff --git a/client/src/hooks/organization_clean_backend_calls.ts b/client/src/hooks/organization_clean_backend_calls.ts index d062ec3..5ac1353 100644 --- a/client/src/hooks/organization_clean_backend_calls.ts +++ b/client/src/hooks/organization_clean_backend_calls.ts @@ -92,7 +92,7 @@ export const useRecentActivities = ( [`${BASE_URL}`, filterType], // The "Key" (Unique identifier) fetcher, // The "Fetcher" (Your function) { - refreshInterval: 30000, // Poll every 30 seconds 30000ms + refreshInterval: 30, // Poll every 30 seconds 30000ms revalidateOnFocus: true, // Refresh when user clicks back into the tab }, ); diff --git a/client/src/mocks/Inventory_Details_Interface_Mocks.tsx b/client/src/mocks/Inventory_Details_Interface_Mocks.tsx index 7656869..a0f4f5c 100644 --- a/client/src/mocks/Inventory_Details_Interface_Mocks.tsx +++ b/client/src/mocks/Inventory_Details_Interface_Mocks.tsx @@ -34,21 +34,25 @@ const users = ["Arush", "Cody", "Jane Doe", "Alex Smith", "System"]; */ // 1. Helper Function: Generates a random date string -const getRandomDate = (start: Date, end: Date): string => { +const getRandomDateTime = (start: Date, end: Date): string => { const date = new Date( start.getTime() + Math.random() * (end.getTime() - start.getTime()), ); - // Formats to "DD MMM YYYY" (e.g., 10 Nov 2025) - return date.toLocaleDateString("en-GB", { + // Formats to e.g., "10 Nov 2025, 14:30" + return date.toLocaleString("en-GB", { day: "2-digit", month: "short", year: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: false, // Use true for AM/PM }); }; // 2. Mock data for the overlay fields const now = new Date(); +// const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); const lastMonth = new Date( now.getFullYear(), now.getMonth() - 1, @@ -56,14 +60,30 @@ const lastMonth = new Date( ); const nextYear = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate()); +// --- Random time within TODAY --- +// const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0); +// const endOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59); + +// const startHour = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 10, 0); +// const endHour = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 1, 59); + +// const oneHourAgo = new Date(now.getTime() - (60 * 60 * 1000)); // Current time minus 3,600,000 milliseconds +const currentTime = now; + +const oneMinuteAgo = new Date(now.getTime() - 1 * 60 * 1000); // Current time minus 60,000 milliseconds + +// const randomTimeToday = getRandomDateTime(startOfToday, endOfToday); + export const generateRandomMockInventoryDetails = (): Inventory_Details_Interface => { // Helper to pick a random item from an array const getRandom = (arr: string[]) => arr[Math.floor(Math.random() * arr.length)]; + const id = Math.floor(Math.random() * 10); // Random ID between 0-9 + return { - id: Math.floor(Math.random() * 10000), // Random ID between 0-9999 + id: id, // Random ID between 0-9 name: getRandom(names), details: getRandom(details), categories: getRandom(categories), @@ -73,10 +93,11 @@ export const generateRandomMockInventoryDetails = borrowerName: getRandom(users), // RANDOMLY GENERATED DATES: - borrowedOn: getRandomDate(lastMonth, now), // Somewhere in the last 30 days - returnedOn: getRandomDate(now, now), // Today - dueOn: getRandomDate(now, nextYear), // Somewhere in the next year - expiryDate: getRandomDate(now, nextYear), // Somewhere in the next year + borrowedOn: getRandomDateTime(lastMonth, now), // Somewhere in the last 30 days + returnedOn: getRandomDateTime(now, now), // Today + dueOn: getRandomDateTime(now, nextYear), // Somewhere in the next year + expiryDate: getRandomDateTime(now, nextYear), // Somewhere in the next year + dateAdded: getRandomDateTime(oneMinuteAgo, currentTime), // Somewhere between yesteday and today }; }; From 00c55ee1403bc9d673b91e0a8167984e044b3228 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Tue, 30 Dec 2025 20:13:08 +0800 Subject: [PATCH 19/54] rename some variables --- .../ui/card_organization_statistics.tsx | 12 ++++++------ client/src/pages/organization_dashboard.tsx | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/client/src/components/ui/card_organization_statistics.tsx b/client/src/components/ui/card_organization_statistics.tsx index 1d021d0..8881fc2 100644 --- a/client/src/components/ui/card_organization_statistics.tsx +++ b/client/src/components/ui/card_organization_statistics.tsx @@ -1,4 +1,4 @@ -// src/components/ui/StatisticsCard.tsx +// src/components/ui/Statistics_Card.tsx import React from "react"; @@ -8,14 +8,14 @@ This component represents a single statistics card used in the organization dash So when you use this interaface, you can put in this data example usage: - */ -interface StatisticsCardProps { +interface Statistics_Card_Interface { title: string; // The card title (e.g., "Total Inventory") value: number; // The main numerical value (e.g., 30) delta: string; // The change string (e.g., "+3 this week") @@ -23,13 +23,13 @@ interface StatisticsCardProps { } /* -React.FC +React.FC - React.FC<> stands for React Functional Component { title, value, delta, status } - Destructuring the props object to directly access title, value, delta, and status */ -const StatisticsCard: React.FC = ({ +const Statistics_Card: React.FC = ({ title, value, delta, @@ -64,4 +64,4 @@ const StatisticsCard: React.FC = ({ ); }; -export default StatisticsCard; +export default Statistics_Card; diff --git a/client/src/pages/organization_dashboard.tsx b/client/src/pages/organization_dashboard.tsx index 41df4ab..04e1335 100644 --- a/client/src/pages/organization_dashboard.tsx +++ b/client/src/pages/organization_dashboard.tsx @@ -13,8 +13,8 @@ import { } from "@/hooks/organization_clean_backend_calls"; import QuickActions from "../components/ui/button_quick_actions"; -import RecentActivityPanel from "../components/ui/card_organization_recent_activities_panel"; -import StatisticsCard from "../components/ui/card_organization_statistics"; +import Recent_Activity_Panel from "../components/ui/card_organization_recent_activities_panel"; +import Statistics_Card from "../components/ui/card_organization_statistics"; import Header from "../components/ui/navbar_organization"; const Organization_Dashboard = () => { @@ -67,31 +67,31 @@ const Organization_Dashboard = () => {
{" "} {/* This class will control the layout of the 5 cards */} - - - - - {
{/* LEFT COLUMN: Recent Activity, used section here to group some assets */}
- +
{/* 2. RIGHT COLUMN: Quick Actions, aside here is used for accessibility, it does not make it appear on the right*/} From 16a40f9bf335241d891df52f413bf53f2f1df55b Mon Sep 17 00:00:00 2001 From: sylee212 Date: Tue, 30 Dec 2025 20:19:18 +0800 Subject: [PATCH 20/54] renamed --- client/src/components/ui/button_quick_actions.tsx | 6 +++--- client/src/pages/organization_dashboard.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/components/ui/button_quick_actions.tsx b/client/src/components/ui/button_quick_actions.tsx index b6539fe..17b98b6 100644 --- a/client/src/components/ui/button_quick_actions.tsx +++ b/client/src/components/ui/button_quick_actions.tsx @@ -2,7 +2,7 @@ import React from "react"; -const QuickActions = () => { +const Quick_Actions = () => { return (

Quick Actions

@@ -10,7 +10,7 @@ const QuickActions = () => {
{/* 2. RIGHT COLUMN: Quick Actions */}
From 2dcacb5a866435a980cd641a78ff422c6faaf349 Mon Sep 17 00:00:00 2001 From: sylee212 Date: Wed, 31 Dec 2025 01:37:45 +0800 Subject: [PATCH 21/54] minor changes to path dependencies --- client/src/components/ui/navbar_organization.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/ui/navbar_organization.tsx b/client/src/components/ui/navbar_organization.tsx index 20dae1c..d3c08a9 100644 --- a/client/src/components/ui/navbar_organization.tsx +++ b/client/src/components/ui/navbar_organization.tsx @@ -28,7 +28,7 @@ onMouseDown vs onClick */ // this is for the dropdown menu from user profile side -import Link from "next/dist/client/link"; +import Link from "next/link"; import React, { useState } from "react"; // <-- Import useState const Header = () => { From cb5b3df37aecea8ca3833fe539bb2d9177a7c1df Mon Sep 17 00:00:00 2001 From: sylee212 Date: Wed, 31 Dec 2025 01:39:59 +0800 Subject: [PATCH 22/54] added paths to the new pages --- .../components/ui/button_quick_actions.tsx | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/client/src/components/ui/button_quick_actions.tsx b/client/src/components/ui/button_quick_actions.tsx index 17b98b6..4a50e22 100644 --- a/client/src/components/ui/button_quick_actions.tsx +++ b/client/src/components/ui/button_quick_actions.tsx @@ -1,5 +1,6 @@ // src/components/button_quick_actions.tsx +import Link from "next/link"; import React from "react"; const Quick_Actions = () => { @@ -12,17 +13,21 @@ const Quick_Actions = () => { From cb63bd96243f05b7f8807927cf9b232f435cbd9b Mon Sep 17 00:00:00 2001 From: sylee212 Date: Wed, 31 Dec 2025 01:41:44 +0800 Subject: [PATCH 23/54] added new page for adding new products --- client/src/pages/organization_add_product.tsx | 105 ++++++++++++++++++ client/src/styles/organization.css | 60 ++++++++++ 2 files changed, 165 insertions(+) create mode 100644 client/src/pages/organization_add_product.tsx diff --git a/client/src/pages/organization_add_product.tsx b/client/src/pages/organization_add_product.tsx new file mode 100644 index 0000000..b479710 --- /dev/null +++ b/client/src/pages/organization_add_product.tsx @@ -0,0 +1,105 @@ +// 1. Swap react-router-dom for next/router +import { useRouter } from "next/router"; +import React, { useState } from "react"; +// import { createItem } from '../hooks/organization_call_backend'; + +const OrganizationAddProduct = () => { + // 2. Initialize the Next.js router + const router = useRouter(); + + const [formData, setFormData] = useState({ + name: "", + details: "", + collectionPoint: "", + expiryDate: "", + }); + + const handleChange = ( + e: React.ChangeEvent, + ) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; + + const handleSave = async (e: React.FormEvent) => { + e.preventDefault(); // This is important to keep the page from refreshing! + + // try { + // await createItem(formData); + // alert("Product Saved Successfully!"); + + // // 3. Navigate back to the dashboard + // router.push('/organization_dashboard'); + // } catch (error) { + // alert("Error saving data"); + // console.error(error); + // } + }; + + return ( +
+

Add New Product

+ +
+
+ + +
+ +
+ +