Merge branch 'mel-admin-GEN-102-tag' into varun-admin-GEN-59-page

This commit is contained in:
Varun Murlidhar 2024-04-05 14:15:49 -04:00
commit 5df525577a
24 changed files with 4985 additions and 4523 deletions

Binary file not shown.

View File

@ -0,0 +1,37 @@
"use client"
import Sidebar from '@/components/resource/Sidebar';
import React, { useState } from 'react';
import { ChevronDoubleRightIcon } from '@heroicons/react/24/outline';
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
return (
<div className="flex-row">
{/* button to open sidebar */}
<button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
aria-label={'Open sidebar'}
>
{!isSidebarOpen &&
<ChevronDoubleRightIcon className="h-5 w-5" /> // Icon for closing the sidebar
}
</button>
{/* sidebar */}
<div className={`absolute inset-y-0 left-0 transform ${isSidebarOpen ? 'translate-x-0' : '-translate-x-full'} w-64 transition duration-300 ease-in-out`}>
<Sidebar setIsSidebarOpen={setIsSidebarOpen} />
</div>
{/* page ui */}
<div className={`flex-1 transition duration-300 ease-in-out ${isSidebarOpen ? 'ml-64' : 'ml-0'}`}>
{children}
</div>
</div>
)
}

View File

@ -0,0 +1,22 @@
"use client";
import { PageLayout } from "@/components/PageLayout";
import { Table } from "@/components/Table/Index";
import { UsersIcon } from "@heroicons/react/24/solid";
export default function Page() {
return (
<div className="min-h-screen flex flex-col">
{/* icon + title */}
<PageLayout title="Users" icon={<UsersIcon />}>
<Table />
</PageLayout>
</div>
);
}

View File

@ -1,86 +0,0 @@
// pages/index.tsx
"use client";
import Button from '@/components/Button';
import Input from '@/components/Input'
import InlineLink from '@/components/InlineLink';
import Paper from '@/components/auth/Paper';
// import { Metadata } from 'next'
import Image from 'next/image';
import {ChangeEvent, useState} from "react";
// export const metadata: Metadata = {
// title: 'Login',
// }
export default function Page() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEmail(event.currentTarget.value);
console.log("email " + email);
}
const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setPassword(event.currentTarget.value);
console.log("password " + password)
}
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
// Priority: Incorrect combo > Missing email > Missing password
if (password.trim().length === 0) {
setError("Please enter your password.")
}
// This shouldn't happen, <input type="email"> already provides validation, but just in case.
if (email.trim().length === 0) {
setError("Please enter your email.")
}
// Placeholder for incorrect email + password combo.
if (email === "incorrect@gmail.com" && password) {
setError("Incorrect password.")
}
}
return (
<>
<Paper>
<form className="mb-0 m-auto mt-6 space-y-4 rounded-lg p-4 shadow-lg sm:p-6 lg:p-8 bg-white max-w-xl">
<Image
src="/logo.png"
alt='Compass Center logo.'
width={100}
height={91}
/>
<h1 className='font-bold text-xl text-purple-800'>Login</h1>
<div className="mb-4">
<Input type='email' title="Email" placeholder="janedoe@gmail.com" onChange={handleEmailChange} />
</div>
<div className="mb-6">
<Input type='password' title="Password" onChange={handlePasswordChange} />
</div>
<div className="flex flex-col items-left space-y-4">
<InlineLink href="/forgot_password">
Forgot password?
</InlineLink>
<Button onClick={handleClick}>
Login
</Button>
<div className="text-center text-red-600" hidden={!error}>
<p>{error}</p>
</div>
</div>
</form>
<p className="text-center mt-6 text-gray-500 text-xs">
&copy; 2024 Compass Center
</p>
</Paper>
</>
);
};

View File

