Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,12 @@
@apply bg-background text-foreground;
}
}

/* Hide scrollbar for horizontal scroll containers */
@utility scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
8 changes: 4 additions & 4 deletions src/app/loans/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ export default async function LoanDashboardPage() {
const items = await getItems();

return (
<div className="min-h-screen w-full bg-[#0C2C47] p-8">
<div className="min-h-screen w-full bg-[#0C2C47] p-4 md:p-8">
<DashboardNav userRole={session.user.role} />
<div className="flex justify-between items-center mb-8">
<div className="flex justify-between items-center mb-6 md:mb-8">
<div>
<h1 className="text-4xl font-bold text-white mb-2">Loans</h1>
<h1 className="text-2xl md:text-4xl font-bold text-white mb-1 md:mb-2">Loans</h1>
<p className="text-white/80">{loans.length} LOAN REQUESTS</p>
</div>

Expand All @@ -43,7 +43,7 @@ export default async function LoanDashboardPage() {
/>
</div>

<div className="bg-white rounded-lg p-6 shadow-sm">
<div className="bg-white rounded-lg p-3 md:p-6 shadow-sm">
<LoansTable data={loans} items={items} />
</div>
</div>
Expand Down
8 changes: 4 additions & 4 deletions src/components/DashboardNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ interface DashboardNavProps {
export function DashboardNav({ userRole }: DashboardNavProps) {
const pathname = usePathname();
// Unauthenticated users only see Catalogue tab
const tabs = userRole
? getAvailableTabs(userRole)
const tabs = userRole
? getAvailableTabs(userRole)
: [{ name: 'CATALOGUE', href: '/catalogue' }];

return (
<div className="flex space-x-8 border-b border-white/10 mb-8">
<div className="flex gap-4 md:gap-8 border-b border-white/10 mb-6 md:mb-8 overflow-x-auto scrollbar-hide">
{tabs.map((tab) => {
const isActive = pathname.startsWith(tab.href);
return (
<Link
key={tab.name}
href={tab.href}
className={cn(
"pb-4 text-sm font-bold tracking-wide transition-colors hover:text-white",
"pb-3 md:pb-4 text-xs md:text-sm font-bold tracking-wide transition-colors hover:text-white whitespace-nowrap shrink-0",
isActive
? "border-b-2 border-[#57A6FF] text-white"
: "text-white/50 border-b-2 border-transparent"
Expand Down
45 changes: 23 additions & 22 deletions src/components/catalogue/Catalogue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,20 @@ export default function Catalogue({ slocs, ihs, userRole }: CatalogueProps) {
}, [fetchItems]);

return (
<div className="min-h-screen w-full bg-[#0C2C47] p-8">
<div className="min-h-screen w-full bg-[#0C2C47] p-4 md:p-8">
<DashboardNav userRole={userRole} />
<div className="mb-8 flex items-center justify-between">
<div className="mb-6 md:mb-8 flex items-center justify-between">
<div>
<h1 className="mb-2 text-4xl font-bold text-white">Catalogue</h1>
<h1 className="mb-1 md:mb-2 text-2xl md:text-4xl font-bold text-white">Catalogue</h1>
<p className="text-white/80">{totalItems} ITEMS</p>
</div>
{canEdit && (
<ItemFormModal slocs={slocs} ihs={ihs} mode="add" onSuccess={refreshItems} />
)}
</div>

<div className="mb-8 flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div className="relative w-full max-w-md">
<div className="mb-6 md:mb-8 flex flex-col md:flex-row md:items-center md:justify-between gap-3 md:gap-4">
<div className="relative w-full md:max-w-md">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Input
ref={searchInputRef}
Expand All @@ -205,38 +205,39 @@ export default function Catalogue({ slocs, ihs, userRole }: CatalogueProps) {
</button>
)}
</div>
<div className="flex gap-3 flex-wrap">
<div className="flex gap-2 md:gap-3 flex-wrap">

{/*Reset Filters Button - only show when filters are active */}
{hasActiveFilters && (
<button
onClick={resetFilters}
className="h-9 rounded-md bg-white/20 px-4 text-white hover:bg-white/30 transition-colors flex items-center gap-2"
className="h-8 md:h-9 rounded-md bg-white/20 px-3 md:px-4 text-sm text-white hover:bg-white/30 transition-colors flex items-center gap-1.5"
>
<RotateCcw className="h-4 w-4" />
Reset Filters
<RotateCcw className="h-3.5 w-3.5" />
<span className="hidden sm:inline">Reset Filters</span>
<span className="sm:hidden">Reset</span>
</button>
)}

<select
value={filterSlocId}
onChange={(e) => setFilterSlocId(e.target.value)}
className="h-9 rounded-md bg-white/20 px-3 text-white border border-white/20 focus:outline-none"
className="h-8 md:h-9 rounded-md bg-white/20 px-2 md:px-3 text-sm text-white border border-white/20 focus:outline-none"
>
<option value="" className="text-black">All Locations</option> {/*Default*/}
<option value="" className="text-black">All Locations</option>
{slocs.map((s) => (
<option key={s.slocId} value={s.slocId} className="text-black">
{s.slocName}
</option>
))}
</select>

<select
value={filterIhId}
onChange={(e) => setFilterIhId(e.target.value)}
className="h-9 rounded-md bg-white/20 px-3 text-white border border-white/20 focus:outline-none"
className="h-8 md:h-9 rounded-md bg-white/20 px-2 md:px-3 text-sm text-white border border-white/20 focus:outline-none"
>
<option value="" className="text-black">All Holders</option> {/*Default*/}
<option value="" className="text-black">All Holders</option>
{ihs.map((h) => (
<option key={h.ihId} value={h.ihId} className="text-black">
{h.ihName}
Expand All @@ -247,19 +248,19 @@ export default function Catalogue({ slocs, ihs, userRole }: CatalogueProps) {
<select
value={sortOption}
onChange={(e) => setSortOption(e.target.value as SortOption)}
className="h-9 rounded-md bg-white/20 px-3 text-white border border-white/20 focus:outline-none"
className="h-8 md:h-9 rounded-md bg-white/20 px-2 md:px-3 text-sm text-white border border-white/20 focus:outline-none"
>
<option value="name" className="text-black">Sort by Item Name</option>
<option value="quantity" className="text-black">Sort by Quantity</option>
<option value="id" className="text-black">Sort by ID</option>
<option value="name" className="text-black">Sort: Name</option>
<option value="quantity" className="text-black">Sort: Qty</option>
<option value="id" className="text-black">Sort: ID</option>
</select>

{/*Asc/Desc Button*/}
<button
onClick={() => {
setSortAsc(!sortAsc);
}}
className="h-9 rounded-md bg-white/20 px-4 text-white hover:bg-white/30 transition-colors"
className="h-8 md:h-9 rounded-md bg-white/20 px-3 md:px-4 text-white hover:bg-white/30 transition-colors"
>
{sortAsc ? "▲" : "▼"}
</button>
Expand Down Expand Up @@ -303,7 +304,7 @@ export default function Catalogue({ slocs, ihs, userRole }: CatalogueProps) {

{/* Action buttons - appear on hover (only for LOGS+) */}
{canEdit && (
<div className="absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="absolute top-2 right-2 flex gap-1 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity">
<ItemFormModal
slocs={slocs}
ihs={ihs}
Expand Down
10 changes: 5 additions & 5 deletions src/components/catalogue/ItemFormModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ export default function ItemFormModal({
)}
/>

<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<FormField
control={form.control}
name="itemQty"
Expand Down Expand Up @@ -340,7 +340,7 @@ export default function ItemFormModal({
/>
</div>

<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<FormField
control={form.control}
name="itemSloc"
Expand Down Expand Up @@ -475,12 +475,12 @@ export default function ItemFormModal({
)}
/>

<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<FormField
control={form.control}
name="itemUnloanable"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-3 sm:p-4">
<FormControl>
<Checkbox
checked={field.value === true}
Expand All @@ -502,7 +502,7 @@ export default function ItemFormModal({
control={form.control}
name="itemExpendable"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-3 sm:p-4">
<FormControl>
<Checkbox
checked={field.value === true}
Expand Down
44 changes: 44 additions & 0 deletions src/components/layout/MobileNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import { useState } from "react";
import { Menu } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
} from "@/components/ui/sheet";

interface MobileNavProps {
children: React.ReactNode;
}

export function MobileNav({ children }: MobileNavProps) {
const [open, setOpen] = useState(false);

return (
<div className="md:hidden">
<Button
variant="ghost"
size="icon"
onClick={() => setOpen(true)}
aria-label="Open menu"
>
<Menu className="h-5 w-5" />
</Button>
<Sheet open={open} onOpenChange={setOpen}>
<SheetContent side="right" className="w-[280px] p-6">
<SheetHeader className="sr-only">
<SheetTitle>Navigation Menu</SheetTitle>
<SheetDescription>App navigation and user actions</SheetDescription>
</SheetHeader>
<div className="mt-6 flex flex-col gap-4">
{children}
</div>
</SheetContent>
</Sheet>
</div>
);
}
26 changes: 17 additions & 9 deletions src/components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ import { getSession } from '@/lib/auth/session';
import { UserMenu } from '@/components/auth/UserMenu';
import { LoginModal } from '@/components/auth/LoginModal';
import { DevRoleDropdown } from '@/components/auth/DevRoleDropdown';
import { MobileNav } from './MobileNav';

export async function Navbar() {
const session = await getSession();
const botUsername = process.env.TELEGRAM_BOT_USERNAME;
const isDev = process.env.NODE_ENV === 'development';

const authContent = isDev ? (
<DevRoleDropdown currentRole={session?.user?.role} />
) : session ? (
<UserMenu user={session.user} />
) : (
botUsername && <LoginModal botUsername={botUsername} />
);

return (
<nav className="sticky top-0 z-50 w-full border-b bg-white shadow-lg">
<div className="flex items-center justify-between px-4 py-2">
Expand All @@ -19,16 +28,15 @@ export async function Navbar() {
NUSC LMS
</Link>

{/* Dev role dropdown (local only) | User Menu or Login */}
<div className="flex items-center gap-4">
{isDev ? (
<DevRoleDropdown currentRole={session?.user?.role} />
) : session ? (
<UserMenu user={session.user} />
) : (
botUsername && <LoginModal botUsername={botUsername} />
)}
{/* Desktop auth controls */}
<div className="hidden md:flex items-center gap-4">
{authContent}
</div>

{/* Mobile menu */}
<MobileNav>
{authContent}
</MobileNav>
</div>
</nav>
);
Expand Down
Loading