Merge remote-tracking branch 'origin/main' into iipbs/sn/dev
This commit is contained in:
@@ -185,6 +185,15 @@ function PostingDialog({ postingType, postingTargets }: PostingDialogProps) {
|
|||||||
...prev,
|
...prev,
|
||||||
[siteId]: value,
|
[siteId]: value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 상시 선택 시 게시 기간 미설정
|
||||||
|
if (value === "99") {
|
||||||
|
handleUpdateFields(siteId)({
|
||||||
|
pstgPrdSeCd: value,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setRememberPeriodDate((prev) => ({
|
setRememberPeriodDate((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[siteId]: { from, to },
|
[siteId]: { from, to },
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
Skeleton,
|
Skeleton,
|
||||||
} from "@common/components";
|
} from "@common/components";
|
||||||
import { AttachmentApi } from "@/entities/attachment";
|
import { AttachmentApi } from "@/entities/attachment";
|
||||||
import { PostApi, type PostParams } from "@/entities/post";
|
import { PostApi, PostPageGuard, type PostParams } from "@/entities/post";
|
||||||
import { useAlertStore, useMenuStore, useUserStore } from "@/shared/lib";
|
import { useAlertStore, useMenuStore, useUserStore } from "@/shared/lib";
|
||||||
import { ENDPOINT } from "@/shared/config";
|
import { ENDPOINT } from "@/shared/config";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
@@ -103,143 +103,147 @@ function FaqPage({ bbsId }: { bbsId: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card card-border !mt-6">
|
<PostPageGuard bbsId={bbsId}>
|
||||||
<div className="card card-border">
|
<div className="card card-border !mt-6">
|
||||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
<div className="card card-border">
|
||||||
<Label className="mr-2 text-label">검색</Label>
|
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||||
<Select
|
<Label className="mr-2 text-label">검색</Label>
|
||||||
value={postParams.keywordType}
|
<Select
|
||||||
onValueChange={(keywordType) => {
|
value={postParams.keywordType}
|
||||||
setPostParams({ ...postParams, keywordType });
|
onValueChange={(keywordType) => {
|
||||||
}}
|
setPostParams({ ...postParams, keywordType });
|
||||||
>
|
}}
|
||||||
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
>
|
||||||
<SelectValue placeholder="선택" />
|
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="선택" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectItem value="title">제목</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="all">제목+내용</SelectItem>
|
<SelectItem value="title">제목</SelectItem>
|
||||||
<SelectItem value="writer">작성자</SelectItem>
|
<SelectItem value="all">제목+내용</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value="writer">작성자</SelectItem>
|
||||||
</Select>
|
</SelectContent>
|
||||||
<Input
|
</Select>
|
||||||
value={postParams.keyword}
|
<Input
|
||||||
onChange={(e) =>
|
value={postParams.keyword}
|
||||||
setPostParams({ ...postParams, keyword: e.target.value })
|
onChange={(e) =>
|
||||||
}
|
setPostParams({ ...postParams, keyword: e.target.value })
|
||||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
}
|
||||||
/>
|
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||||
<Button className="h-[3.2rem]" type="submit">
|
/>
|
||||||
조회
|
<Button className="h-[3.2rem]" type="submit">
|
||||||
</Button>
|
조회
|
||||||
</form>
|
</Button>
|
||||||
</div>
|
</form>
|
||||||
<div className="card card-border">
|
</div>
|
||||||
<div className="card-header">
|
<div className="card card-border">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="card-header">
|
||||||
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
|
<div className="flex w-full items-center justify-between">
|
||||||
<div className="flex gap-2">
|
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
|
||||||
{pageMenuAuthority?.regAuthrtYn === "Y" && (
|
<div className="flex gap-2">
|
||||||
<Button onClick={() => router.push(`${pathname}/register`)}>
|
{pageMenuAuthority?.regAuthrtYn === "Y" && (
|
||||||
등록
|
<Button onClick={() => router.push(`${pathname}/register`)}>
|
||||||
</Button>
|
등록
|
||||||
)}
|
</Button>
|
||||||
{pageMenuAuthority?.delAuthrtYn === "Y" && (
|
)}
|
||||||
<Button
|
{pageMenuAuthority?.delAuthrtYn === "Y" && (
|
||||||
color="red"
|
<Button
|
||||||
onClick={onSelectedPostDelete}
|
color="red"
|
||||||
disabled={!selectedPostNumbers.length}
|
onClick={onSelectedPostDelete}
|
||||||
>
|
disabled={!selectedPostNumbers.length}
|
||||||
선택삭제
|
>
|
||||||
</Button>
|
선택삭제
|
||||||
)}
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
|
||||||
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
|
<div className="mb-4 flex items-center gap-2 rounded-sm border border-border bg-background p-4">
|
||||||
<div className="mb-4 flex items-center gap-2 rounded-sm border border-border bg-background p-4">
|
<Checkbox
|
||||||
<Checkbox
|
id="all-select"
|
||||||
id="all-select"
|
checked={
|
||||||
checked={selectedPostNumbers.length === postList?.content.length}
|
selectedPostNumbers.length === postList?.content.length
|
||||||
onClick={(e) => e.stopPropagation()}
|
}
|
||||||
onCheckedChange={handleAllCheckboxChange}
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
onCheckedChange={handleAllCheckboxChange}
|
||||||
<Label htmlFor="all-select" className="text-2xl">
|
/>
|
||||||
전체선택
|
<Label htmlFor="all-select" className="text-2xl">
|
||||||
</Label>
|
전체선택
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
{isFetched ? (
|
||||||
|
<Accordion
|
||||||
|
type="multiple"
|
||||||
|
className="secondary flex flex-col gap-4"
|
||||||
|
>
|
||||||
|
{postList?.content.map((item) => (
|
||||||
|
<div key={item.pstNo} className="flex items-start gap-2">
|
||||||
|
<Checkbox
|
||||||
|
id={item.pstNo.toString()}
|
||||||
|
checked={selectedPostNumbers.includes(item.pstNo)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleCheckboxChange(item.pstNo, checked)
|
||||||
|
}
|
||||||
|
className="mx-2 my-6"
|
||||||
|
/>
|
||||||
|
<AccordionItem
|
||||||
|
value={item.pstNo.toString()}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex flex-1 items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span className="text-primary">Q</span>
|
||||||
|
<span>{item.pstTtl}</span>
|
||||||
|
</div>
|
||||||
|
<span className="px-4 text-[1.2rem] font-normal text-label">
|
||||||
|
등록일: {dayjs(item.regDt).format("YYYY-MM-DD")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<span className="text-[1.4rem] font-bold text-label">
|
||||||
|
A
|
||||||
|
</span>
|
||||||
|
<div className="flex-1">
|
||||||
|
<pre className="whitespace-pre-wrap font-noto text-[1.4rem] tracking-tighter text-foreground">
|
||||||
|
{item.pstCn}
|
||||||
|
</pre>
|
||||||
|
<Attachment atflCd={item.atflCd} />
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="white"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`${pathname}/${item.pstNo}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="diveicon di-pencil" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="white"
|
||||||
|
onClick={() => handleDeletePost(item.pstNo)}
|
||||||
|
>
|
||||||
|
<i className="diveicon di-trash" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
) : (
|
||||||
|
<Skeleton className="h-[36rem] w-full" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isFetched ? (
|
|
||||||
<Accordion
|
|
||||||
type="multiple"
|
|
||||||
className="secondary flex flex-col gap-4"
|
|
||||||
>
|
|
||||||
{postList?.content.map((item) => (
|
|
||||||
<div key={item.pstNo} className="flex items-start gap-2">
|
|
||||||
<Checkbox
|
|
||||||
id={item.pstNo.toString()}
|
|
||||||
checked={selectedPostNumbers.includes(item.pstNo)}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleCheckboxChange(item.pstNo, checked)
|
|
||||||
}
|
|
||||||
className="mx-2 my-6"
|
|
||||||
/>
|
|
||||||
<AccordionItem
|
|
||||||
value={item.pstNo.toString()}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
<AccordionTrigger>
|
|
||||||
<div className="flex flex-1 items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-primary">Q</span>
|
|
||||||
<span>{item.pstTtl}</span>
|
|
||||||
</div>
|
|
||||||
<span className="px-4 text-[1.2rem] font-normal text-label">
|
|
||||||
등록일: {dayjs(item.regDt).format("YYYY-MM-DD")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<span className="text-[1.4rem] font-bold text-label">
|
|
||||||
A
|
|
||||||
</span>
|
|
||||||
<div className="flex-1">
|
|
||||||
<pre className="whitespace-pre-wrap font-noto text-[1.4rem] tracking-tighter text-foreground">
|
|
||||||
{item.pstCn}
|
|
||||||
</pre>
|
|
||||||
<Attachment atflCd={item.atflCd} />
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end gap-2">
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
color="white"
|
|
||||||
onClick={() =>
|
|
||||||
router.push(`${pathname}/${item.pstNo}`)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i className="diveicon di-pencil" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
color="white"
|
|
||||||
onClick={() => handleDeletePost(item.pstNo)}
|
|
||||||
>
|
|
||||||
<i className="diveicon di-trash" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Accordion>
|
|
||||||
) : (
|
|
||||||
<Skeleton className="h-[36rem] w-full" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PostPageGuard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-8
@@ -1,5 +1,12 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { CLAIM_NAME, AUTH_USER_ID } from "./src/shared/config";
|
import {
|
||||||
|
CLAIM_NAME,
|
||||||
|
AUTH_USER_ID,
|
||||||
|
PORT,
|
||||||
|
PROXY_HOST,
|
||||||
|
SERVER_API_URL,
|
||||||
|
SITE_ID,
|
||||||
|
} from "./src/shared/config";
|
||||||
|
|
||||||
const serverApiUrl =
|
const serverApiUrl =
|
||||||
process.env.NEXT_PUBLIC_SERVER_API_URL ?? "http://localhost:3000";
|
process.env.NEXT_PUBLIC_SERVER_API_URL ?? "http://localhost:3000";
|
||||||
@@ -7,17 +14,23 @@ const serverApiUrl =
|
|||||||
export function middleware(req: NextRequest) {
|
export function middleware(req: NextRequest) {
|
||||||
const pathname = req.nextUrl.pathname;
|
const pathname = req.nextUrl.pathname;
|
||||||
const searchParams = req.nextUrl.searchParams;
|
const searchParams = req.nextUrl.searchParams;
|
||||||
|
|
||||||
|
const logMessages: string[] = [
|
||||||
|
`[Middleware Request] ${new Date().toISOString()}`,
|
||||||
|
`[Environment]\n Mode: ${process.env.NODE_ENV}\n Port: ${PORT}\n Proxy Host: ${PROXY_HOST}\n Server API URL: ${SERVER_API_URL}\n Site ID: ${SITE_ID}`,
|
||||||
|
`[Request]\n Original Pathname: ${pathname}`,
|
||||||
|
];
|
||||||
|
|
||||||
if (pathname.startsWith("/server")) {
|
if (pathname.startsWith("/server")) {
|
||||||
|
const queryParams = searchParams.toString();
|
||||||
const decodedParams = Array.from(searchParams.entries())
|
|
||||||
.map(([key, value]) => `${key}=${decodeURIComponent(value)}`)
|
|
||||||
.join("&");
|
|
||||||
|
|
||||||
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
|
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
|
||||||
decodedParams ? `?${decodedParams}` : ""
|
queryParams ? `?${queryParams}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
logMessages.push(` Rewritten URL: ${newUrl}`);
|
||||||
|
logMessages.push("\n--------------------------------\n");
|
||||||
|
console.log(logMessages.join("\n"));
|
||||||
|
|
||||||
return NextResponse.rewrite(new URL(newUrl));
|
return NextResponse.rewrite(new URL(newUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +43,10 @@ export function middleware(req: NextRequest) {
|
|||||||
if (token) requestHeaders.set(CLAIM_NAME, token);
|
if (token) requestHeaders.set(CLAIM_NAME, token);
|
||||||
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
|
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
|
||||||
|
|
||||||
|
logMessages.push(` Rewritten URL: ${newUrl}`);
|
||||||
|
logMessages.push("\n--------------------------------\n");
|
||||||
|
console.log(logMessages.join("\n"));
|
||||||
|
|
||||||
return NextResponse.rewrite(new URL(newUrl), {
|
return NextResponse.rewrite(new URL(newUrl), {
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
});
|
});
|
||||||
@@ -39,6 +56,8 @@ export function middleware(req: NextRequest) {
|
|||||||
return NextResponse.redirect(new URL("/dashboard", req.url));
|
return NextResponse.redirect(new URL("/dashboard", req.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logMessages.push("\n--------------------------------\n");
|
||||||
|
console.log(logMessages.join("\n"));
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,11 +67,22 @@ export const contentsDetailSchema = z.object({
|
|||||||
useYn: z.string(),
|
useYn: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const contentsFormSchema = contentsDetailSchema.omit({
|
export const contentsFormSchema = contentsDetailSchema
|
||||||
contsVer: true,
|
.omit({
|
||||||
aprvDt: true,
|
contsVer: true,
|
||||||
autzr: true,
|
aprvDt: true,
|
||||||
});
|
autzr: true,
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
contsSn: z.number().optional(),
|
||||||
|
aprvCd: z
|
||||||
|
.enum([
|
||||||
|
CONTENTS_APPROVE_STATUS.DRAFT,
|
||||||
|
CONTENTS_APPROVE_STATUS.REQUESTED,
|
||||||
|
CONTENTS_APPROVE_STATUS.APPROVED,
|
||||||
|
])
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export type Contents = z.infer<typeof contentsSchema>;
|
export type Contents = z.infer<typeof contentsSchema>;
|
||||||
export type ContentsDetail = z.infer<typeof contentsDetailSchema>;
|
export type ContentsDetail = z.infer<typeof contentsDetailSchema>;
|
||||||
|
|||||||
@@ -194,6 +194,15 @@ function PostingDialog({
|
|||||||
...prev,
|
...prev,
|
||||||
[siteId]: value,
|
[siteId]: value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 상시 선택 시 게시 기간 미설정
|
||||||
|
if (value === "99") {
|
||||||
|
handleUpdateFields(siteId)({
|
||||||
|
pstgPrdSeCd: value,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setRememberPeriodDate((prev) => ({
|
setRememberPeriodDate((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[siteId]: { from, to },
|
[siteId]: { from, to },
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export {
|
|||||||
userSaveSchema,
|
userSaveSchema,
|
||||||
userPasswordUpdateSchema,
|
userPasswordUpdateSchema,
|
||||||
userUpdateSchema,
|
userUpdateSchema,
|
||||||
|
type UserStateCode,
|
||||||
type UserSearchParams,
|
type UserSearchParams,
|
||||||
type UserListResponse,
|
type UserListResponse,
|
||||||
type UserSaveData,
|
type UserSaveData,
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ export const USER_STATE_CODE = {
|
|||||||
삭제: "09",
|
삭제: "09",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type UserStateCode = (typeof USER_STATE_CODE)[keyof typeof USER_STATE_CODE];
|
export type UserStateCode =
|
||||||
|
(typeof USER_STATE_CODE)[keyof typeof USER_STATE_CODE];
|
||||||
|
|
||||||
// 사용자 검색 파라미터
|
// 사용자 검색 파라미터
|
||||||
export interface UserSearchParams extends PageableParams {
|
export interface UserSearchParams extends PageableParams {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
import { AttachmentApi } from "@/entities/attachment";
|
import { AttachmentApi } from "@/entities/attachment";
|
||||||
import {
|
import {
|
||||||
PostApi,
|
PostApi,
|
||||||
|
PostPageGuard,
|
||||||
deleteReasonMessages,
|
deleteReasonMessages,
|
||||||
type PostParams,
|
type PostParams,
|
||||||
} from "@/entities/post";
|
} from "@/entities/post";
|
||||||
@@ -120,143 +121,147 @@ function FaqPage({ bbsId }: { bbsId: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card card-border !mt-6">
|
<PostPageGuard bbsId={bbsId}>
|
||||||
<div className="card card-border">
|
<div className="card card-border !mt-6">
|
||||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
<div className="card card-border">
|
||||||
<Label className="mr-2 text-label">검색</Label>
|
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||||
<Select
|
<Label className="mr-2 text-label">검색</Label>
|
||||||
value={postParams.keywordType}
|
<Select
|
||||||
onValueChange={(keywordType) => {
|
value={postParams.keywordType}
|
||||||
setPostParams({ ...postParams, keywordType });
|
onValueChange={(keywordType) => {
|
||||||
}}
|
setPostParams({ ...postParams, keywordType });
|
||||||
>
|
}}
|
||||||
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
>
|
||||||
<SelectValue placeholder="선택" />
|
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="선택" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectItem value="title">제목</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="all">제목+내용</SelectItem>
|
<SelectItem value="title">제목</SelectItem>
|
||||||
<SelectItem value="writer">작성자</SelectItem>
|
<SelectItem value="all">제목+내용</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value="writer">작성자</SelectItem>
|
||||||
</Select>
|
</SelectContent>
|
||||||
<Input
|
</Select>
|
||||||
value={postParams.keyword}
|
<Input
|
||||||
onChange={(e) =>
|
value={postParams.keyword}
|
||||||
setPostParams({ ...postParams, keyword: e.target.value })
|
onChange={(e) =>
|
||||||
}
|
setPostParams({ ...postParams, keyword: e.target.value })
|
||||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
}
|
||||||
/>
|
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||||
<Button className="h-[3.2rem]" type="submit">
|
/>
|
||||||
조회
|
<Button className="h-[3.2rem]" type="submit">
|
||||||
</Button>
|
조회
|
||||||
</form>
|
</Button>
|
||||||
</div>
|
</form>
|
||||||
<div className="card card-border">
|
</div>
|
||||||
<div className="card-header">
|
<div className="card card-border">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="card-header">
|
||||||
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
|
<div className="flex w-full items-center justify-between">
|
||||||
<div className="flex gap-2">
|
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
|
||||||
<AuthorityButton
|
<div className="flex gap-2">
|
||||||
authority="regAuthrtYn"
|
<AuthorityButton
|
||||||
onClick={() => router.push(`${pathname}/register`)}
|
authority="regAuthrtYn"
|
||||||
>
|
onClick={() => router.push(`${pathname}/register`)}
|
||||||
등록
|
>
|
||||||
</AuthorityButton>
|
등록
|
||||||
<AuthorityButton
|
</AuthorityButton>
|
||||||
authority="delAuthrtYn"
|
<AuthorityButton
|
||||||
color="red"
|
authority="delAuthrtYn"
|
||||||
onClick={onSelectedPostDelete}
|
color="red"
|
||||||
disabled={!selectedPostNumbers.length}
|
onClick={onSelectedPostDelete}
|
||||||
>
|
disabled={!selectedPostNumbers.length}
|
||||||
선택삭제
|
>
|
||||||
</AuthorityButton>
|
선택삭제
|
||||||
|
</AuthorityButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
|
||||||
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
|
<div className="mb-4 flex items-center gap-2 rounded-sm border border-border bg-background p-4">
|
||||||
<div className="mb-4 flex items-center gap-2 rounded-sm border border-border bg-background p-4">
|
<Checkbox
|
||||||
<Checkbox
|
id="all-select"
|
||||||
id="all-select"
|
checked={
|
||||||
checked={selectedPostNumbers.length === postList?.content.length}
|
selectedPostNumbers.length === postList?.content.length
|
||||||
onClick={(e) => e.stopPropagation()}
|
}
|
||||||
onCheckedChange={handleAllCheckboxChange}
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
onCheckedChange={handleAllCheckboxChange}
|
||||||
<Label htmlFor="all-select" className="text-2xl">
|
/>
|
||||||
전체선택
|
<Label htmlFor="all-select" className="text-2xl">
|
||||||
</Label>
|
전체선택
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
{isFetched ? (
|
||||||
|
<Accordion
|
||||||
|
type="multiple"
|
||||||
|
className="secondary flex flex-col gap-4"
|
||||||
|
>
|
||||||
|
{postList?.content.map((item) => (
|
||||||
|
<div key={item.pstNo} className="flex items-start gap-2">
|
||||||
|
<Checkbox
|
||||||
|
id={item.pstNo.toString()}
|
||||||
|
checked={selectedPostNumbers.includes(item.pstNo)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleCheckboxChange(item.pstNo, checked)
|
||||||
|
}
|
||||||
|
className="mx-2 my-6"
|
||||||
|
/>
|
||||||
|
<AccordionItem
|
||||||
|
value={item.pstNo.toString()}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex flex-1 items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span className="text-primary">Q</span>
|
||||||
|
<span>{item.pstTtl}</span>
|
||||||
|
</div>
|
||||||
|
<span className="px-4 text-[1.2rem] font-normal text-label">
|
||||||
|
등록일: {dayjs(item.regDt).format("YYYY-MM-DD")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<span className="text-[1.4rem] font-bold text-label">
|
||||||
|
A
|
||||||
|
</span>
|
||||||
|
<div className="flex-1">
|
||||||
|
<pre className="whitespace-pre-wrap font-noto text-[1.4rem] tracking-tighter text-foreground">
|
||||||
|
{item.pstCn}
|
||||||
|
</pre>
|
||||||
|
<Attachment atflCd={item.atflCd} />
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="white"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`${pathname}/${item.pstNo}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="diveicon di-pencil" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="white"
|
||||||
|
onClick={() => handleDeletePost(item.pstNo)}
|
||||||
|
>
|
||||||
|
<i className="diveicon di-trash" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
) : (
|
||||||
|
<Skeleton className="h-[36rem] w-full" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isFetched ? (
|
|
||||||
<Accordion
|
|
||||||
type="multiple"
|
|
||||||
className="secondary flex flex-col gap-4"
|
|
||||||
>
|
|
||||||
{postList?.content.map((item) => (
|
|
||||||
<div key={item.pstNo} className="flex items-start gap-2">
|
|
||||||
<Checkbox
|
|
||||||
id={item.pstNo.toString()}
|
|
||||||
checked={selectedPostNumbers.includes(item.pstNo)}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
handleCheckboxChange(item.pstNo, checked)
|
|
||||||
}
|
|
||||||
className="mx-2 my-6"
|
|
||||||
/>
|
|
||||||
<AccordionItem
|
|
||||||
value={item.pstNo.toString()}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
<AccordionTrigger>
|
|
||||||
<div className="flex flex-1 items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-primary">Q</span>
|
|
||||||
<span>{item.pstTtl}</span>
|
|
||||||
</div>
|
|
||||||
<span className="px-4 text-[1.2rem] font-normal text-label">
|
|
||||||
등록일: {dayjs(item.regDt).format("YYYY-MM-DD")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<span className="text-[1.4rem] font-bold text-label">
|
|
||||||
A
|
|
||||||
</span>
|
|
||||||
<div className="flex-1">
|
|
||||||
<pre className="whitespace-pre-wrap font-noto text-[1.4rem] tracking-tighter text-foreground">
|
|
||||||
{item.pstCn}
|
|
||||||
</pre>
|
|
||||||
<Attachment atflCd={item.atflCd} />
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end gap-2">
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
color="white"
|
|
||||||
onClick={() =>
|
|
||||||
router.push(`${pathname}/${item.pstNo}`)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i className="diveicon di-pencil" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
color="white"
|
|
||||||
onClick={() => handleDeletePost(item.pstNo)}
|
|
||||||
>
|
|
||||||
<i className="diveicon di-trash" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Accordion>
|
|
||||||
) : (
|
|
||||||
<Skeleton className="h-[36rem] w-full" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PostPageGuard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function AdminPage() {
|
|||||||
const alert = useAlertStore((state) => state.setMessage);
|
const alert = useAlertStore((state) => state.setMessage);
|
||||||
const { confirm } = useConfirm();
|
const { confirm } = useConfirm();
|
||||||
|
|
||||||
const [keywordType, setKeywordType] = useState("");
|
const [keywordType, setKeywordType] = useState("userId");
|
||||||
const [keyword, setKeyword] = useState("");
|
const [keyword, setKeyword] = useState("");
|
||||||
const [grpId, setGrpId] = useState("");
|
const [grpId, setGrpId] = useState("");
|
||||||
const [searchParams, setSearchParams] = useState<UserSearchParams>({
|
const [searchParams, setSearchParams] = useState<UserSearchParams>({
|
||||||
|
|||||||
@@ -35,9 +35,14 @@ export const columns: ColumnDef<Post>[] = [
|
|||||||
accessorKey: "pstgPrdSeNm",
|
accessorKey: "pstgPrdSeNm",
|
||||||
header: "게시기간",
|
header: "게시기간",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const pstgPrdSeCd = row.original.pstgPrdSeCd;
|
||||||
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
||||||
const pstgBgngDt = row.original.pstgBgngDt;
|
const pstgBgngDt = row.original.pstgBgngDt;
|
||||||
const pstgEndDt = row.original.pstgEndDt;
|
const pstgEndDt = row.original.pstgEndDt;
|
||||||
|
|
||||||
|
if (pstgPrdSeCd === "99") {
|
||||||
|
return pstgPrdSeNm;
|
||||||
|
}
|
||||||
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ function CreatePage() {
|
|||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<h3 className="text-2xl font-medium text-label">콘텐츠 등록</h3>
|
<h3 className="text-2xl font-medium text-label">콘텐츠 등록</h3>
|
||||||
</div>
|
</div>
|
||||||
<ContentsForm form={form} handleSave={createContents} />
|
<ContentsForm isEditable form={form} handleSave={createContents} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||||
@@ -24,6 +25,7 @@ import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
|||||||
import { ContentsForm } from "./contents-form";
|
import { ContentsForm } from "./contents-form";
|
||||||
|
|
||||||
function ModifyPage({ sequential }: { sequential: string }) {
|
function ModifyPage({ sequential }: { sequential: string }) {
|
||||||
|
const router = useRouter();
|
||||||
const { confirm } = useConfirm();
|
const { confirm } = useConfirm();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const alert = useAlertStore((state) => state.setMessage);
|
const alert = useAlertStore((state) => state.setMessage);
|
||||||
@@ -51,9 +53,11 @@ function ModifyPage({ sequential }: { sequential: string }) {
|
|||||||
mutationFn: (data: ContentsFormData) => ContentsApi.createContents(data),
|
mutationFn: (data: ContentsFormData) => ContentsApi.createContents(data),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setContsVer(0);
|
setContsVer(0);
|
||||||
alert("수정되었습니다.");
|
alert("수정되었습니다.", () => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contsSn],
|
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contsSn],
|
||||||
|
});
|
||||||
|
router.back();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -62,7 +66,7 @@ function ModifyPage({ sequential }: { sequential: string }) {
|
|||||||
mutationFn: (data: ContentsFormData) =>
|
mutationFn: (data: ContentsFormData) =>
|
||||||
ContentsApi.updateContents(contsSn, contsVer, data),
|
ContentsApi.updateContents(contsSn, contsVer, data),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
alert("수정되었습니다.");
|
alert("수정되었습니다.", router.back);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,9 +74,11 @@ function ModifyPage({ sequential }: { sequential: string }) {
|
|||||||
mutationFn: () => ContentsApi.deleteContents(contsSn, contsVer),
|
mutationFn: () => ContentsApi.deleteContents(contsSn, contsVer),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setContsVer(0);
|
setContsVer(0);
|
||||||
alert("삭제되었습니다.");
|
alert("삭제되었습니다.", () => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contsSn],
|
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contsSn],
|
||||||
|
});
|
||||||
|
router.back();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ function FilePage() {
|
|||||||
mutationFn: (atflId: string) =>
|
mutationFn: (atflId: string) =>
|
||||||
AttachmentApi.deleteAttachmentByAtflId(atflId),
|
AttachmentApi.deleteAttachmentByAtflId(atflId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
query.refetch();
|
alert("삭제되었습니다.", query.refetch);
|
||||||
alert("삭제되었습니다.");
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
keepPreviousData,
|
keepPreviousData,
|
||||||
useQuery,
|
useQuery,
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
Input,
|
Input,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Label,
|
||||||
} from "@common/components";
|
} from "@common/components";
|
||||||
import {
|
import {
|
||||||
UserApi,
|
UserApi,
|
||||||
@@ -41,7 +42,8 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
|||||||
const alert = useAlertStore((state) => state.setMessage);
|
const alert = useAlertStore((state) => state.setMessage);
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [userSearchQuery, setUserSearchQuery] = useState<UserSearchParams>({
|
const [userName, setUserName] = useState("");
|
||||||
|
const [searchParam, setSearchParam] = useState<UserSearchParams>({
|
||||||
userName: "",
|
userName: "",
|
||||||
});
|
});
|
||||||
const [pagination, setPagination] = useState<PaginationState>({
|
const [pagination, setPagination] = useState<PaginationState>({
|
||||||
@@ -53,10 +55,10 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, userSearchQuery, pagination],
|
queryKey: [ENDPOINT.USER_SERVICE.USERS, searchParam, pagination],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
UserApi.findPageUser({
|
UserApi.findPageUser({
|
||||||
...userSearchQuery,
|
...searchParam,
|
||||||
page: pagination.pageIndex,
|
page: pagination.pageIndex,
|
||||||
size: pagination.pageSize,
|
size: pagination.pageSize,
|
||||||
}),
|
}),
|
||||||
@@ -118,6 +120,9 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
|||||||
onRowSelectionChange: setUserRowSelection,
|
onRowSelectionChange: setUserRowSelection,
|
||||||
getRowId: (row) => row.userUnqId,
|
getRowId: (row) => row.userUnqId,
|
||||||
},
|
},
|
||||||
|
dataTableOptions: {
|
||||||
|
viewPagination: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAddUsers = async () => {
|
const handleAddUsers = async () => {
|
||||||
@@ -144,10 +149,30 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
setPagination({ pageIndex: 0, pageSize: 10 });
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setPagination({
|
||||||
|
...pagination,
|
||||||
|
pageIndex: 0,
|
||||||
|
});
|
||||||
|
setSearchParam({ userName });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetSearch = () => {
|
||||||
|
setPagination({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
setUserName("");
|
||||||
|
setSearchParam({ userName: "" });
|
||||||
|
setUserRowSelection({});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) resetSearch();
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@@ -157,20 +182,31 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>사용자 추가</DialogTitle>
|
<DialogTitle>사용자 추가</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="mb-4 flex items-end gap-2">
|
<form onSubmit={handleSearch} className="mb-4 flex items-end gap-2">
|
||||||
<Input
|
<div className="flex items-center gap-2">
|
||||||
value={userSearchQuery.userName}
|
<Label htmlFor="userName" className="mr-2 text-label">
|
||||||
onChange={(e) =>
|
사용자 이름
|
||||||
setUserSearchQuery({
|
</Label>
|
||||||
...userSearchQuery,
|
<Input
|
||||||
userName: e.target.value,
|
id="userName"
|
||||||
})
|
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||||
}
|
value={userName}
|
||||||
className="!text-[1.3rem]"
|
onChange={(e) => setUserName(e.target.value)}
|
||||||
placeholder="사용자 이름으로 검색"
|
placeholder="사용자 이름으로 검색"
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleSearch}>검색</Button>
|
</div>
|
||||||
</div>
|
<Button className="h-[3.2rem]" type="submit">
|
||||||
|
조회
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="white"
|
||||||
|
className="h-[3.2rem]"
|
||||||
|
onClick={resetSearch}
|
||||||
|
>
|
||||||
|
초기화
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
{tableComponent}
|
{tableComponent}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button color="white" onClick={() => setOpen(false)}>
|
<Button color="white" onClick={() => setOpen(false)}>
|
||||||
|
|||||||
@@ -35,9 +35,14 @@ export const columns: ColumnDef<Post>[] = [
|
|||||||
accessorKey: "pstgPrdSeNm",
|
accessorKey: "pstgPrdSeNm",
|
||||||
header: "게시기간",
|
header: "게시기간",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const pstgPrdSeCd = row.original.pstgPrdSeCd;
|
||||||
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
||||||
const pstgBgngDt = row.original.pstgBgngDt;
|
const pstgBgngDt = row.original.pstgBgngDt;
|
||||||
const pstgEndDt = row.original.pstgEndDt;
|
const pstgEndDt = row.original.pstgEndDt;
|
||||||
|
|
||||||
|
if (pstgPrdSeCd === "99") {
|
||||||
|
return pstgPrdSeNm;
|
||||||
|
}
|
||||||
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ function AddUserDialog({ onAddUsers, ...props }: AddUserDialogProps) {
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
|
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>콘텐츠 조회</DialogTitle>
|
<DialogTitle>사용자 조회</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="card card-border !m-0 flex w-full items-center gap-2">
|
<div className="card card-border !m-0 flex w-full items-center gap-2">
|
||||||
|
|||||||
@@ -242,6 +242,16 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="card max-h-[calc(100vh-58rem)] overflow-auto !p-0">
|
<div className="card max-h-[calc(100vh-58rem)] overflow-auto !p-0">
|
||||||
<Table>
|
<Table>
|
||||||
|
<colgroup>
|
||||||
|
<col width="5%" />
|
||||||
|
<col width="5%" />
|
||||||
|
<col width="auto" />
|
||||||
|
<col width="10%" />
|
||||||
|
<col width="10%" />
|
||||||
|
<col width="10%" />
|
||||||
|
<col width="15%" />
|
||||||
|
<col width="15%" />
|
||||||
|
</colgroup>
|
||||||
<TableHeader className="sticky top-0">
|
<TableHeader className="sticky top-0">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
@@ -300,6 +310,9 @@ const UserCells = memo(
|
|||||||
jobPositionCodeData,
|
jobPositionCodeData,
|
||||||
jobGradeCodeData,
|
jobGradeCodeData,
|
||||||
}: UserCellsProps) => {
|
}: UserCellsProps) => {
|
||||||
|
const [jbpsSeCd, setJbpsSeCd] = useState(user.jbpsSeCd);
|
||||||
|
const [jbgdSeCd, setJbgdSeCd] = useState(user.jbgdSeCd);
|
||||||
|
|
||||||
const updateMode = (index: number) => {
|
const updateMode = (index: number) => {
|
||||||
const mode = form.watch(`userOrganization.${index}.mode`);
|
const mode = form.watch(`userOrganization.${index}.mode`);
|
||||||
if (mode === "R") {
|
if (mode === "R") {
|
||||||
@@ -322,17 +335,33 @@ const UserCells = memo(
|
|||||||
<CodeSelect
|
<CodeSelect
|
||||||
codeData={jobPositionCodeData}
|
codeData={jobPositionCodeData}
|
||||||
defaultValue={user.jbpsSeCd}
|
defaultValue={user.jbpsSeCd}
|
||||||
onValueChange={updateFormValue(index, "jbpsSeCd")}
|
onValueChange={(value) => {
|
||||||
|
setJbpsSeCd(value);
|
||||||
|
updateFormValue(index, "jbpsSeCd")(value);
|
||||||
|
}}
|
||||||
{...form.register(`userOrganization.${index}.jbpsSeCd`)}
|
{...form.register(`userOrganization.${index}.jbpsSeCd`)}
|
||||||
/>
|
/>
|
||||||
|
{!jbpsSeCd && (
|
||||||
|
<p className="pt-1 text-base text-destructive">
|
||||||
|
직위를 선택해주세요.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
<CodeSelect
|
<CodeSelect
|
||||||
codeData={jobGradeCodeData}
|
codeData={jobGradeCodeData}
|
||||||
defaultValue={user.jbgdSeCd}
|
defaultValue={user.jbgdSeCd}
|
||||||
onValueChange={updateFormValue(index, "jbgdSeCd")}
|
onValueChange={(value) => {
|
||||||
|
setJbgdSeCd(value);
|
||||||
|
updateFormValue(index, "jbgdSeCd")(value);
|
||||||
|
}}
|
||||||
{...form.register(`userOrganization.${index}.jbgdSeCd`)}
|
{...form.register(`userOrganization.${index}.jbgdSeCd`)}
|
||||||
/>
|
/>
|
||||||
|
{!jbgdSeCd && (
|
||||||
|
<p className="pt-1 text-base text-destructive">
|
||||||
|
직급을 선택해주세요.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -33,9 +33,14 @@ export const columns: ColumnDef<Post>[] = [
|
|||||||
accessorKey: "pstgPrdSeNm",
|
accessorKey: "pstgPrdSeNm",
|
||||||
header: "게시기간",
|
header: "게시기간",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const pstgPrdSeCd = row.original.pstgPrdSeCd;
|
||||||
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
||||||
const pstgBgngDt = row.original.pstgBgngDt;
|
const pstgBgngDt = row.original.pstgBgngDt;
|
||||||
const pstgEndDt = row.original.pstgEndDt;
|
const pstgEndDt = row.original.pstgEndDt;
|
||||||
|
|
||||||
|
if (pstgPrdSeCd === "99") {
|
||||||
|
return pstgPrdSeNm;
|
||||||
|
}
|
||||||
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,8 +3,13 @@ import { useState } from "react";
|
|||||||
import type { Role } from "@/entities/role";
|
import type { Role } from "@/entities/role";
|
||||||
import type { ColumnDef } from "@tanstack/react-table";
|
import type { ColumnDef } from "@tanstack/react-table";
|
||||||
import { MappingRoleDialog } from "../ui/mapping-role-dialog";
|
import { MappingRoleDialog } from "../ui/mapping-role-dialog";
|
||||||
|
import { Button } from "@common/components";
|
||||||
|
|
||||||
export const columns: ColumnDef<Role>[] = [
|
export const createColumns = ({
|
||||||
|
deleteRole,
|
||||||
|
}: {
|
||||||
|
deleteRole: (roleId: string) => void;
|
||||||
|
}): ColumnDef<Role>[] => [
|
||||||
{
|
{
|
||||||
id: "no",
|
id: "no",
|
||||||
header: "NO",
|
header: "NO",
|
||||||
@@ -39,7 +44,18 @@ export const columns: ColumnDef<Role>[] = [
|
|||||||
{
|
{
|
||||||
id: "action",
|
id: "action",
|
||||||
header: "관리",
|
header: "관리",
|
||||||
cell: ({ row }) => <MappingRoleButton roleId={row.original.roleId} />,
|
cell: ({ row }) => (
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
|
<MappingRoleButton roleId={row.original.roleId} />
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="red"
|
||||||
|
onClick={() => deleteRole(row.original.roleId)}
|
||||||
|
>
|
||||||
|
삭제
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
|||||||
import { Button, Input, Label } from "@common/components";
|
import { Button, Input, Label } from "@common/components";
|
||||||
import { RoleApi, type Role, type RoleParams } from "@/entities/role";
|
import { RoleApi, type Role, type RoleParams } from "@/entities/role";
|
||||||
import { ENDPOINT } from "@/shared/config";
|
import { ENDPOINT } from "@/shared/config";
|
||||||
import { useDataTable } from "@/shared/lib";
|
import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib";
|
||||||
import { columns } from "../model/table-columns";
|
import { createColumns } from "../model/table-columns";
|
||||||
|
|
||||||
function RolePage() {
|
function RolePage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const alert = useAlertStore((state) => state.setMessage);
|
||||||
|
|
||||||
const [keyword, setKeyword] = useState("");
|
const [keyword, setKeyword] = useState("");
|
||||||
const [searchParams, setSearchParams] = useState<RoleParams>({
|
const [searchParams, setSearchParams] = useState<RoleParams>({
|
||||||
@@ -33,6 +34,13 @@ function RolePage() {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { mutateAsync: deleteRole } = useMutationWrapper({
|
||||||
|
mutationFn: (roleId: string) => RoleApi.deleteRole(roleId),
|
||||||
|
onSuccess: () => {
|
||||||
|
alert("삭제되었습니다.", query.refetch);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleRowClick = (row: Role) => {
|
const handleRowClick = (row: Role) => {
|
||||||
router.push(`/role/register/${row.roleId}`);
|
router.push(`/role/register/${row.roleId}`);
|
||||||
};
|
};
|
||||||
@@ -40,7 +48,9 @@ function RolePage() {
|
|||||||
const { tableComponent } = useDataTable({
|
const { tableComponent } = useDataTable({
|
||||||
query,
|
query,
|
||||||
tableOptions: {
|
tableOptions: {
|
||||||
columns,
|
columns: createColumns({
|
||||||
|
deleteRole,
|
||||||
|
}),
|
||||||
state: {
|
state: {
|
||||||
pagination,
|
pagination,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,9 +36,14 @@ export const columns: ColumnDef<Post>[] = [
|
|||||||
accessorKey: "pstgPrdSeNm",
|
accessorKey: "pstgPrdSeNm",
|
||||||
header: "게시기간",
|
header: "게시기간",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const pstgPrdSeCd = row.original.pstgPrdSeCd;
|
||||||
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
||||||
const pstgBgngDt = row.original.pstgBgngDt;
|
const pstgBgngDt = row.original.pstgBgngDt;
|
||||||
const pstgEndDt = row.original.pstgEndDt;
|
const pstgEndDt = row.original.pstgEndDt;
|
||||||
|
|
||||||
|
if (pstgPrdSeCd === "99") {
|
||||||
|
return pstgPrdSeNm;
|
||||||
|
}
|
||||||
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import type { ColumnDef } from "@tanstack/react-table";
|
import type { ColumnDef } from "@tanstack/react-table";
|
||||||
import type { UserListResponse } from "@/entities/user";
|
import {
|
||||||
|
USER_STATE_CODE,
|
||||||
|
type UserListResponse,
|
||||||
|
type UserStateCode,
|
||||||
|
} from "@/entities/user";
|
||||||
|
import { Badge } from "@common/components";
|
||||||
|
|
||||||
export const columns: ColumnDef<UserListResponse>[] = [
|
export const columns: ColumnDef<UserListResponse>[] = [
|
||||||
{
|
{
|
||||||
@@ -42,8 +47,11 @@ export const columns: ColumnDef<UserListResponse>[] = [
|
|||||||
header: "기관/부서",
|
header: "기관/부서",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "userStateCodeName",
|
accessorKey: "userStateCode",
|
||||||
header: "회원상태",
|
header: "회원상태",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return getUserStateBadge(row.original.userStateCode);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "aprvYmd",
|
accessorKey: "aprvYmd",
|
||||||
@@ -63,3 +71,18 @@ export const columns: ColumnDef<UserListResponse>[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const getUserStateBadge = (userStateCode: UserStateCode) => {
|
||||||
|
switch (userStateCode) {
|
||||||
|
case USER_STATE_CODE.대기:
|
||||||
|
return <Badge color="gray">대기</Badge>;
|
||||||
|
case USER_STATE_CODE.정상:
|
||||||
|
return <Badge color="green">정상</Badge>;
|
||||||
|
case USER_STATE_CODE.정지:
|
||||||
|
return <Badge color="yellow">정지</Badge>;
|
||||||
|
case USER_STATE_CODE.탈퇴:
|
||||||
|
return <Badge color="purple">탈퇴</Badge>;
|
||||||
|
case USER_STATE_CODE.삭제:
|
||||||
|
return <Badge color="red">삭제</Badge>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,20 +16,11 @@ function CreatePage() {
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
userId: "",
|
userId: "",
|
||||||
userName: "",
|
userName: "",
|
||||||
email: "",
|
|
||||||
password: "",
|
password: "",
|
||||||
pswdHnt: "",
|
pswdHnt: "",
|
||||||
pswdAns: "",
|
|
||||||
mphn: "",
|
|
||||||
telno: "",
|
|
||||||
zip: "",
|
|
||||||
addr: "",
|
|
||||||
daddr: "",
|
|
||||||
laeSeCd: "",
|
laeSeCd: "",
|
||||||
ogdpSeCd: "",
|
ogdpSeCd: "",
|
||||||
expryYmd: "",
|
expryYmd: "",
|
||||||
instSeCd: "",
|
|
||||||
enoCd: "",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user