@ -1,40 +0,0 @@
// pages/index.tsx
"use client";
import Image from 'next/image';
import Drawer from '@/components/page/Drawer';
// import { Metadata } from 'next'
import {ChangeEvent, useState} from "react";
// export const metadata: Metadata = {
// title: 'Login',
// }
export default function Page() {
const [pageContent, setPageContent] = useState("")
const handleDrawerContentChange = (newContent) => {
setPageContent(newContent);
};
return (
<div className="min-h-screen flex flex-col">
<div className="pt-16 px-8 pb-4 flex-grow">
<div className="mb-4 flex items-center space-x-4">
<Image
src="/logo.png" // Ensure the path to your logo is correct
alt="Compass Center logo"
width={25}
height={25}
// If you are using TypeScript and Next.js Image component, ensure you have set up 'next/image' in your 'next.config.js' for static import
/>
<h1 className="font-bold text-2xl text-purple-800">Untitled Page</h1>
</div>
<Drawer title="Sidebar Component" editableContent={pageContent} onSave={handleDrawerContentChange}>{pageContent}</Drawer>
</div>
</div>
);
};

View File

@ -9,7 +9,7 @@ export default function Page() {
return (
<div className="min-h-screen flex flex-col">
{/* icon + title */}
<div className="pt-16 px-8 pb-4 flex-grow">
<div className="pt-16 px-8 pb-4 flex-row">
<div className="mb-4 flex items-center space-x-4">
<Image
src="/logo.png"

View File

@ -0,0 +1,23 @@
interface PageLayoutProps {
icon: React.ReactElement;
title: string;
children: React.ReactElement
}
export const PageLayout: React.FC<PageLayoutProps> = ({ icon, title, children }) => {
return (
<div className="min-h-screen flex flex-col">
{/* icon + title */}
<div className="pt-16 px-8 pb-4 flex-row">
<div className="mb-4 flex items-center space-x-4">
<span className="w-6 h-6 text-purple-200">{icon}</span>
<h1 className='font-bold text-2xl text-purple-800'>{title}</h1>
</div>
</div>
{/* data */}
<div className="px-8 py-8">
{children}
</div>
</div>
);
};

View File

@ -0,0 +1,45 @@
import { Component } from "react"
// interface TableHeader {
// title: string,
// type: string
// }
// interface TableRow {
// [key: string]: any,
// }
interface TableProps {
headersData: string[];
data: { [key: string]: any }[];
}
const Table: React.FC<TableProps> = ({ headersData, data }) => {
const headers = headersData.map((header, i) => {
return <th key={"header-" + i} >{header}</th>
})
console.log(data);
const rows = data.map((item) => {
const row = headersData.map(key => {
return <td key={"item-" + item.id + key}>{item[key]}</td>
});
return <tr key={"item-" + item.id}>{row}</tr>
})
return (<>
<table>
<thead>
<tr>
{headers}
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</>);
}
export default Table

View File

@ -0,0 +1,113 @@
// for showcasing to compass
import usersImport from "./users.json";
import {
ColumnDef,
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import { useState } from "react";
import { RowOptionMenu } from "./RowOptionMenu";
import { RowOpenAction } from "./RowOpenAction";
import { TableAction } from "./TableAction";
import { AtSymbolIcon, Bars2Icon } from "@heroicons/react/24/solid";
import TagsInput from "../TagsInput/Index";
const usersExample = usersImport as unknown as User[];
type User = {
id: number;
created_at: any;
username: string;
role: "administrator" | "employee" | "volunteer";
email: string;
program: "domestic" | "economic" | "community";
experience: number;
group?: string;
};
export const Table = () => {
const columnHelper = createColumnHelper<User>();
const columns = [
columnHelper.display({
id: "options",
cell: props => <RowOptionMenu />
}),
columnHelper.accessor("username", {
header: () => <><Bars2Icon className="inline align-top h-4" /> Username</>,
cell: (info) => <RowOpenAction title={info.getValue()} />,
}),
columnHelper.accessor("role", {
cell: (info) => <TagsInput presetValue={info.getValue() }presetOptions={["administrator","volunteer","employee"]} />,
}),
columnHelper.accessor("email", {
header: () => <><AtSymbolIcon className="inline align-top h-4" /> Email</>,
cell: (info) => info.renderValue(),
}),
columnHelper.accessor("program", {
cell: (info) => info.renderValue(),
}),
];
const [data, setData] = useState<User[]>([...usersExample]);
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
});
return (
<div className="flex flex-col">
<div className="flex flex-row justify-end">
<TableAction />
</div>
<table className="w-full text-xs text-left rtl:text-right">
<thead className="text-xs text-gray-500 capitalize">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header, i) => (
<th
scope="col"
className={
"p-2 border-gray-200 border-y font-medium "
+ ((1 < i && i < columns.length - 1) ? "border-x" : "")
}
key={header.id}
>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody className="">
{table.getRowModel().rows.map((row) => (
<tr className="text-gray-800 border-y lowercase hover:bg-gray-50" key={row.id}>
{row.getVisibleCells().map((cell, i) => (
<td
className={
"p-2 "
+ ((1 < i && i < columns.length - 1) ? "border-x" : "")
+ ((i === 0) ? "text-center px-0" : "")
}
key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}

View File

@ -0,0 +1,21 @@
import Drawer from "@/components/page/Drawer";
import {ChangeEvent, useState} from "react";
export const RowOpenAction = ({ title }) => {
const [pageContent, setPageContent] = useState("")
const handleDrawerContentChange = (newContent) => {
setPageContent(newContent);
};
return (
<div className="font-semibold group flex flex-row items-center justify-between pr-2">
{title}
<span >
<Drawer title="My Drawer Title" editableContent={pageContent} onSave={handleDrawerContentChange}>{pageContent}</Drawer>
</span>
</div>
);
};

View File

@ -0,0 +1,26 @@
//delete, duplicate, open
import { TrashIcon, DocumentDuplicateIcon, ArrowUpRightIcon, EllipsisVerticalIcon } from "@heroicons/react/24/solid";
import { useState, useEffect, useRef } from "react";
export const RowOptionMenu = () => {
const [menuOpen, setMenuOpen] = useState(false);
const openMenu = () => setMenuOpen(true);
const closeMenu = () => setMenuOpen(false);
// TODO: Hide menu if clicked elsewhere
return (
<>
<button className="align-center" onClick={() => setMenuOpen(!menuOpen)}><EllipsisVerticalIcon className="h-4"/></button>
<div
className={"absolute text-left bg-white w-auto p-1 rounded [&>*]:p-1 [&>*]:px-5 [&>*]:rounded" + (!menuOpen ? " invisible" : "")}
>
<div className="hover:bg-gray-100"><TrashIcon className="inline h-4"/> Delete</div>
<div className="hover:bg-gray-100"><DocumentDuplicateIcon className="inline h-4"/> Duplicate</div>
<div className="hover:bg-gray-100"><ArrowUpRightIcon className="inline h-4"/> Open</div>
</div>
</>
);
}

View File

@ -0,0 +1,36 @@
import { MagnifyingGlassIcon } from "@heroicons/react/24/solid";
import { useRef, useState } from "react";
export const TableAction = () => {
const searchInput = useRef<HTMLInputElement>(null);
const [searchActive, setSearchActive] = useState(false);
const activateSearch = () => {
setSearchActive(true);
if (searchInput.current === null) { return; }
searchInput.current.focus();
searchInput.current.addEventListener("focusout", () => {
if (searchInput.current?.value.trim() === "") {
searchInput.current.value = "";
deactivateSearch();
}
})
}
const deactivateSearch = () => setSearchActive(false);
return (
<div className="w-auto flex flex-row gap-x-0.5 items-center justify-between text-xs font-medium text-gray-500 p-2">
<span className="p-1 rounded hover:bg-gray-100">Filter</span>
<span className="p-1 rounded hover:bg-gray-100">Sort</span>
<span className="p-1 rounded hover:bg-gray-100" onClick={() => activateSearch()}>
<MagnifyingGlassIcon className="w-4 h-4 inline" />
</span>
<input
ref={searchInput}
className={"outline-none transition-all duration-300 " + (searchActive ? "w-48" : "w-0")}
type="text"
name="search"
placeholder="Type to search..." />
</div>
);
};

View File

@ -0,0 +1 @@
[{"id":0,"created_at":1711482132230,"username":"Bo_Pfeffer","role":"ADMIN","email":"Bo.Pfeffer@gmail.com","program":"DOMESTIC","experience":2,"group":""},{"id":1,"created_at":1711482132231,"username":"Marianna_Heathcote76","role":"ADMIN","email":"Marianna_Heathcote14@yahoo.com","program":"DOMESTIC","experience":1,"group":""},{"id":2,"created_at":1711482132231,"username":"Queenie_Schroeder","role":"VOLUNTEER","email":"Queenie_Schroeder@yahoo.com","program":"COMMUNITY","experience":5,"group":""},{"id":3,"created_at":1711482132231,"username":"Arne.Bode","role":"VOLUNTEER","email":"Arne.Bode@hotmail.com","program":"DOMESTIC","experience":3,"group":""},{"id":4,"created_at":1711482132231,"username":"Maia.Zulauf9","role":"ADMIN","email":"Maia_Zulauf@gmail.com","program":"DOMESTIC","experience":5,"group":""},{"id":5,"created_at":1711482132231,"username":"River_Bauch","role":"EMPLOYEE","email":"River.Bauch@yahoo.com","program":"ECONOMIC","experience":2,"group":""},{"id":6,"created_at":1711482132231,"username":"Virgil.Hilll","role":"VOLUNTEER","email":"Virgil.Hilll@yahoo.com","program":"ECONOMIC","experience":3,"group":""},{"id":7,"created_at":1711482132231,"username":"Bridget_Cartwright","role":"ADMIN","email":"Bridget_Cartwright@yahoo.com","program":"ECONOMIC","experience":3,"group":""},{"id":8,"created_at":1711482132231,"username":"Glennie_Keebler64","role":"EMPLOYEE","email":"Glennie_Keebler60@yahoo.com","program":"DOMESTIC","experience":2,"group":""},{"id":9,"created_at":1711482132232,"username":"Orin.Jenkins53","role":"EMPLOYEE","email":"Orin.Jenkins@gmail.com","program":"ECONOMIC","experience":1,"group":""},{"id":10,"created_at":1711482132232,"username":"Zachery.Rosenbaum","role":"ADMIN","email":"Zachery.Rosenbaum@hotmail.com","program":"COMMUNITY","experience":3,"group":""},{"id":11,"created_at":1711482132232,"username":"Phoebe.Ziemann","role":"EMPLOYEE","email":"Phoebe_Ziemann92@gmail.com","program":"COMMUNITY","experience":2,"group":""},{"id":12,"created_at":1711482132232,"username":"Bradford_Conroy53","role":"VOLUNTEER","email":"Bradford_Conroy94@hotmail.com","program":"COMMUNITY","experience":2,"group":""},{"id":13,"created_at":1711482132232,"username":"Florine_Strosin55","role":"VOLUNTEER","email":"Florine.Strosin29@hotmail.com","program":"ECONOMIC","experience":1,"group":""},{"id":14,"created_at":1711482132232,"username":"Constance.Doyle59","role":"EMPLOYEE","email":"Constance_Doyle@hotmail.com","program":"DOMESTIC","experience":3,"group":""},{"id":15,"created_at":1711482132232,"username":"Chauncey_Lockman","role":"ADMIN","email":"Chauncey_Lockman@yahoo.com","program":"DOMESTIC","experience":5,"group":""},{"id":16,"created_at":1711482132232,"username":"Esther_Wuckert-Larson26","role":"EMPLOYEE","email":"Esther_Wuckert-Larson@gmail.com","program":"ECONOMIC","experience":0,"group":""},{"id":17,"created_at":1711482132232,"username":"Jewel.Kunde","role":"VOLUNTEER","email":"Jewel_Kunde29@gmail.com","program":"ECONOMIC","experience":5,"group":""},{"id":18,"created_at":1711482132232,"username":"Hildegard_Parker92","role":"ADMIN","email":"Hildegard_Parker74@yahoo.com","program":"ECONOMIC","experience":2,"group":""},{"id":19,"created_at":1711482132232,"username":"Jordane.Lakin2","role":"ADMIN","email":"Jordane_Lakin@hotmail.com","program":"COMMUNITY","experience":1,"group":""}]

View File

@ -0,0 +1,10 @@
import { Tag } from "./Tag"
export const CreateNewTagAction = ({ input }) => {
return (
<div className="flex flex-row space-x-2 hover:bg-gray-100 rounded-md py-2 p-2 items-center">
<p className="capitalize">Create</p>
<Tag active={false} onDelete={null} >{input}</Tag>
</div>
)
}

View File

@ -0,0 +1,49 @@
import { EllipsisHorizontalIcon, TrashIcon } from "@heroicons/react/24/solid";
import { useState } from "react";
export const DropdownAction = ({ tag, handleDeleteTag, handleEditTag }) => {
const [isVisible, setVisible] = useState(false);
const [inputValue, setInputValue] = useState(tag);
const editTagOption = (e) => {
if (e.key === 'Enter') {
handleEditTag(tag, inputValue)
setVisible(false);
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
return (
<div>
<EllipsisHorizontalIcon
className="w-5 text-gray-500"
onClick={() => setVisible(!isVisible)}
/>
{isVisible && (
<div className="absolute flex flex-col justify-start z-50 rounded-md bg-white border border-gray-200 shadow p-2 space-y-2">
<input
type="text"
value={inputValue}
onChange={handleInputChange}
onKeyDown={editTagOption}
autoFocus
className="bg-gray-50 text-2xs focus:outline-none rounded-md font-normal text-gray-800 p-1 border-2 focus:border-blue-200"
/>
<button onClick={() => {
handleDeleteTag(inputValue)
setVisible(false)
}} className="justify-start flex flex-row space-x-4 hover:bg-gray-100 rounded-md items-center p-2 px-2">
<TrashIcon className="w-3 h-3" />
<p>Delete</p>
</button>
</div>
)}
</div>
);
};

View File

@ -0,0 +1,122 @@
import React, { useState } from "react";
import "tailwindcss/tailwind.css";
import { TagsArray } from "./TagsArray";
import { TagDropdown } from "./TagDropdown";
import { CreateNewTagAction } from "./CreateNewTagAction";
interface TagsInputProps {
presetOptions: string[];
}
const TagsInput: React.FC<TagsInputProps> = ({
presetValue,
presetOptions,
}) => {
const [inputValue, setInputValue] = useState("");
const [cellSelected, setCellSelected] = useState(false);
const [tags, setTags] = useState<Set<string>>(
new Set(presetValue ? [presetValue] : [])
);
const [options, setOptions] = useState<Set<string>>(new Set(presetOptions));
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && inputValue.trim()) {
setTags((prevTags) => new Set(prevTags).add(inputValue));
setOptions((prevOptions) => new Set(prevOptions).add(inputValue));
setInputValue("");
}
};
const handleSelectTag = (tagToAdd: string) => {
if (!tags.has(tagToAdd)) {
// Corrected syntax for checking if a Set contains an item
setTags((prevTags) => new Set(prevTags).add(tagToAdd));
}
};
const handleDeleteTag = (tagToDelete: string) => {
setTags((prevTags) => {
const updatedTags = new Set(prevTags);
updatedTags.delete(tagToDelete);
return updatedTags;
});
};
const handleDeleteTagOption = (tagToDelete: string) => {
setOptions((prevOptions) => {
const updatedOptions = new Set(prevOptions);
updatedOptions.delete(tagToDelete);
return updatedOptions;
});
if (tags.has(tagToDelete)) {
handleDeleteTag(tagToDelete);
}
};
const handleEditTag = (oldTag: string, newTag: string) => {
if (oldTag !== newTag) {
setTags((prevTags) => {
const tagsArray = Array.from(prevTags);
const oldTagIndex = tagsArray.indexOf(oldTag);
if (oldTagIndex !== -1) {
tagsArray.splice(oldTagIndex, 1, newTag);
}
return new Set(tagsArray);
});
setOptions((prevOptions) => {
const optionsArray = Array.from(prevOptions);
const oldTagIndex = optionsArray.indexOf(oldTag);
if (oldTagIndex !== -1) {
optionsArray.splice(oldTagIndex, 1, newTag);
}
return new Set(optionsArray);
});
}
};
return (
<div className="cursor-pointer" onClick={() => setCellSelected(true)}>
{!cellSelected ? (
<TagsArray handleDelete={handleDeleteTag} tags={tags} />
) : (
<div className="absolute w-64 z-50 -ml-3 -mt-7">
<div className="rounded-md border border-gray-200 shadow">
<div className="flex flex-wrap rounded-t-md items-center gap-2 bg-gray-50 p-2">
<TagsArray handleDelete={handleDeleteTag} active tags={tags} />
<input
type="text"
value={inputValue}
placeholder="Search for an option..."
onChange={handleInputChange}
onKeyDown={handleAddTag}
className="focus:outline-none bg-transparent"
autoFocus
/>
</div>
<div className="flex rounded-b-md bg-white flex-col border-t border-gray-100 text-2xs font-medium text-gray-500 p-2">
<p className="capitalize">Select an option or create one</p>
<TagDropdown
handleDeleteTag={handleDeleteTagOption}
handleEditTag={handleEditTag}
handleAdd={handleSelectTag}
tags={options}
/>
{inputValue.length > 0 && (
<CreateNewTagAction input={inputValue} />
)}
</div>
</div>
</div>
)}
</div>
);
};
export default TagsInput;

View File

View File

@ -0,0 +1,15 @@
import { XMarkIcon } from "@heroicons/react/24/solid";
export const Tag = ({ children, handleDelete, active = false }) => {
return (
<span className={`font-normal bg-cyan-100 text-gray-800 flex flex-row p-1 px-2 rounded-lg`}>
{children}
{active && handleDelete && (
<button onClick={() => handleDelete(children)}>
<XMarkIcon className={`ml-1 w-3 text-cyan-500`} />
</button>
)}
</span>
);
};

View File

@ -0,0 +1,16 @@
import { Tag } from "./Tag";
import { DropdownAction } from "./DropdownAction";
export const TagDropdown = ({ tags, handleEditTag,handleDeleteTag,handleAdd }) => {
return (
<div className="z-50 flex flex-col space-y-2 mt-2">
{Array.from(tags).map((tag, index) => (
<div key={index} className="items-center rounded-md p-1 flex flex-row justify-between hover:bg-gray-100">
<button onClick={() => handleAdd(tag)}><Tag >{tag}</Tag></button>
<DropdownAction handleDeleteTag={handleDeleteTag} handleEditTag={handleEditTag} tag={tag} />
</div>
))}
</div>
);
};

View File

@ -0,0 +1,16 @@
import { Tag } from "./Tag"
export const TagsArray = ({ tags, handleDelete, active = false }) => {
return(
<div className="flex flex-wrap gap-2 items-center">
{
Array.from(tags).map((tag) => {
return (
<Tag handleDelete={handleDelete} active={active}>{tag}</Tag>
)
})
}
</div>
)
}

View File

@ -31,7 +31,7 @@ const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen }) => {
<div className="flex flex-col space-y-2">
<h4 className="text-xs font-semibold text-gray-500">Pages</h4>
<nav className="flex flex-col">
<SidebarItem icon={<HomeIcon />} text="Home" />
<SidebarItem active={true} icon={<HomeIcon />} text="Home" />
<SidebarItem icon={<BookmarkIcon />} text="Resources" />
<SidebarItem icon={<ClipboardIcon />} text="Services" />
<SidebarItem icon={<BookOpenIcon />} text="Training Manuals" />

View File

@ -2,11 +2,12 @@
interface SidebarItemProps {
icon: React.ReactElement;
text: string;
active: boolean;
}
export const SidebarItem: React.FC<SidebarItemProps> = ({ icon, text }) => {
export const SidebarItem: React.FC<SidebarItemProps> = ({ icon, text, active }) => {
return (
<a href="#" className="flex items-center p-2 space-x-2 hover:bg-gray-200 rounded-md">
<a href="#" className={active ? "flex items-center p-2 space-x-2 bg-gray-200 rounded-md" : "flex items-center p-2 space-x-2 hover:bg-gray-200 rounded-md"}>
<span className="h-5 text-gray-500 w-5">
{icon}
</span>

8763
compass/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,30 @@
{
"name": "compass",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@heroicons/react": "^2.1.1",
"next": "13.5.6",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10",
"eslint": "^8",
"eslint-config-next": "13.5.6",
"postcss": "^8",
"tailwindcss": "^3",
"typescript": "^5"
}
}
{
"name": "compass",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@heroicons/react": "^2.1.1",
"@tanstack/react-table": "^8.15.0",
"next": "13.5.6",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10",
"eslint": "^8",
"eslint-config-next": "13.5.6",
"postcss": "^8",
"tailwindcss": "^3",
"typescript": "^5"
}
}