260322:1648 Correct Coresspondence / Doing RFA / Correct CI
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
// File: components/layout/dashboard-shell.tsx
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { useUIStore } from "@/lib/stores/ui-store";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useUIStore } from '@/lib/stores/ui-store';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export function DashboardShell({ children }: { children: React.ReactNode }) {
|
||||
const { isSidebarOpen } = useUIStore();
|
||||
@@ -10,12 +10,12 @@ export function DashboardShell({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col min-h-screen transition-all duration-300 ease-in-out",
|
||||
'flex flex-col min-h-screen transition-all duration-300 ease-in-out',
|
||||
// ปรับ Margin ซ้าย ตามสถานะ Sidebar
|
||||
isSidebarOpen ? "md:ml-[240px]" : "md:ml-[70px]"
|
||||
isSidebarOpen ? 'md:ml-[240px]' : 'md:ml-[70px]'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Search, FileText, Clipboard, Image, Loader2 } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Command, CommandGroup, CommandItem, CommandList,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { useSearchSuggestions } from "@/hooks/use-search";
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Search, FileText, Clipboard, Image, Loader2 } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/command';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { useSearchSuggestions } from '@/hooks/use-search';
|
||||
|
||||
/** Search suggestion item returned from the API */
|
||||
interface SearchSuggestion {
|
||||
@@ -39,7 +33,7 @@ function useDebounceValue<T>(value: T, delay: number): T {
|
||||
export function GlobalSearch() {
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const debouncedQuery = useDebounceValue(query, 300);
|
||||
|
||||
@@ -62,10 +56,14 @@ export function GlobalSearch() {
|
||||
|
||||
const getIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case "correspondence": return <FileText className="mr-2 h-4 w-4" />;
|
||||
case "rfa": return <Clipboard className="mr-2 h-4 w-4" />;
|
||||
case "drawing": return <Image className="mr-2 h-4 w-4" />;
|
||||
default: return <Search className="mr-2 h-4 w-4" />;
|
||||
case 'correspondence':
|
||||
return <FileText className="mr-2 h-4 w-4" />;
|
||||
case 'rfa':
|
||||
return <Clipboard className="mr-2 h-4 w-4" />;
|
||||
case 'drawing':
|
||||
return <Image className="mr-2 h-4 w-4" />;
|
||||
default:
|
||||
return <Search className="mr-2 h-4 w-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,17 +79,19 @@ export function GlobalSearch() {
|
||||
className="pl-8 w-full bg-background"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
onFocus={() => {
|
||||
if (suggestions && suggestions.length > 0) setOpen(true);
|
||||
}}
|
||||
/>
|
||||
{isLoading && (
|
||||
<Loader2 className="absolute right-2.5 top-2.5 h-4 w-4 animate-spin text-muted-foreground" />
|
||||
)}
|
||||
{isLoading && <Loader2 className="absolute right-2.5 top-2.5 h-4 w-4 animate-spin text-muted-foreground" />}
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]" align="start" onOpenAutoFocus={(e) => e.preventDefault()}>
|
||||
<PopoverContent
|
||||
className="p-0 w-[var(--radix-popover-trigger-width)]"
|
||||
align="start"
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
<Command>
|
||||
<CommandList>
|
||||
{suggestions && suggestions.length > 0 && (
|
||||
@@ -114,9 +114,7 @@ export function GlobalSearch() {
|
||||
</CommandGroup>
|
||||
)}
|
||||
{(!suggestions || suggestions.length === 0) && !isLoading && (
|
||||
<div className="py-6 text-center text-sm text-muted-foreground">
|
||||
No suggestions found.
|
||||
</div>
|
||||
<div className="py-6 text-center text-sm text-muted-foreground">No suggestions found.</div>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// File: components/layout/navbar.tsx
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import Link from "next/link";
|
||||
import { Menu, Bell } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useUIStore } from "@/lib/stores/ui-store";
|
||||
import { UserNav } from "./user-nav";
|
||||
import _Link from 'next/link';
|
||||
import { Menu, Bell } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useUIStore } from '@/lib/stores/ui-store';
|
||||
import { UserNav } from './user-nav';
|
||||
|
||||
export function Navbar() {
|
||||
const { toggleSidebar } = useUIStore();
|
||||
@@ -13,21 +13,14 @@ export function Navbar() {
|
||||
return (
|
||||
<header className="flex h-14 items-center gap-4 border-b bg-background px-4 lg:h-[60px] lg:pr-6 lg:pl-1 sticky top-0 z-30">
|
||||
{/* Toggle Sidebar Button (Mobile Only) */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="shrink-0 md:hidden"
|
||||
onClick={toggleSidebar}
|
||||
>
|
||||
<Button variant="outline" size="icon" className="shrink-0 md:hidden" onClick={toggleSidebar}>
|
||||
<Menu className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle navigation menu</span>
|
||||
</Button>
|
||||
|
||||
<div className="w-full flex-1">
|
||||
{/* Breadcrumbs หรือ Search Bar จะมาใส่ตรงนี้ */}
|
||||
<h1 className="text-lg font-semibold md:text-xl hidden md:block">
|
||||
Document Management System
|
||||
</h1>
|
||||
<h1 className="text-lg font-semibold md:text-xl hidden md:block">Document Management System</h1>
|
||||
</div>
|
||||
|
||||
{/* Right Actions (เหลือชุดเดียวที่ถูกต้อง) */}
|
||||
@@ -36,10 +29,10 @@ export function Navbar() {
|
||||
<Bell className="h-5 w-5" />
|
||||
<span className="sr-only">Notifications</span>
|
||||
</Button>
|
||||
|
||||
|
||||
{/* User Menu */}
|
||||
<UserNav />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { Bell, Loader2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Bell, Loader2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useNotifications, useMarkNotificationRead } from "@/hooks/use-notification";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { Notification } from "@/types/notification";
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { useNotifications, useMarkNotificationRead } from '@/hooks/use-notification';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import type { Notification } from '@/types/notification';
|
||||
|
||||
export function NotificationsDropdown() {
|
||||
const router = useRouter();
|
||||
@@ -54,30 +54,24 @@ export function NotificationsDropdown() {
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center p-4">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : notifications.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
No new notifications
|
||||
<div className="flex justify-center p-4">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : notifications.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">No new notifications</div>
|
||||
) : (
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
{notifications.slice(0, 5).map((notification: Notification) => (
|
||||
<DropdownMenuItem
|
||||
key={notification.notificationId}
|
||||
className={`flex flex-col items-start p-3 cursor-pointer ${
|
||||
!notification.isRead ? 'bg-muted/30' : ''
|
||||
}`}
|
||||
className={`flex flex-col items-start p-3 cursor-pointer ${!notification.isRead ? 'bg-muted/30' : ''}`}
|
||||
onClick={() => handleNotificationClick(notification)}
|
||||
>
|
||||
<div className="flex justify-between w-full">
|
||||
<span className="font-medium text-sm">{notification.title}</span>
|
||||
{!notification.isRead && <span className="h-2 w-2 rounded-full bg-blue-500 mt-1" />}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-1 line-clamp-2">
|
||||
{notification.message}
|
||||
<span className="font-medium text-sm">{notification.title}</span>
|
||||
{!notification.isRead && <span className="h-2 w-2 rounded-full bg-blue-500 mt-1" />}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-1 line-clamp-2">{notification.message}</div>
|
||||
<div className="text-[10px] text-muted-foreground mt-1 self-end">
|
||||
{formatDistanceToNow(new Date(notification.createdAt), {
|
||||
addSuffix: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { LogOut, Settings, User } from "lucide-react";
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { signOut, useSession } from 'next-auth/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { LogOut, Settings, User } from 'lucide-react';
|
||||
|
||||
export function UserMenu() {
|
||||
const router = useRouter();
|
||||
@@ -24,18 +24,18 @@ export function UserMenu() {
|
||||
// Generate initials from name or username
|
||||
const getInitials = (name: string) => {
|
||||
return name
|
||||
.split(" ")
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join("")
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
};
|
||||
|
||||
const initials = user.name ? getInitials(user.name) : "U";
|
||||
const initials = user.name ? getInitials(user.name) : 'U';
|
||||
|
||||
const handleLogout = async () => {
|
||||
await signOut({ redirect: false });
|
||||
router.push("/login");
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -43,9 +43,7 @@ export function UserMenu() {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative h-10 w-10 rounded-full">
|
||||
<Avatar className="h-10 w-10">
|
||||
<AvatarFallback className="bg-primary/10 text-primary">
|
||||
{initials}
|
||||
</AvatarFallback>
|
||||
<AvatarFallback className="bg-primary/10 text-primary">{initials}</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -53,20 +51,16 @@ export function UserMenu() {
|
||||
<DropdownMenuLabel>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">{user.name}</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
{user.email}
|
||||
</p>
|
||||
<p className="text-xs leading-none text-muted-foreground mt-1">
|
||||
Role: {user.role}
|
||||
</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">{user.email}</p>
|
||||
<p className="text-xs leading-none text-muted-foreground mt-1">Role: {user.role}</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => router.push("/profile")}>
|
||||
<DropdownMenuItem onClick={() => router.push('/profile')}>
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>Profile</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => router.push("/settings")}>
|
||||
<DropdownMenuItem onClick={() => router.push('/settings')}>
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
<span>Settings</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
// File: components/layout/user-nav.tsx
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -16,9 +12,9 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { signOut, useSession } from 'next-auth/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export function UserNav() {
|
||||
const { data: session } = useSession();
|
||||
@@ -26,22 +22,24 @@ export function UserNav() {
|
||||
|
||||
// Helper function to get initials from name
|
||||
const getInitials = (name: string) => {
|
||||
return name
|
||||
?.split(" ")
|
||||
.map((n) => n[0])
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
.substring(0, 2) || "US";
|
||||
return (
|
||||
name
|
||||
?.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.substring(0, 2) || 'US'
|
||||
);
|
||||
};
|
||||
|
||||
const userName = session?.user?.name || "User";
|
||||
const userEmail = session?.user?.email || "user@example.com";
|
||||
const userName = session?.user?.name || 'User';
|
||||
const userEmail = session?.user?.email || 'user@example.com';
|
||||
// ใช้ role หรือ organization หากมีใน session (ต้องแก้ type ใน next-auth.d.ts แล้ว)
|
||||
const userRole = session?.user?.role || "Viewer";
|
||||
const userRole = session?.user?.role || 'Viewer';
|
||||
|
||||
const handleLogout = async () => {
|
||||
await signOut({ redirect: false });
|
||||
router.push("/login");
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -50,7 +48,7 @@ export function UserNav() {
|
||||
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
||||
<Avatar className="h-8 w-8">
|
||||
{/* ใส่ URL รูปถ้ามี */}
|
||||
<AvatarImage src={session?.user?.image || ""} alt={userName} />
|
||||
<AvatarImage src={session?.user?.image || ''} alt={userName} />
|
||||
<AvatarFallback>{getInitials(userName)}</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
@@ -59,12 +57,8 @@ export function UserNav() {
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">{userName}</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
{userEmail}
|
||||
</p>
|
||||
<p className="text-xs leading-none text-primary mt-1 font-semibold">
|
||||
{userRole}
|
||||
</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">{userEmail}</p>
|
||||
<p className="text-xs leading-none text-primary mt-1 font-semibold">{userRole}</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
@@ -86,4 +80,4 @@ export function UserNav() {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user