Files
frontend/apps/cs/src/pages/users/ui/users-page.tsx
T

399 lines
12 KiB
TypeScript

"use client";
import dayjs from "dayjs";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import {
Button,
Checkbox,
Input,
InputDatePicker,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components";
import { CodeApi } from "@/entities/code";
import {
UserApi,
type UserListResponse,
type UserSearchParams,
} from "@/entities/user";
import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib";
import { columns } from "../model/table-columns";
type DateRange = {
from?: Date | undefined;
to?: Date | undefined;
};
function UsersPage() {
const router = useRouter();
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [presetDateRange, setPresetDateRange] = useState("");
const [dateRange, setDateRange] = useState<DateRange | undefined>({
from: undefined,
to: undefined,
});
const [keywordType, setKeywordType] = useState("all");
const [keyword, setKeyword] = useState("");
const [userStateCode, setUserStateCode] = useState("");
const [aprvYn, setAprvYn] = useState("");
const [grpId, setGrpId] = useState("");
const [searchParams, setSearchParams] = useState<UserSearchParams>({
keywordType: "all",
keyword: "",
userStateCode: "",
aprvYn: "",
grpId: "",
frDateYmd: "",
toDateYmd: "",
sort: "regDt,desc",
});
const { data: userSttsStats } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
queryFn: () => UserApi.selectUserSttsStats(),
});
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS, searchParams, pagination],
queryFn: () =>
UserApi.findPageUser({
...searchParams,
page: pagination.pageIndex,
size: pagination.pageSize,
}),
});
const { data: userStateCodes } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.CODE],
queryFn: () => CodeApi.getCode("USERSTTS"),
});
const handleRowClick = (row: UserListResponse) => {
router.push(`/users/${row.userUnqId}`);
};
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
},
dataTableOptions: {
onRowClick: handleRowClick,
viewPagination: true,
},
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
keywordType,
keyword,
userStateCode,
aprvYn,
grpId,
frDateYmd: dateRange?.from
? dayjs(dateRange.from).format("YYYYMMDD")
: "",
toDateYmd: dateRange?.to ? dayjs(dateRange.to).format("YYYYMMDD") : "",
});
};
const handleReset = () => {
setKeywordType("all");
setKeyword("");
setUserStateCode("");
setAprvYn("");
setGrpId("");
setDateRange({
from: undefined,
to: undefined,
});
setSearchParams({
...searchParams,
keywordType: "all",
keyword: "",
userStateCode: "",
aprvYn: "",
grpId: "",
frDateYmd: "",
toDateYmd: "",
});
};
const handlePresetDateRange = (value: string) => {
let presetDateRange: DateRange | undefined;
switch (value) {
case "1M":
presetDateRange = {
from: dayjs().subtract(1, "month").toDate(),
to: dayjs().toDate(),
};
break;
case "3M":
presetDateRange = {
from: dayjs().subtract(3, "month").toDate(),
to: dayjs().toDate(),
};
break;
case "6M":
presetDateRange = {
from: dayjs().subtract(6, "month").toDate(),
to: dayjs().toDate(),
};
break;
default:
presetDateRange = undefined;
}
setPresetDateRange(value);
setDateRange(presetDateRange);
};
const getUserSttsStatsCnt = (code: string): number => {
if (!Array.isArray(userSttsStats)) return 0;
return userSttsStats.find((item) => item.sttsCd === code)?.cnt ?? 0;
};
const handleUserStateCodeChange = (code: string, checked: boolean) => {
let newUserStateCode = userStateCode;
if (checked) {
newUserStateCode = userStateCode ? `${userStateCode},${code}` : code;
} else {
newUserStateCode = userStateCode
.split(",")
.filter((c) => c !== code)
.join(",");
}
setUserStateCode(newUserStateCode);
};
return (
<div className="card card-border !mt-6">
<div className="card card-border flex items-center justify-center gap-10 !bg-accent !py-6">
<div className="flex items-center gap-4">
<div className="flex size-12 items-center justify-center rounded-full bg-[rgba(153,153,153,1)]">
<i className="diveicon di-person text-xl text-white" />
</div>
<span className="text-2xl font-medium text-label">
승인대기{" "}
<em className="not-italic text-primary">
{getUserSttsStatsCnt("00")}
</em>
명
</span>
</div>
<div className="flex items-center gap-4">
<div className="flex size-12 items-center justify-center rounded-full bg-primary">
<i className="diveicon di-person-fill text-xl text-white" />
</div>
<span className="text-2xl font-medium text-label">
전체{" "}
<em className="not-italic text-primary">
{getUserSttsStatsCnt("TOTAL")}
</em>
명
</span>
</div>
<div className="flex items-center gap-4">
<div className="flex size-12 items-center justify-center rounded-full bg-[rgba(255,149,0,1)]">
<i className="diveicon di-lock text-xl text-white" />
</div>
<span className="text-2xl font-medium text-label">
잠금{" "}
<em className="not-italic text-primary">
{getUserSttsStatsCnt("07")}
</em>
명
</span>
</div>
<div className="flex items-center gap-4">
<div className="flex size-12 items-center justify-center rounded-full bg-destructive">
<i className="diveicon di-exclamation-triangle-fill text-xl text-white" />
</div>
<span className="text-2xl font-medium text-label">
탈퇴{" "}
<em className="not-italic text-primary">
{getUserSttsStatsCnt("08")}
</em>
명
</span>
</div>
</div>
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex flex-col gap-6 p-2">
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<Label className="mr-2 whitespace-nowrap text-label">
회원정보
</Label>
<Select
value={keywordType}
onValueChange={(value) => setKeywordType(value)}
>
<SelectTrigger className="w-[18rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">전체</SelectItem>
<SelectItem value="userId">아이디</SelectItem>
<SelectItem value="userName">이름</SelectItem>
<SelectItem value="email">이메일</SelectItem>
<SelectItem value="mphn">휴대폰번호</SelectItem>
</SelectContent>
</Select>
<Input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
className="!text-[1.3rem]"
placeholder="검색어를 입력하세요"
/>
</div>
<div className="flex items-center gap-2">
<Label className="mr-2 text-label">그룹</Label>
{/* Select */}
</div>
<div className="flex items-center gap-2">
<Label className="mr-2 text-label">기간</Label>
<Select
value={presetDateRange}
onValueChange={handlePresetDateRange}
>
<SelectTrigger className="w-[12rem]">
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1M">1개월</SelectItem>
<SelectItem value="3M">3개월</SelectItem>
<SelectItem value="6M">6개월</SelectItem>
</SelectContent>
</Select>
<InputDatePicker
value={dateRange?.from}
onChange={(date) => {
setDateRange({ ...dateRange, from: date });
setPresetDateRange("");
}}
/>
~
<InputDatePicker
value={dateRange?.to}
onChange={(date) => {
setDateRange({ ...dateRange, to: date });
setPresetDateRange("");
}}
/>
</div>
</div>
<div className="flex items-center gap-2">
<Label className="mr-2 text-label">회원상태</Label>
<div className="flex gap-4">
{userStateCodes?.map((item) => (
<div key={item.lwpoCd} className="flex items-center gap-2">
<Checkbox
id={item.lwpoCd}
value={item.lwpoCd}
checked={userStateCode?.includes(item.lwpoCd)}
onCheckedChange={(checked) => {
handleUserStateCodeChange(item.lwpoCd, !!checked);
}}
/>
<Label htmlFor={item.lwpoCd}>{item.lwpoCdNm}</Label>
</div>
))}
</div>
</div>
<hr />
<div className="flex justify-center gap-2">
<Button type="submit" className="w-[16rem]">
<i className="diveicon di-search" />
검색
</Button>
<Button
type="button"
color="white"
className="w-[16rem]"
onClick={handleReset}
>
초기화
</Button>
</div>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">회원 목록</h3>
<div className="flex gap-2">
<Select
defaultValue="regDt,desc"
onValueChange={(value) => {
setSearchParams({
...searchParams,
sort: value,
});
}}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="aprvYmd,desc">승인일순</SelectItem>
<SelectItem value="regDt,desc">등록일순</SelectItem>
</SelectContent>
</Select>
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(parseInt(value));
}}
>
<SelectTrigger className="h-[3.2rem] w-[12rem]">
<SelectValue placeholder="페이지 크기" />
</SelectTrigger>
<SelectContent>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={pageSize.toString()}>
{pageSize}개씩 보기
</SelectItem>
))}
</SelectContent>
</Select>
<Button onClick={() => router.push("/users/register")}>
등록
</Button>
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
}
export { UsersPage };