Commit e7743761 authored by Lê Đức Huy's avatar Lê Đức Huy

feat: implement admin sidebar, layout shell, and login page components

parent 91a2bc40
...@@ -109,6 +109,7 @@ function Footer() { ...@@ -109,6 +109,7 @@ function Footer() {
const contactInfo = { const contactInfo = {
name: name:
primaryBranch?.branch_name ??
siteInformation?.website_name ?? siteInformation?.website_name ??
"LIÊN ĐOÀN THƯƠNG MẠI & CÔNG NGHIỆP VIỆT NAM - CHI NHÁNH KHU VỰC THÀNH PHỐ HỒ CHÍ MINH", "LIÊN ĐOÀN THƯƠNG MẠI & CÔNG NGHIỆP VIỆT NAM - CHI NHÁNH KHU VỰC THÀNH PHỐ HỒ CHÍ MINH",
address: address:
...@@ -285,7 +286,7 @@ function Footer() { ...@@ -285,7 +286,7 @@ function Footer() {
{extraBranches.length > 0 ? ( {extraBranches.length > 0 ? (
<div className="pt-4"> <div className="pt-4">
<p className="text-[14px] font-semibold uppercase text-[#dce7ff]"> <p className="text-[14px] font-semibold uppercase text-[#dce7ff]">
Chi nhánh khác Các chi nhánh khác
</p> </p>
<div className="mt-3 space-y-3 text-[14px] text-[#c7d8ff]"> <div className="mt-3 space-y-3 text-[14px] text-[#c7d8ff]">
{extraBranches.map((branch) => ( {extraBranches.map((branch) => (
...@@ -295,32 +296,6 @@ function Footer() { ...@@ -295,32 +296,6 @@ function Footer() {
{branch.branch_name} {branch.branch_name}
</p> </p>
) : null} ) : null}
{branch.address ? (
<div className="flex items-start gap-2">
<MapPin className="mt-0.5 h-3.5 w-3.5 shrink-0 text-[#f7b500]" />
<span>{branch.address}</span>
</div>
) : null}
{branch.telephone || branch.hotline ? (
<div className="flex items-center gap-2">
<Phone className="h-3.5 w-3.5 shrink-0 text-[#f7b500]" />
<span>{branch.telephone || branch.hotline}</span>
</div>
) : null}
{branch.fax ? (
<div className="flex items-center gap-2">
<Printer className="h-3.5 w-3.5 shrink-0 text-[#f7b500]" />
<span>{branch.fax}</span>
</div>
) : null}
{branch.email ? (
<div className="flex items-center gap-2">
<Mail className="h-3.5 w-3.5 shrink-0 text-[#f7b500]" />
<a href={`mailto:${branch.email}`}>
{branch.email}
</a>
</div>
) : null}
</div> </div>
))} ))}
</div> </div>
......
...@@ -7,8 +7,9 @@ import { useQuery } from "@tanstack/react-query"; ...@@ -7,8 +7,9 @@ import { useQuery } from "@tanstack/react-query";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import logo from "@/assets/VCCI-HCM-logo-VN-2025.png"; import logo from "@/assets/VCCI-HCM-logo-VN-2025.png";
import { getLogo } from "@/api/endpoints/logo"; import { useGetLogo } from "@/api/endpoints/logo";
import { getSiteInformation } from "@/api/endpoints/site-information"; import { getSiteInformation } from "@/api/endpoints/site-information";
import { resolveUploadUrl } from "@/links";
import type { Logo } from "@/api/models/logo"; import type { Logo } from "@/api/models/logo";
import type { import type {
SiteInformationData, SiteInformationData,
...@@ -176,20 +177,23 @@ function Header() { ...@@ -176,20 +177,23 @@ function Header() {
staleTime: 5 * 60 * 1000, staleTime: 5 * 60 * 1000,
}); });
const { data: currentLogo = null } = useQuery({ const { data: currentLogo = null } = useGetLogo(
queryKey: ["header-logo"], {
queryFn: () => page: 1,
getLogo({ pageSize: 1,
page: 1, sortField: "updated_at",
pageSize: 1, sortOrder: "desc",
sortField: "updated_at", },
sortOrder: "desc", {
}).catch(() => null), query: {
select: (response) => select: (response: any) => {
(response as LogoListEnvelope | null)?.data?.responseData?.rows?.[0] ?? const responseData = response?.responseData ?? response?.data?.responseData;
null, return (responseData?.rows?.[0] as Logo | undefined) ?? null;
staleTime: 5 * 60 * 1000, },
}); staleTime: 5 * 60 * 1000,
},
}
);
const { data: siteInformationResponse } = const { data: siteInformationResponse } =
useQuery<ApiEnvelope<SiteInformationData> | null>({ useQuery<ApiEnvelope<SiteInformationData> | null>({
...@@ -252,9 +256,8 @@ function Header() { ...@@ -252,9 +256,8 @@ function Header() {
return ( return (
<header className="sticky top-0 z-50 shadow-[0_1px_0_rgba(15,23,42,0.05)]"> <header className="sticky top-0 z-50 shadow-[0_1px_0_rgba(15,23,42,0.05)]">
<div <div
className={`hidden w-full items-center justify-center overflow-hidden bg-[#25439a] ${ className={`hidden w-full items-center justify-center overflow-hidden bg-[#25439a] ${isTopBarHidden ? "lg:hidden" : "h-10 lg:flex"
isTopBarHidden ? "lg:hidden" : "h-10 lg:flex" }`}
}`}
> >
<div className="mx-auto flex h-full w-full max-w-[1460px] items-center justify-between gap-6 px-6 xl:px-8"> <div className="mx-auto flex h-full w-full max-w-[1460px] items-center justify-between gap-6 px-6 xl:px-8">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
...@@ -321,8 +324,8 @@ function Header() { ...@@ -321,8 +324,8 @@ function Header() {
<Image <Image
width={108} width={108}
height={40} height={40}
className="h-auto w-[108px] object-contain" className="h-auto max-h-10 w-[108px] object-contain"
src={currentLogo?.logo_url || logo} src={currentLogo?.logo_url ? resolveUploadUrl(currentLogo.logo_url) : logo}
alt={currentLogo?.logo_name || "VCCI-HCM"} alt={currentLogo?.logo_name || "VCCI-HCM"}
priority priority
/> />
...@@ -360,11 +363,10 @@ function Header() { ...@@ -360,11 +363,10 @@ function Header() {
</div> </div>
<div <div
className={`fixed inset-0 z-60 bg-white transition-all duration-300 lg:hidden ${ className={`fixed inset-0 z-60 bg-white transition-all duration-300 lg:hidden ${toggleMenu
toggleMenu
? "pointer-events-auto translate-y-0 opacity-100" ? "pointer-events-auto translate-y-0 opacity-100"
: "pointer-events-none -translate-y-2 opacity-0" : "pointer-events-none -translate-y-2 opacity-0"
}`} }`}
> >
<div className="flex h-full flex-col overflow-hidden"> <div className="flex h-full flex-col overflow-hidden">
<div className="sticky top-0 z-10 flex h-[78px] shrink-0 items-center justify-between border-b border-slate-100 bg-white px-6 shadow-[0_1px_0_rgba(15,23,42,0.04)]"> <div className="sticky top-0 z-10 flex h-[78px] shrink-0 items-center justify-between border-b border-slate-100 bg-white px-6 shadow-[0_1px_0_rgba(15,23,42,0.04)]">
...@@ -376,8 +378,8 @@ function Header() { ...@@ -376,8 +378,8 @@ function Header() {
<Image <Image
width={108} width={108}
height={40} height={40}
className="h-auto w-[108px] object-contain" className="h-auto max-h-10 w-[108px] object-contain"
src={currentLogo?.logo_url || logo} src={currentLogo?.logo_url ? resolveUploadUrl(currentLogo.logo_url) : logo}
alt={currentLogo?.logo_name || "VCCI-HCM"} alt={currentLogo?.logo_name || "VCCI-HCM"}
priority priority
/> />
......
...@@ -15,14 +15,14 @@ import { ...@@ -15,14 +15,14 @@ import {
} from "lucide-react"; } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { getLogo } from "@/api/endpoints/logo"; import { useGetLogo } from "@/api/endpoints/logo";
import type { Logo } from "@/api/models/logo"; import type { Logo } from "@/api/models/logo";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import logo from "@/assets/VCCI-HCM-logo-VN-2025.png"; import logo from "@/assets/VCCI-HCM-logo-VN-2025.png";
import links from "@/links"; import links, { resolveUploadUrl } from "@/links";
import { loginAdmin } from "@/lib/auth/admin-auth"; import { loginAdmin } from "@/lib/auth/admin-auth";
import useAuthStore from "@/store/useAuthStore"; import useAuthStore from "@/store/useAuthStore";
...@@ -131,12 +131,22 @@ function AuthShell({ ...@@ -131,12 +131,22 @@ function AuthShell({
mode: AuthMode; mode: AuthMode;
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const { data: logoData } = useQuery({ const { data: logoData } = useGetLogo(
queryKey: ["logo", { page: 1, pageSize: 1, sortOrder: "desc" }], {
queryFn: () => getLogo({ page: 1, pageSize: 1, sortOrder: "desc" }), page: 1,
select: (response) => pageSize: 1,
(response as LogoListEnvelope)?.data?.responseData?.rows?.[0], sortField: "updated_at",
}); sortOrder: "desc",
},
{
query: {
select: (response: any) => {
const responseData = response?.responseData ?? response?.data?.responseData;
return (responseData?.rows?.[0] as Logo | undefined) ?? null;
},
},
}
);
const title = const title =
mode === "login" mode === "login"
...@@ -163,7 +173,7 @@ function AuthShell({ ...@@ -163,7 +173,7 @@ function AuthShell({
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex h-16 w-16 items-center justify-center rounded-2xl border border-[#063e8e]/10 bg-white shadow-sm"> <div className="flex h-16 w-16 items-center justify-center rounded-2xl border border-[#063e8e]/10 bg-white shadow-sm">
<Image <Image
src={logoData?.logo_url || logo} src={logoData?.logo_url ? resolveUploadUrl(logoData.logo_url) : logo}
alt={logoData?.logo_name || "VCCI HCM"} alt={logoData?.logo_name || "VCCI HCM"}
width={48} width={48}
height={48} height={48}
...@@ -217,7 +227,7 @@ function AuthShell({ ...@@ -217,7 +227,7 @@ function AuthShell({
<div className="mb-8 flex items-center gap-3 lg:hidden"> <div className="mb-8 flex items-center gap-3 lg:hidden">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl border border-[#063e8e]/10 bg-[#f8fbff]"> <div className="flex h-12 w-12 items-center justify-center rounded-2xl border border-[#063e8e]/10 bg-[#f8fbff]">
<Image <Image
src={logoData?.logo_url || logo} src={logoData?.logo_url ? resolveUploadUrl(logoData.logo_url) : logo}
alt={logoData?.logo_name || "VCCI HCM"} alt={logoData?.logo_name || "VCCI HCM"}
width={36} width={36}
height={36} height={36}
......
...@@ -17,9 +17,10 @@ import { ...@@ -17,9 +17,10 @@ import {
Video, Video,
} from "lucide-react"; } from "lucide-react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { getLogo } from "@/api/endpoints/logo"; import { useGetLogo } from "@/api/endpoints/logo";
import type { Logo } from "@/api/models/logo"; import type { Logo } from "@/api/models/logo";
import logo from "@/assets/VCCI-HCM-logo-VN-2025.png"; import logo from "@/assets/VCCI-HCM-logo-VN-2025.png";
import { resolveUploadUrl } from "@/links";
type LogoListEnvelope = { type LogoListEnvelope = {
data?: { data?: {
...@@ -89,12 +90,22 @@ export function AdminSidebar() { ...@@ -89,12 +90,22 @@ export function AdminSidebar() {
Record<string, boolean> Record<string, boolean>
>({}); >({});
const { data: logoData } = useQuery({ const { data: logoData } = useGetLogo(
queryKey: ["logo", { page: 1, pageSize: 1, sortOrder: "desc" }], {
queryFn: () => getLogo({ page: 1, pageSize: 1, sortOrder: "desc" }), page: 1,
select: (response) => pageSize: 1,
(response as LogoListEnvelope)?.data?.responseData?.rows?.[0], sortField: "updated_at",
}); sortOrder: "desc",
},
{
query: {
select: (response: any) => {
const responseData = response?.responseData ?? response?.data?.responseData;
return (responseData?.rows?.[0] as Logo | undefined) ?? null;
},
},
}
);
const isItemActive = React.useCallback( const isItemActive = React.useCallback(
(href: string) => { (href: string) => {
...@@ -146,7 +157,7 @@ export function AdminSidebar() { ...@@ -146,7 +157,7 @@ export function AdminSidebar() {
> >
<div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl border border-[#063e8e]/10 bg-[#f8fbff] shadow-sm"> <div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl border border-[#063e8e]/10 bg-[#f8fbff] shadow-sm">
<Image <Image
src={logoData?.logo_url || logo} src={logoData?.logo_url ? resolveUploadUrl(logoData.logo_url) : logo}
alt={logoData?.logo_name || "VCCI HCM"} alt={logoData?.logo_name || "VCCI HCM"}
width={40} width={40}
height={40} height={40}
...@@ -194,8 +205,8 @@ export function AdminSidebar() { ...@@ -194,8 +205,8 @@ export function AdminSidebar() {
className={cn( className={cn(
"rounded-[26px] border border-transparent transition-all duration-200", "rounded-[26px] border border-transparent transition-all duration-200",
isOpen && isOpen &&
expanded && expanded &&
"border-[#063e8e]/10 bg-white/70 p-2 shadow-sm", "border-[#063e8e]/10 bg-white/70 p-2 shadow-sm",
)} )}
> >
<button <button
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment