스마트서비스 최종완료보고 버전

This commit is contained in:
CodeTempla
2026-05-19 20:58:19 +09:00
commit d26eb50875
405 changed files with 46755 additions and 0 deletions
@@ -0,0 +1,125 @@
"use client";
import React, { useState, useRef, useEffect } from "react";
import CtModal from "@/components/CtModal";
import Label from '@/components/form/Label2';
import Input from '@/components/form/input/InputField2';
import Select from '@/components/form/Select2';
import { useMultiState } from "@/hooks/useMultiState";
import api from '@/lib/_AG';
export interface CustomerAccountModalProps {
biz_group_name?: string; // internal use
biz_reg_num?: string; // internal use
isModify: boolean; // external set
gid: string; // external set
//
biz_group_id: string;
new_biz_group_id: string;
user_id: string;
user_pw: string;
nick_name: string;
email: string;
state: string;
}
interface AddModifyCustomerAccountModalProps {
multiState: ReturnType<typeof useMultiState<CustomerAccountModalProps>>;
onOk: () => void;
isOpen: boolean;
closeModal: () => void;
}
export default function AddModifyCustomerAccountModal({ multiState, onOk, isOpen, closeModal }: AddModifyCustomerAccountModalProps) {
const handleClickAddOrModify = () => {
if (multiState.values.user_id == "") {
alert("계정 아이디를 입력하셔야 합니다.");
return;
}
else if (multiState.values.user_pw == "") {
alert("계정 비밀번호를 입력하셔야 합니다.");
return;
}
const { biz_group_name, biz_reg_num, ...params } = multiState.values;
if (multiState.values.isModify) {
api.post('/api/modify-account.do', params).then((resp) => {
console.log(resp);
if (resp.data.errCode == 0) {
alert("수정되었습니다.");
onOk();
}
else {
alert("일시적으로 사용할 수 없습니다 : " + resp.data.errCode);
}
})
.catch((err) => console.error(err))
}
else {
api.post('/api/add-account.do', params).then((resp) => {
console.log(resp);
if (resp.data.errCode == 0) {
alert("추가되었습니다.");
onOk();
}
else {
alert("일시적으로 사용할 수 없습니다 : " + resp.data.errCode);
}
})
.catch((err) => console.error(err))
}
}
return (
// <div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
<CtModal
title={multiState.values.isModify ? "계정 정보 수정": "계정 등록"}
description={multiState.values.isModify ? "수정할 계정 정보를 입력하세요." : "새로 등록할 계정 정보를 입력하세요."}
okBtnText={multiState.values.isModify ? "수정": "추가"}
onClickOk={handleClickAddOrModify}
isOpen={isOpen}
closeModal={closeModal}
className="max-w-[700px] p-6 lg:p-10"
>
<div>
<Label></Label>
<Input type="text" value={multiState.values.biz_group_name + "(" + multiState.values.biz_reg_num + ")"} disabled />
</div>
<div>
<Label redPoint></Label>
<Input type="text" value={multiState.values.user_id} onChange={(e) => multiState.set("user_id", e.target.value)} disabled={multiState.values.isModify ? true : false} />
</div>
<div>
<Label redPoint></Label>
<Input type="password" value={multiState.values.user_pw} onChange={(e) => multiState.set("user_pw", e.target.value)} />
</div>
<div>
<Label></Label>
<Input type="text" value={multiState.values.nick_name} onChange={(e) => multiState.set("nick_name", e.target.value)} />
</div>
<div>
<Label></Label>
<Input type="email" value={multiState.values.email} onChange={(e) => multiState.set("email", e.target.value)} />
</div>
<div>
<Label></Label>
<Select
placeholder="선택하세요"
options={[
{ label: "정상", value: "1" },
{ label: "정지", value: "4" },
]}
defaultValue={multiState.values.state} onChange={(value) => multiState.set("state", value)}
className="dark:bg-dark-900" />
</div>
</CtModal>
// </div>
);
};
@@ -0,0 +1,434 @@
"use client"
import PageBreadcrumb from "@/components/common/PageBreadCrumb2";
import ComponentCard from '@/components/common/ComponentCard2';
import BizGroupPanel from "@/components/common/BizGroupPanel";
import CtTable1 from '@/components/tables/CtTable1';
import Button from '@/components/ui/button/Button2';
import { DocsIcon, TableIcon, PlusIcon, TrashBinIcon, PencilIcon } from "@/icons";
import WithLabel from '@/components/form/WithLabel';
import Input from '@/components/form/input/InputField2';
import DatePicker from '@/components/form/date-picker2';
import Badge from "@/components/ui/badge/Badge";
import React, { Key } from "react";
import { useEffect, useState } from 'react';
import api, { useAuthStore, convertDateTime2, convertDateTime3, useUiLoadingStore, postFileDownload } from '@/lib/_AG';
import { useModal } from "@/hooks/useModal";
import AddModifyCustomerAccountModal, { CustomerAccountModalProps } from './AddModifyCustomerAccountModal';
import { useMultiState } from "@/hooks/useMultiState";
import CtTree, { CtTreeNode, findNodeByKey, findRootNodeByKey, treeToList } from "@/components/CtTree";
import { reqBizGroupTree2, reqBizGroupTree3 } from "../biz-group/BizGroupContent";
import Checkbox from "@/components/form/input/Checkbox";
import ManageBizGroupModal from "../biz-group/ManageBizGroupModal";
const SEARCH_LABEL_WIDTH = 24;
const COUNT_PER_PAGE = 10;
const PAGE_NUM_COUNT = 5;
export default function CustomerAccount() {
const tokenPayload = useAuthStore((s) => s.tokenPayload); //Bruce, JWT
const isLoadingGlobal = useUiLoadingStore((s) => s.isLoading);
const [ tableData, setTableData ] = useState<any[]>([]);
const [ tableOffset, setTableOffset ] = useState<number>(0);
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
const [ treeData, setTreeData ] = useState<CtTreeNode[]>([]);
const [ selectedTreeKeys, setSelectedTreeKeys ] = useState<Key[]>([]);
const [ expandedTreeKeys, setExpandedTreeKeys ] = useState<Key[]>([]);
const [ isGroupAccess, setGroupAccess ] = useState(tokenPayload?.permission == 1); //TempCode
const [ isTreeOpen, setTreeOpen ] = useState(tokenPayload?.permission == 1); //TempCode
// group search fields
const [ name, setName ] = useState<string>("");
const [ biz_reg_num, setBizRegNum ] = useState<string>("");
//search fields
const [ user_id, setUserId ] = useState<string>("");
const [ email, setEmail ] = useState<string>("");
const [ nick_name, setNickName ] = useState<string>("");
const [ date_start, setDateStart ] = useState<string>("");
const [ date_end, setDateEnd ] = useState<string>("");
// modal
const { isOpen, openModal, closeModal } = useModal();
const modalData = useMultiState<CustomerAccountModalProps>({biz_group_name: "", biz_reg_num: "", isModify: false, gid: "", biz_group_id: "", new_biz_group_id: "", user_id: "", user_pw: "", nick_name: "", email: "", state: "1" });
const { isOpen: isOpenManageBizGroup, openModal: openModalManageBizGroup, closeModal: closeModalManageBizGroup } = useModal();
//function part
//
function reqAccountList(is_excel: boolean, biz_group_id: string, groupAccess: boolean) {
if (!biz_group_id)
return;
const params = {
is_excel: is_excel,
is_group_access: groupAccess,
offset: tableOffset,
limit: COUNT_PER_PAGE,
user_id: user_id,
email: email,
nick_name: nick_name,
date_start: date_start,
date_end: date_end,
biz_group_id: biz_group_id,
}
if (is_excel == true) {
postFileDownload('/api/get-account.do', params);
return;
}
api.post('/api/get-account.do', params).then((resp) => {
console.log(resp);
if (resp.data.errCode === 0) {
if (resp.data.result.list.length == 0) {
alert("조회 결과가 없습니다.");
setTableTotalCount(0);
setTableData([]);
return;
}
setTableTotalCount(resp.data.result.totalCount);
setTableData(resp.data.result.list);
}
else {
alert("일시적으로 사용할 수 없습니다 : " + resp.data.errCode);
}
})
.catch((err) => console.error(err));
}
function reqRemoveAccount(gid: string) {
const params = {
ids: gid
}
api.post('/api/remove-account.do', params).then((resp) => {
console.log(resp);
if (resp.data.errCode == 0) {
reqAccountList(false, selectedTreeKeys[0] as string, isGroupAccess);
}
else {
alert("일시적으로 사용할 수 없습니다 : " + resp.data.errCode);
}
})
.catch((err) => console.error(err))
}
//
// event processing part
//
const handleClickGroupManage = () => {
openModalManageBizGroup();
}
const handleManageBizGroupModalClose = () => {
closeModalManageBizGroup();
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
reqAccountList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
});
};
const handleClickGroupSearch = () => {
if (tokenPayload?.permission == 1) {
if (name == "" && biz_reg_num == "") {
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
return;
}
if (name != "" && name.length < 3) {
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
return;
}
if (biz_reg_num != "" && biz_reg_num.length < 3) {
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
return;
}
reqBizGroupTree2(true, "", "", "", "", name, biz_reg_num, treeData, setTreeData);
}
else {
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
//reqAccountList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
reqAccountList(false, String(resp.data.result.tree[0].key), isGroupAccess);
});
}
};
const handleClickNode = (key: Key) => {
if (isGroupAccess == false) {
const root = findRootNodeByKey(treeData, key as string);
reqAccountList(false, root.key as string, isGroupAccess);
return;
}
setTableOffset(0);
reqAccountList(false, key as string, isGroupAccess);
}
const handleClickTreeExpanded = (key: Key) => {
const node = findNodeByKey(treeData, key);
if (node.children == undefined)
reqBizGroupTree2(false, key as string, "", "", "", "", "2", treeData, setTreeData);
}
const handleClickGroupAccess = (value: boolean) => {
setGroupAccess(value);
if (tokenPayload?.permission == 1) {
if (selectedTreeKeys.length <= 0) {
return;
}
reqAccountList(false, selectedTreeKeys[0] as string, value);
return;
}
if (value == false || selectedTreeKeys.length <= 0) {
reqAccountList(false, String(tokenPayload?.biz_group_id), value);
}
else {
reqAccountList(false, selectedTreeKeys[0] as string, value);
}
}
// tree part //////////
const handleClickAdd = () => {
if (selectedTreeKeys.length <= 0) {
alert("신규 추가하려면 사업자를 선택해야 합니다.");
return;
}
/*
const treeList = treeToList(treeData);
console.log("treeList: ", treeList)
const optBizGroup = treeList.map((item: any) => (
{ label: item.title, value: item.key }
));
console.log("optBizGroup: ", optBizGroup)
modalData.set("optBizGroup", optBizGroup);
*/
const node = findNodeByKey(treeData, selectedTreeKeys[0]);
modalData.setAll({
isModify: false,
biz_group_name: node.name,
biz_reg_num: node.biz_reg_num,
biz_group_id: node.key,
new_biz_group_id: "",
user_id: "",
user_pw: "",
nick_name: "",
email: "",
state: "1"
});
openModal();
};
const handleClickDownload = () => {
reqAccountList(true, selectedTreeKeys[0] as string, isGroupAccess);
};
const handleClickSearch = () => {
if (selectedTreeKeys.length <= 0) {
alert("조회하려면 사업자를 선택해야 합니다.");
return;
}
reqAccountList(false, selectedTreeKeys[0] as string, isGroupAccess);
};
const handlePageChange = (page: number) => {
setTableOffset((page - 1) * COUNT_PER_PAGE);
};
const handleRowClickModify = (row: number) => {
modalData.setAll({...tableData[row], isModify: true});
openModal();
};
const handleRowClickDelete = (row: number) => {
if (confirm("'" + tableData[row].user_id + "' 계정을 삭제하시겠습니까?")) {
reqRemoveAccount(tableData[row].gid);
}
};
const handleModalOk = () => {
closeModal();
reqAccountList(false, selectedTreeKeys[0] as string, isGroupAccess);
}
//
useEffect(() => {
if (selectedTreeKeys.length > 0)
reqAccountList(false, selectedTreeKeys[0] as string, isGroupAccess);
}, [tableOffset]);
useEffect(() => {
if (tokenPayload == undefined)
return;
if (tokenPayload?.permission == 1)
return;
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
setGroupAccess(tokenPayload?.permission == 1);
setTreeOpen(tokenPayload?.permission == 1);
reqAccountList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
});
}, [tokenPayload]);
const columns = [
{
title: "등록일",
key: "reg_time" ,
renderItem: (item:any) => convertDateTime3(item.reg_time),
},
{
title: "그룹",
key: "biz_group_name",
},
{
title: "아이디",
key: "user_id",
},
{
title: "닉네임",
key: "nick_name",
},
{
title: "이메일",
key: "email",
},
{
title: "상태",
key: "state",
renderItem: (item : any) => (
<Badge size="sm" color={item.state === 1 ? "success" : item.state === 4 ? "error" : "warning"}>
{item.state === 1 ? "정상" : "정지"}
</Badge>
)
},
{
title: "수정",
key: "_",
renderItem: (item: any, row: number) => (
<div>
<PencilIcon
className="cursor-pointer hover:fill-error-500 dark:hover:fill-error-500 fill-gray-700 dark:fill-gray-400"
onClick={() => {handleRowClickModify(row)}} />
</div>
),
},
{
title: "삭제",
key: "_",
renderItem: (item: any, row: number) => (
<div>
<TrashBinIcon
className="cursor-pointer hover:fill-error-500 dark:hover:fill-error-500 fill-gray-700 dark:fill-gray-400"
onClick={() => {handleRowClickDelete(row)}} />
</div>
),
},
];
return (
<div>
<PageBreadcrumb pageTitle1="계정 및 그룹관리" pageTitle2="계정관리" />
<div className="space-y-3">
<BizGroupPanel
title="사업자 그룹별로 관리"
isOpen={isTreeOpen}
setOpen={setTreeOpen}
name={name}
bizRegNum={biz_reg_num}
onChangeName={setName}
onChangeBizRegNum={setBizRegNum}
onClickSearch={handleClickGroupSearch}
onClickManage={handleClickGroupManage}
isDisabled={isLoadingGlobal}
searchLabelWidth={SEARCH_LABEL_WIDTH}
>
<CtTree
treeData={treeData}
selectedKeys={selectedTreeKeys}
setSelectedKeys={setSelectedTreeKeys}
onClickNode={handleClickNode}
expandedKeys={expandedTreeKeys}
setExpandedKeys={setExpandedTreeKeys}
onClickExpanded={handleClickTreeExpanded}
/>
</BizGroupPanel>
<ComponentCard title="계정 목록 조회" titleIcon={<TableIcon />}>
<div className="space-y-3">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
<WithLabel label="아이디" label_width={SEARCH_LABEL_WIDTH}>
<Input type="text" placeholder="조회할 아이디를 입력하세요." value={user_id} onChange={(e) => setUserId(e.target.value)} />
</WithLabel>
<WithLabel label="이메일" label_width={SEARCH_LABEL_WIDTH}>
<Input type="text" placeholder="조회할 이메일을 입력하세요." value={email} onChange={(e) => setEmail(e.target.value)} />
</WithLabel>
<WithLabel label="닉네임" label_width={SEARCH_LABEL_WIDTH}>
<Input type="text" placeholder="조회할 닉네임을 입력하세요." value={nick_name} onChange={(e) => setNickName(e.target.value)} />
</WithLabel>
</div>
<div className="space-y-2 grid grid-cols-1 gap-3 sm:grid-cols-3">
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<DatePicker
id="date_start-picker"
placeholder="조회 시작일"
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
/>
<DatePicker
id="date_end-picker"
placeholder="조회 종료일"
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
/>
</div>
</WithLabel>
<div className="flex items-center gap-3">
<Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} />
<span className="block font-normal text-gray-700 text-theme-sm dark:text-gray-400">
</span>
</div>
<div className="flex items-center justify-end gap-5">
<Button size="sm" variant="outline" startIcon={<DocsIcon />} onClick={handleClickDownload}> </Button>
<Button size="sm" variant="primary" onClick={handleClickSearch} disabled={isLoadingGlobal}></Button>
</div>
</div>
</div>
<CtTable1 columns={columns} bodyData={tableData}
offset={tableOffset} totalCount={tableTotalCount} countPerPage={COUNT_PER_PAGE} pageNumCount={PAGE_NUM_COUNT}
onClickPageChange={handlePageChange} />
<div>
<Button size="sm" variant="primary" startIcon={<PlusIcon />} onClick={handleClickAdd} disabled={isLoadingGlobal}> </Button>
</div>
</ComponentCard>
<ManageBizGroupModal treeData={treeData} isOpen={isOpenManageBizGroup} closeModal={handleManageBizGroupModalClose} />
<AddModifyCustomerAccountModal multiState={modalData} onOk={handleModalOk} isOpen={isOpen} closeModal={closeModal} />
</div>
</div>
);
}
@@ -0,0 +1,15 @@
import CustomerAccount from './CustomerAccount';
import { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "계정관리",
description: "계정관리",
};
export default function AccountPage() {
return (
<CustomerAccount />
);
}