Ignore:
Timestamp:
05/05/26 17:55:27 (2 months ago)
Author:
Andrej <asumanovski@…>
Branches:
master
Children:
b3d5fed
Parents:
4f85b41
Message:

Custom tracking category feature complete

File:
1 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/pages/Dashboard/components/DashboardSidebar.jsx

    r4f85b41 re9a0543  
    1 import React from "react";
     1import React, { useEffect, useMemo, useState } from "react";
    22import { NavLink, useLocation } from "react-router-dom";
    33
     
    2020  MdChecklist,
    2121  MdAdd,
     22  MdBook,
     23  MdFlag,
     24  MdStar,
     25  MdBolt,
     26  MdNoteAlt,
     27  MdAutoGraph,
    2228} from "react-icons/md";
    2329
    2430import logo from "../../../assets/logo.png";
     31
     32import AddCustomCategoryModal from "./AddCustomCategoryModal.jsx";
     33import {
     34  createCustomTrackingCategory,
     35  getCustomTrackingCategories,
     36} from "../../../api/discipline.js";
    2537
    2638const navItems = [
     
    3345];
    3446
     47const CUSTOM_ICONS = [MdBook, MdFlag, MdStar, MdBolt, MdNoteAlt, MdAutoGraph];
     48
     49function stableIconForId(id) {
     50  const n = Number(id);
     51  if (!Number.isFinite(n)) return CUSTOM_ICONS[0];
     52  return CUSTOM_ICONS[Math.abs(n) % CUSTOM_ICONS.length];
     53}
     54
    3555const DashboardSidebar = () => {
    3656  const location = useLocation();
     57  const [customOpen, setCustomOpen] = useState(false);
     58  const [customCategories, setCustomCategories] = useState([]);
     59
     60  useEffect(() => {
     61    let mounted = true;
     62    (async () => {
     63      try {
     64        const res = await getCustomTrackingCategories();
     65        const items = res?.items ?? [];
     66        if (mounted) setCustomCategories(items);
     67      } catch {
     68        // If the user isn't authenticated yet or the endpoint fails,
     69        // keep the sidebar usable (no custom categories).
     70        if (mounted) setCustomCategories([]);
     71      }
     72    })();
     73    return () => {
     74      mounted = false;
     75    };
     76  }, []);
     77
     78  const customNavItems = useMemo(() => {
     79    return (customCategories ?? []).map((c) => {
     80      const Icon = stableIconForId(c.customTrackingId);
     81      return {
     82        title: c.name,
     83        to: `/dashboard/custom/${c.customTrackingId}`,
     84        icon: Icon,
     85      };
     86    });
     87  }, [customCategories]);
    3788
    3889  return (
     
    52103          {navItems.map((item) => (
    53104            <SidebarMenuItem key={item.title}>
     105              {(() => {
     106                const isActive =
     107                  location.pathname === item.to ||
     108                  location.pathname.startsWith(`${item.to}/`);
     109
     110                return (
     111                  <SidebarMenuButton
     112                    asChild
     113                    isActive={isActive}
     114                    className={
     115                      isActive
     116                        ? "!bg-green-400 !text-black hover:!bg-green-400 active:!bg-green-400"
     117                        : ""
     118                    }
     119                  >
     120                    <NavLink
     121                      to={item.to}
     122                      className="flex w-full items-center gap-3"
     123                    >
     124                      <item.icon
     125                        className={
     126                          isActive
     127                            ? "size-6 shrink-0 text-black"
     128                            : "size-6 shrink-0"
     129                        }
     130                      />
     131                      <span className={isActive ? "text-black" : ""}>
     132                        {item.title}
     133                      </span>
     134                    </NavLink>
     135                  </SidebarMenuButton>
     136                );
     137              })()}
     138            </SidebarMenuItem>
     139          ))}
     140
     141          {customNavItems.length > 0 ? (
     142            <div className="my-4 border-t border-white/10" />
     143          ) : null}
     144
     145          {customNavItems.map((item) => (
     146            <SidebarMenuItem key={`custom-${item.to}`}>
    54147              {(() => {
    55148                const isActive =
     
    94187            className="w-full justify-start gap-2 !bg-green-400 !text-black hover:!bg-green-500"
    95188            type="button"
     189            onClick={() => setCustomOpen(true)}
    96190          >
    97191            <MdAdd className="size-5" />
     
    99193          </Button>
    100194        </div>
     195
     196        <AddCustomCategoryModal
     197          open={customOpen}
     198          onClose={() => setCustomOpen(false)}
     199          onSubmit={async (payload) => {
     200            const created = await createCustomTrackingCategory(payload);
     201            setCustomCategories((prev) => [created, ...(prev ?? [])]);
     202          }}
     203        />
    101204      </SidebarContent>
    102205    </Sidebar>
Note: See TracChangeset for help on using the changeset viewer.