3달간 수정 사항
This commit is contained in:
Generated
+476
-485
File diff suppressed because it is too large
Load Diff
@@ -24,10 +24,13 @@
|
||||
"axios": "^1.6.0",
|
||||
"express": "^5.1.0",
|
||||
"flatpickr": "^4.6.13",
|
||||
"headlessui": "^0.0.0",
|
||||
"html-to-image": "^1.11.13",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"morgan": "^1.10.1",
|
||||
"next": "^15.5.6",
|
||||
"pretendard": "^1.3.9",
|
||||
"rc-tree": "^5.13.1",
|
||||
"react": "^19.0.0",
|
||||
"react-apexcharts": "^1.7.0",
|
||||
@@ -47,6 +50,7 @@
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"baseline-browser-mapping": "^2.10.40",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.3",
|
||||
"postcss": "^8",
|
||||
|
||||
Binary file not shown.
@@ -11,7 +11,7 @@ import Badge from "@/components/ui/badge/Badge";
|
||||
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import api, { convertDateTime2, convertDateTime3, postFileDownload, useAuthStore } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime2, convertDateTime3, postFileDownload, todayYMD, useAuthStore } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyAccountModal, { AccountModalProps } from './AddModifyAccountModal';
|
||||
@@ -19,10 +19,12 @@ import { useMultiState } from "@/hooks/useMultiState";
|
||||
|
||||
import CtTree, { CtTreeNode, expandAndSelectTreeNode, getAllNodeKeys, findParentKeys, findNodeByKey, addNodeByKey, deleteNodeByKey } from "@/components/CtTree";
|
||||
import { reqBizGroupTree2 } from "@/app/(admin)/biz-group/BizGroupContent";
|
||||
import Select from "@/components/form/Select2";
|
||||
import PageCountSelector from "@/components/PageCountSelector";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
//const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -33,6 +35,8 @@ export default function Account() {
|
||||
const [ tableOffset, setTableOffset ] = useState<number>(0);
|
||||
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
|
||||
|
||||
const [ COUNT_PER_PAGE, setCOUNT_PER_PAGE ] = useState<number>(20);
|
||||
|
||||
const [ treeData, setTreeData ] = useState<CtTreeNode[]>([]);
|
||||
const [ selectedTreeKeys, setSelectedTreeKeys ] = useState<Key[]>([]);
|
||||
const [ expandedTreeKeys, setExpandedTreeKeys ] = useState<Key[]>([]);
|
||||
@@ -45,12 +49,14 @@ export default function Account() {
|
||||
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>("");
|
||||
const [ date_start, setDateStart ] = useState<string>();
|
||||
const [ date_end, setDateEnd ] = useState<string>();
|
||||
|
||||
// modal
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
const modalData = useMultiState<AccountModalProps>({biz_group_name: "", isModify: false, gid: "", biz_group_id: "", user_id: "", user_pw: "", nick_name: "", email: "", state: "1"});
|
||||
const modalData = useMultiState<AccountModalProps>({ biz_group_name: "", isModify: false,
|
||||
gid: "", biz_group_id: "", user_id: "", user_pw: "", name: "", phone: "", email: "", state: "1",
|
||||
perm_group: true, perm_uid1: true, perm_cancel: true, perm_account: true });
|
||||
|
||||
//function part
|
||||
//
|
||||
@@ -84,6 +90,13 @@ export default function Account() {
|
||||
setTableData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
resp.data.result.list.forEach((item: any) => {
|
||||
item.perm_group = (item.permission & 0x2) === 0x2;
|
||||
item.perm_uid1 = (item.permission & 0x4) === 0x4;
|
||||
item.perm_cancel = (item.permission & 0x8) === 0x8;
|
||||
});
|
||||
|
||||
setTableTotalCount(resp.data.result.totalCount);
|
||||
setTableData(resp.data.result.list);
|
||||
}
|
||||
@@ -117,17 +130,17 @@ export default function Account() {
|
||||
// event processing part
|
||||
//
|
||||
const handleClickGroupSearch = () => {
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (name == "" && biz_reg_num == "") {
|
||||
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (name != "" && name.length < 3) {
|
||||
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
|
||||
if (name != "" && name.length < 2) {
|
||||
alert("그룹(사업자)명은 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 3) {
|
||||
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 2) {
|
||||
alert("사업자등록번호는 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,7 +170,7 @@ export default function Account() {
|
||||
|
||||
const node = findNodeByKey(treeData, selectedTreeKeys[0]);
|
||||
modalData.setAll({biz_group_name: node.name, biz_reg_num: node.biz_reg_num,
|
||||
isModify: false, gid: "", user_id: "", user_pw: "", nick_name: "", email: "", state: "1", biz_group_id: node.key});
|
||||
isModify: false, gid: "", user_id: "", user_pw: "", name: "", phone: "", email: "", state: "1", biz_group_id: node.key});
|
||||
openModal();
|
||||
};
|
||||
|
||||
@@ -210,18 +223,25 @@ export default function Account() {
|
||||
title: "등록일",
|
||||
key: "reg_time" ,
|
||||
renderItem: (item:any) => convertDateTime3(item.reg_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "그룹",
|
||||
key: "biz_group_name",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "아이디",
|
||||
key: "user_id",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "닉네임",
|
||||
key: "nick_name",
|
||||
title: "이름",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "휴대폰번호",
|
||||
key: "phone",
|
||||
},
|
||||
{
|
||||
title: "이메일",
|
||||
@@ -270,11 +290,11 @@ export default function Account() {
|
||||
<div className="flex-1 space-y-6">
|
||||
{/*<div className="hidden sm:block h-[86px]"></div>*/}
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="그룹명(사업자명)" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="조회할 그룹명(사업자명) 입력하세요." value={name} onChange={(e) => setName(e.target.value)}
|
||||
<WithLabel label="그룹(사업자)명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 그룹(사업자)명 입력하세요." value={name} onChange={(e) => setName(e.target.value)}
|
||||
onKeyDown={(e) => {if (e.key === "Enter") handleClickGroupSearch()}} />
|
||||
</WithLabel>
|
||||
<WithLabel label="사업자번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="사업자번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 사업자번호를 입력하세요." value={biz_reg_num} onChange={(e) => setBizRegNum(e.target.value)}
|
||||
onKeyDown={(e) => {if (e.key === "Enter") handleClickGroupSearch()}} />
|
||||
</WithLabel>
|
||||
@@ -304,28 +324,30 @@ export default function Account() {
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<WithLabel label="아이디" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="아이디" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 아이디를 입력하세요." value={user_id} onChange={(e) => setUserId(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="이메일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="이메일" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="email" placeholder="조회할 이메일을 입력하세요." value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="닉네임" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="닉네임" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<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-2">
|
||||
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
@@ -336,6 +358,10 @@ export default function Account() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
<PageCountSelector defaultValue={COUNT_PER_PAGE.toString()} onChange={(e) => setCOUNT_PER_PAGE(parseInt(e.target.value))} />
|
||||
</div>
|
||||
|
||||
<CtTable1 columns={columns} bodyData={tableData}
|
||||
offset={tableOffset} totalCount={tableTotalCount} countPerPage={COUNT_PER_PAGE} pageNumCount={PAGE_NUM_COUNT}
|
||||
onClickPageChange={handlePageChange} />
|
||||
|
||||
@@ -5,7 +5,8 @@ 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';
|
||||
import api, { useAuthStore } from '@/lib/_AG';
|
||||
import Checkbox from "@/components/form/input/Checkbox";
|
||||
|
||||
|
||||
|
||||
@@ -18,9 +19,14 @@ export interface AccountModalProps {
|
||||
biz_group_id: string;
|
||||
user_id: string;
|
||||
user_pw: string;
|
||||
nick_name: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
state: string;
|
||||
perm_group: boolean;
|
||||
perm_uid1: boolean;
|
||||
perm_cancel: boolean;
|
||||
perm_account: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +38,11 @@ interface AddModifyAccountModalProps {
|
||||
}
|
||||
|
||||
export default function AddModifyAccountModal({ multiState, onOk, isOpen, closeModal }: AddModifyAccountModalProps) {
|
||||
const tokenPayload = useAuthStore((s) => s.tokenPayload); //Bruce, JWT
|
||||
|
||||
const [ user_pw_confirm, setUserPwConfirm ] = useState<string>("");
|
||||
|
||||
|
||||
const handleClickAddOrModify = () => {
|
||||
if (multiState.values.user_id == "") {
|
||||
alert("계정 아이디를 입력하셔야 합니다.");
|
||||
@@ -42,6 +52,22 @@ export default function AddModifyAccountModal({ multiState, onOk, isOpen, closeM
|
||||
alert("계정 비밀번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
else if (user_pw_confirm == "") {
|
||||
alert("비밀번호 확인을 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
else if (multiState.values.user_pw !== user_pw_confirm) {
|
||||
alert("비밀번호 확인이 일치하지 않습니다.");
|
||||
return;
|
||||
}
|
||||
else if (multiState.values.name == "") {
|
||||
alert("이름 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
else if (multiState.values.phone == "") {
|
||||
alert("휴대폰 번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (multiState.values.isModify) {
|
||||
api.post('/api/modify-account.do', multiState.values).then((resp) => {
|
||||
@@ -87,7 +113,7 @@ export default function AddModifyAccountModal({ multiState, onOk, isOpen, closeM
|
||||
>
|
||||
<div>
|
||||
<Label>사업자원장</Label>
|
||||
<Input type="text" value={multiState.values.biz_group_name + "(" + multiState.values.biz_reg_num + ")"} disabled />
|
||||
<Input type="text" value={multiState.values.biz_group_name + (multiState.values.biz_reg_num ? `(${multiState.values.biz_reg_num})` : "")} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<Label redPoint>아이디</Label>
|
||||
@@ -95,16 +121,33 @@ export default function AddModifyAccountModal({ multiState, onOk, isOpen, closeM
|
||||
</div>
|
||||
<div>
|
||||
<Label redPoint>비밀번호</Label>
|
||||
<Input type="password" value={multiState.values.user_pw} onChange={(e) => multiState.set("user_pw", e.target.value)} />
|
||||
<Input type="password" defaultValue="" 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)} />
|
||||
<Label redPoint>비밀번호 확인</Label>
|
||||
<Input type="password" value={user_pw_confirm} onChange={(e) => setUserPwConfirm(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<Label redPoint>이름</Label>
|
||||
<Input type="text" value={multiState.values.name} onChange={(e) => multiState.set("name", e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<Label redPoint>휴대폰</Label>
|
||||
<Input type="text" value={multiState.values.phone} onChange={(e) => multiState.set("phone", 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>
|
||||
<div className="flex items-center gap-5">
|
||||
<Checkbox checked={multiState.values.perm_group} onChange={(value) => multiState.set("perm_group", value)} label="그룹변경" />
|
||||
<Checkbox checked={multiState.values.perm_uid1} onChange={(value) => multiState.set("perm_uid1", value)} label="TID변경" />
|
||||
<Checkbox checked={multiState.values.perm_cancel} onChange={(value) => multiState.set("perm_cancel", value)} label="카드승인취소" />
|
||||
<Checkbox checked={multiState.values.perm_account} onChange={(value) => multiState.set("perm_account", value)} label="계정관리" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>상태</Label>
|
||||
<Select
|
||||
@@ -113,7 +156,7 @@ export default function AddModifyAccountModal({ multiState, onOk, isOpen, closeM
|
||||
{ label: "정상", value: "1" },
|
||||
{ label: "정지", value: "4" },
|
||||
]}
|
||||
defaultValue={multiState.values.state} onChange={(value) => multiState.set("state", value)}
|
||||
defaultValue={multiState.values.state} onChange={(e) => multiState.set("state", e.target.value)}
|
||||
className="dark:bg-dark-900" />
|
||||
</div>
|
||||
</CtModal>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Label from '@/components/form/Label2';
|
||||
import Input from '@/components/form/input/InputField2';
|
||||
import CtModal from "@/components/CtModal";
|
||||
import { useMultiState } from "@/hooks/useMultiState";
|
||||
import api, { formatBusinessNumber } from '@/lib/_AG';
|
||||
import api, { formatBusinessNumber, isValidBusinessNumber } from '@/lib/_AG';
|
||||
|
||||
|
||||
export interface BizGroupModalProps {
|
||||
@@ -30,13 +30,17 @@ export default function AddModifyBizGroupModal({ multiState, onOk, isOpen, close
|
||||
|
||||
const handleClickAddOrModify = () => {
|
||||
if (multiState.values.name == "") {
|
||||
alert("그룹명(사업자명)을 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명을 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
//else if (multiState.values.biz_reg_num == "") {
|
||||
// alert("사업자등록번호를 입력하셔야 합니다.");
|
||||
// return;
|
||||
//}
|
||||
if (multiState.values.biz_reg_num != "" && !isValidBusinessNumber(multiState.values.biz_reg_num)) {
|
||||
if (!confirm("잘못된 형식의 사업자등록번호입니다. 그래도 등록하시겠습니까?"))
|
||||
return;
|
||||
}
|
||||
|
||||
if (multiState.values.isModify) {
|
||||
api.post('/api/modify-biz-group.do', multiState.values).then((resp) => {
|
||||
@@ -96,7 +100,7 @@ export default function AddModifyBizGroupModal({ multiState, onOk, isOpen, close
|
||||
className="max-w-[700px] p-6 lg:p-10"
|
||||
>
|
||||
<div>
|
||||
<Label redPoint>그룹명(사업자명)</Label>
|
||||
<Label redPoint>그룹(사업자)명</Label>
|
||||
<Input type="text" value={multiState.values.name} onChange={(e) => multiState.set("name", e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import Input from '@/components/form/input/InputField2';
|
||||
import DatePicker from '@/components/form/date-picker2';
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import api, { convertDateTime3, formatBusinessNumber, useAuthStore } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, formatBusinessNumber, isValidBusinessNumber, todayYMD, useAuthStore } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyBizGroupModal, { BizGroupModalProps } from './AddModifyBizGroupModal';
|
||||
@@ -19,7 +19,7 @@ import CtTree, { CtTreeNode, expandAndSelectTreeNode, getAllNodeKeys, findParent
|
||||
import { tree } from "next/dist/build/templates/app-page";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
|
||||
|
||||
// api component
|
||||
@@ -126,22 +126,22 @@ export default function BizGroupContent({
|
||||
}: BizGroupContentProps) {
|
||||
const tokenPayload = useAuthStore((s) => s.tokenPayload); //Bruce, JWT
|
||||
|
||||
const {isOpen, openModal, closeModal} = useModal();
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
|
||||
// tree part
|
||||
const [treeData, setTreeData] = useState<CtTreeNode[]>(preloadTreeData ? preloadTreeData : []);
|
||||
const [selectedTreeKeys, setSelectedTreeKeys] = useState<Key[]>(preloadTreeData ? [preloadTreeData[0].key as string] : []);
|
||||
const [expandedTreeKeys, setExpandedTreeKeys] = useState<Key[]>(preloadTreeData ? getAllNodeKeys(preloadTreeData) : []);
|
||||
const [ treeData, setTreeData ] = useState<CtTreeNode[]>(preloadTreeData ? preloadTreeData : []);
|
||||
const [ selectedTreeKeys, setSelectedTreeKeys ] = useState<Key[]>(preloadTreeData ? [preloadTreeData[0].key as string] : []);
|
||||
const [ expandedTreeKeys, setExpandedTreeKeys ] = useState<Key[]>(preloadTreeData ? getAllNodeKeys(preloadTreeData) : []);
|
||||
|
||||
// search fields
|
||||
const [name, setName] = useState<string>("");
|
||||
const [biz_reg_num, setBizRegNum] = useState<string>("");
|
||||
const [date_start, setDateStart] = useState<string>("");
|
||||
const [date_end, setDateEnd] = useState<string>("");
|
||||
const [ name, setName ] = useState<string>("");
|
||||
const [ biz_reg_num, setBizRegNum ] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>();
|
||||
const [ date_end, setDateEnd ] = useState<string>();
|
||||
//
|
||||
|
||||
// form fields
|
||||
const [formInfo, setFormInfo] = useState({name: "", biz_reg_num: "", email: "", phone: "", ceo: "", address: ""});
|
||||
const [ formInfo, setFormInfo ] = useState({name: "", biz_reg_num: "", email: "", phone: "", ceo: "", address: ""});
|
||||
const handleChangeFormInfo = (field: string, value?: string | number) => { setFormInfo(prev => ({ ...prev, [field]: value ?? "" })); };
|
||||
|
||||
// modal values
|
||||
@@ -175,13 +175,17 @@ export default function BizGroupContent({
|
||||
|
||||
function reqModifyBizGroup(biz_group_id: Key) {
|
||||
if (formInfo.name == "") {
|
||||
alert("그룹명(사업자명)을 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명을 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
//else if (formInfo.biz_reg_num == "") {
|
||||
// alert("사업자등록번호를 입력하셔야 합니다.");
|
||||
// return;
|
||||
//}
|
||||
if (formInfo.biz_reg_num != "" && !isValidBusinessNumber(formInfo.biz_reg_num)) {
|
||||
if (!confirm("잘못된 형식의 사업자등록번호입니다. 그래도 등록하시겠습니까?"))
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {
|
||||
biz_group_id: biz_group_id,
|
||||
@@ -233,17 +237,17 @@ export default function BizGroupContent({
|
||||
}
|
||||
|
||||
const handleClickSearch = () => {
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (name == "" && biz_reg_num == "") {
|
||||
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (name != "" && name.length < 3) {
|
||||
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
|
||||
if (name != "" && name.length < 2) {
|
||||
alert("그룹(사업자)명은 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 3) {
|
||||
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 2) {
|
||||
alert("사업자등록번호는 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -339,11 +343,11 @@ export default function BizGroupContent({
|
||||
<ComponentCard title={title} titleIcon={titleIcon}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="그룹명(사업자명)" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="조회할 그룹명(사업자명)을 입력하세요." value={name} onChange={(e) => setName(e.target.value)}
|
||||
<WithLabel label="그룹(사업자)명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 그룹(사업자)명을 입력하세요." value={name} onChange={(e) => setName(e.target.value)}
|
||||
onKeyDown={(e) => {if (e.key === "Enter") handleClickSearch()}} />
|
||||
</WithLabel>
|
||||
<WithLabel label="사업자번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="사업자번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 사업자번호를 입력하세요." value={biz_reg_num} onChange={(e) => handleChangeBizRegNum(e.target.value)}
|
||||
onKeyDown={(e) => {if (e.key === "Enter") handleClickSearch()}} />
|
||||
</WithLabel>
|
||||
@@ -353,17 +357,19 @@ export default function BizGroupContent({
|
||||
</div>
|
||||
{/*
|
||||
<div className="space-y-2 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
@@ -399,9 +405,9 @@ export default function BizGroupContent({
|
||||
</div>
|
||||
</ComponentCard>
|
||||
<div className="w-[380px]">
|
||||
<div className="flex-1 space-y-6">
|
||||
<div className="flex-1 space-y-4">
|
||||
<div>
|
||||
<Label>그룹명(사업자명)</Label>
|
||||
<Label>그룹(사업자)명</Label>
|
||||
<Input type="text" value={formInfo.name} onChange={(e) => handleChangeFormInfo("name", e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ export default function AddModifyBizGroupModal({ multiState, onOk, isOpen, close
|
||||
|
||||
const handleClickAddOrModify = () => {
|
||||
if (multiState.values.name == "") {
|
||||
alert("그룹명(사업자명)을 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명을 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
//else if (multiState.values.biz_reg_num == "") {
|
||||
@@ -80,7 +80,7 @@ export default function AddModifyBizGroupModal({ multiState, onOk, isOpen, close
|
||||
className="max-w-[700px] p-6 lg:p-10"
|
||||
>
|
||||
<div>
|
||||
<Label redPoint>그룹명(사업자명)</Label>
|
||||
<Label redPoint>그룹(사업자)명</Label>
|
||||
<Input type="text" value={multiState.values.name} onChange={(e) => multiState.set("name", e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
+37
-28
@@ -10,17 +10,19 @@ import DatePicker from '@/components/form/date-picker2';
|
||||
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import api, { convertDateTime3 } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, todayYMD } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyBizGroupModal, { BizGroupModalProps } from './AddModifyBizGroupModal';
|
||||
import { useMultiState } from "@/hooks/useMultiState";
|
||||
|
||||
import CtTree, { CtTreeNode, expandAndSelectTreeNode, getAllNodeKeys, findParentKeys, findNodeByKey, deleteNodeByKey } from "@/components/CtTree";
|
||||
import Select from "@/components/form/Select2";
|
||||
import PageCountSelector from "@/components/PageCountSelector";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
//const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -29,6 +31,8 @@ export default function BizGroup() {
|
||||
const [ tableOffset, setTableOffset ] = useState<number>(0);
|
||||
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
|
||||
|
||||
const [ COUNT_PER_PAGE, setCOUNT_PER_PAGE ] = useState<number>(20);
|
||||
|
||||
const [ treeData, setTreeData ] = useState<CtTreeNode[]>([]);
|
||||
const [ selectedTreeKeys, setSelectedTreeKeys ] = useState<Key[]>([]);
|
||||
const [ expandedTreeKeys, setExpandedTreeKeys ] = useState<Key[]>([]);
|
||||
@@ -196,7 +200,7 @@ export default function BizGroup() {
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time)
|
||||
},
|
||||
{
|
||||
title: "그룹명(사업자명)",
|
||||
title: "그룹(사업자)명",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
@@ -257,36 +261,38 @@ export default function BizGroup() {
|
||||
|
||||
<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={name} onChange={(e) => setName(e.target.value)} />
|
||||
<WithLabel label="그룹(사업자)명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 그룹(사업자)명을 입력하세요." value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="사업자번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="사업자번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 사업자번호를 입력하세요." value={biz_reg_num} onChange={(e) => setBizRegNum(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="이메일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="이메일" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 이메일를 입력하세요." value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<WithLabel label="전화번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="전화번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 전화번호를 입력하세요." value={phone} onChange={(e) => setPhone(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="주소" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="주소" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 주소를 입력하세요." value={address} onChange={(e) => setAddress(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="space-y-2 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
@@ -295,25 +301,28 @@ export default function BizGroup() {
|
||||
<Button size="sm" variant="primary" onClick={handleClickSearch}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
<PageCountSelector defaultValue={COUNT_PER_PAGE.toString()} onChange={(e) => setCOUNT_PER_PAGE(parseInt(e.target.value))} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<ComponentCard title='조직 계층도'>
|
||||
<div className="min-w-[310px] w-full h-[330px] overflow-auto">
|
||||
<CtTree
|
||||
treeData={treeData}
|
||||
selectedKeys={selectedTreeKeys}
|
||||
setSelectedKeys={setSelectedTreeKeys}
|
||||
expandedKeys={expandedTreeKeys}
|
||||
setExpandedKeys={setExpandedTreeKeys}
|
||||
onClickExpanded={handleClickTreeExpanded}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-5">
|
||||
<Button size="sm" variant="primary" startIcon={<PlusIcon />} onClick={handleClickAdd}>선택사업자 하위에<br></br>신규사업자 추가</Button>
|
||||
<Button size="sm" variant="primary" startIcon={<TrashBinIcon />} onClick={handleClickDelete}>선택사업자<br></br>삭제</Button>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
<ComponentCard title='조직 계층도'>
|
||||
<div className="min-w-[310px] w-full h-[330px] overflow-auto">
|
||||
<CtTree
|
||||
treeData={treeData}
|
||||
selectedKeys={selectedTreeKeys}
|
||||
setSelectedKeys={setSelectedTreeKeys}
|
||||
expandedKeys={expandedTreeKeys}
|
||||
setExpandedKeys={setExpandedTreeKeys}
|
||||
onClickExpanded={handleClickTreeExpanded}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-5">
|
||||
<Button size="sm" variant="primary" startIcon={<PlusIcon />} onClick={handleClickAdd}>선택사업자 하위에<br></br>신규사업자 추가</Button>
|
||||
<Button size="sm" variant="primary" startIcon={<TrashBinIcon />} onClick={handleClickDelete}>선택사업자<br></br>삭제</Button>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
<div className="flex-1">
|
||||
<CtTable1 columns={columns} bodyData={tableData}
|
||||
offset={tableOffset} totalCount={tableTotalCount} countPerPage={COUNT_PER_PAGE} pageNumCount={PAGE_NUM_COUNT}
|
||||
+52
-7
@@ -5,7 +5,8 @@ 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';
|
||||
import api, { useAuthStore } from '@/lib/_AG';
|
||||
import Checkbox from "@/components/form/input/Checkbox";
|
||||
|
||||
|
||||
|
||||
@@ -19,9 +20,14 @@ export interface CustomerAccountModalProps {
|
||||
new_biz_group_id: string;
|
||||
user_id: string;
|
||||
user_pw: string;
|
||||
nick_name: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
state: string;
|
||||
perm_group: boolean;
|
||||
perm_uid1: boolean;
|
||||
perm_cancel: boolean;
|
||||
perm_account: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +39,10 @@ interface AddModifyCustomerAccountModalProps {
|
||||
}
|
||||
|
||||
export default function AddModifyCustomerAccountModal({ multiState, onOk, isOpen, closeModal }: AddModifyCustomerAccountModalProps) {
|
||||
const tokenPayload = useAuthStore((s) => s.tokenPayload); //Bruce, JWT
|
||||
|
||||
const [ user_pw_confirm, setUserPwConfirm ] = useState<string>("");
|
||||
|
||||
|
||||
const handleClickAddOrModify = () => {
|
||||
if (multiState.values.user_id == "") {
|
||||
@@ -43,6 +53,22 @@ export default function AddModifyCustomerAccountModal({ multiState, onOk, isOpen
|
||||
alert("계정 비밀번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
else if (user_pw_confirm == "") {
|
||||
alert("비밀번호 확인을 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
else if (multiState.values.user_pw !== user_pw_confirm) {
|
||||
alert("비밀번호 확인이 일치하지 않습니다.");
|
||||
return;
|
||||
}
|
||||
else if (multiState.values.name == "") {
|
||||
alert("이름 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
else if (multiState.values.phone == "") {
|
||||
alert("휴대폰 번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const { biz_group_name, biz_reg_num, ...params } = multiState.values;
|
||||
|
||||
@@ -90,24 +116,43 @@ export default function AddModifyCustomerAccountModal({ multiState, onOk, isOpen
|
||||
>
|
||||
<div>
|
||||
<Label>사업자원장</Label>
|
||||
<Input type="text" value={multiState.values.biz_group_name + "(" + multiState.values.biz_reg_num + ")"} disabled />
|
||||
<Input type="text" value={multiState.values.biz_group_name + (multiState.values.biz_reg_num ? `(${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>
|
||||
{ !multiState.values.isModify || (multiState.values.isModify && tokenPayload?.user_id === multiState.values.user_id) ? <>
|
||||
<div>
|
||||
<Label redPoint>비밀번호</Label>
|
||||
<Input type="password" value={multiState.values.user_pw} onChange={(e) => multiState.set("user_pw", e.target.value)} />
|
||||
<Input type="password" defaultValue="" 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)} />
|
||||
<Label redPoint>비밀번호 확인</Label>
|
||||
<Input type="password" value={user_pw_confirm} onChange={(e) => setUserPwConfirm(e.target.value)} />
|
||||
</div>
|
||||
</> : <></>}
|
||||
<div>
|
||||
<Label redPoint>이름</Label>
|
||||
<Input type="text" value={multiState.values.name} onChange={(e) => multiState.set("name", e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<Label redPoint>휴대폰</Label>
|
||||
<Input type="text" value={multiState.values.phone} onChange={(e) => multiState.set("phone", 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>
|
||||
<div className="flex items-center gap-5">
|
||||
<Checkbox checked={multiState.values.perm_group} onChange={(value) => multiState.set("perm_group", value)} label="그룹변경" />
|
||||
<Checkbox checked={multiState.values.perm_uid1} onChange={(value) => multiState.set("perm_uid1", value)} label="TID변경" />
|
||||
<Checkbox checked={multiState.values.perm_cancel} onChange={(value) => multiState.set("perm_cancel", value)} label="카드승인취소" />
|
||||
<Checkbox checked={multiState.values.perm_account} onChange={(value) => multiState.set("perm_account", value)} label="계정관리" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>상태</Label>
|
||||
<Select
|
||||
@@ -116,7 +161,7 @@ export default function AddModifyCustomerAccountModal({ multiState, onOk, isOpen
|
||||
{ label: "정상", value: "1" },
|
||||
{ label: "정지", value: "4" },
|
||||
]}
|
||||
defaultValue={multiState.values.state} onChange={(value) => multiState.set("state", value)}
|
||||
defaultValue={multiState.values.state} onChange={(e) => multiState.set("state", e.target.value)}
|
||||
className="dark:bg-dark-900" />
|
||||
</div>
|
||||
</CtModal>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client"
|
||||
import PageBreadcrumb from "@/components/common/PageBreadCrumb2";
|
||||
import ComponentCard from '@/components/common/ComponentCard2';
|
||||
import BizGroupPanel from "@/components/common/BizGroupPanel";
|
||||
import BizGroupPanel from "@/components/BizGroupPanel";
|
||||
import CtTable1 from '@/components/tables/CtTable1';
|
||||
import Button from '@/components/ui/button/Button2';
|
||||
import { DocsIcon, TableIcon, PlusIcon, TrashBinIcon, PencilIcon } from "@/icons";
|
||||
import { DocsIcon, TableIcon, PlusIcon, TrashBinIcon, PencilIcon, FilterIcon } from "@/icons";
|
||||
import WithLabel from '@/components/form/WithLabel';
|
||||
import Input from '@/components/form/input/InputField2';
|
||||
import DatePicker from '@/components/form/date-picker2';
|
||||
@@ -12,7 +12,7 @@ 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 api, { useAuthStore, convertDateTime2, convertDateTime3, useUiLoadingStore, postFileDownload, todayYMD, addDaysYMD } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyCustomerAccountModal, { CustomerAccountModalProps } from './AddModifyCustomerAccountModal';
|
||||
@@ -21,10 +21,14 @@ import CtTree, { CtTreeNode, findNodeByKey, findRootNodeByKey, treeToList } from
|
||||
import { reqBizGroupTree2, reqBizGroupTree3 } from "../biz-group/BizGroupContent";
|
||||
import Checkbox from "@/components/form/input/Checkbox";
|
||||
import ManageBizGroupModal from "../biz-group/ManageBizGroupModal";
|
||||
import CtDrawer from "@/components/CtDrawer";
|
||||
import CtSelectedTextField from "@/components/CtSelectedText";
|
||||
import Select from "@/components/form/Select2";
|
||||
import PageCountSelector from "@/components/PageCountSelector";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
//const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -37,11 +41,13 @@ export default function CustomerAccount() {
|
||||
const [ tableOffset, setTableOffset ] = useState<number>(0);
|
||||
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
|
||||
|
||||
const [ COUNT_PER_PAGE, setCOUNT_PER_PAGE ] = useState<number>(20);
|
||||
|
||||
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
|
||||
const [ isGroupAccess, setGroupAccess ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
const [ isTreeOpen, setTreeOpen ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
|
||||
// group search fields
|
||||
const [ name, setName ] = useState<string>("");
|
||||
@@ -54,10 +60,23 @@ export default function CustomerAccount() {
|
||||
const [ date_start, setDateStart ] = useState<string>("");
|
||||
const [ date_end, setDateEnd ] = useState<string>("");
|
||||
|
||||
const [ isMobileMode, setIsMobileMode ] = useState<boolean>(false);
|
||||
const [ isSearchFieldOpen, setIsSearchFieldOpen ] = useState<boolean>(false);
|
||||
|
||||
const [ listSelectedText, setListSelectedText ] = useState<string[]>([]);
|
||||
const changeListSelectedText = (index: number, text: string) => {
|
||||
setListSelectedText(prev => {
|
||||
const copy = [...prev];
|
||||
copy[index] = text;
|
||||
return copy;
|
||||
});
|
||||
}
|
||||
|
||||
// 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 modalData = useMultiState<CustomerAccountModalProps>({ biz_group_name: "", biz_reg_num: "", isModify: false,
|
||||
gid: "", biz_group_id: "", new_biz_group_id: "", user_id: "", user_pw: "", name: "", phone: "", email: "", state: "1",
|
||||
perm_group: true, perm_uid1: true, perm_cancel: true, perm_account: true });
|
||||
|
||||
const { isOpen: isOpenManageBizGroup, openModal: openModalManageBizGroup, closeModal: closeModalManageBizGroup } = useModal();
|
||||
|
||||
@@ -95,6 +114,13 @@ export default function CustomerAccount() {
|
||||
setTableData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
resp.data.result.list.forEach((item: any) => {
|
||||
item.perm_group = (item.permission & 0x2) === 0x2;
|
||||
item.perm_uid1 = (item.permission & 0x4) === 0x4;
|
||||
item.perm_cancel = (item.permission & 0x8) === 0x8;
|
||||
});
|
||||
|
||||
setTableTotalCount(resp.data.result.totalCount);
|
||||
setTableData(resp.data.result.list);
|
||||
}
|
||||
@@ -134,22 +160,22 @@ export default function CustomerAccount() {
|
||||
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);
|
||||
reqAccountList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickGroupSearch = () => {
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (name == "" && biz_reg_num == "") {
|
||||
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (name != "" && name.length < 3) {
|
||||
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
|
||||
if (name != "" && name.length < 2) {
|
||||
alert("그룹(사업자)명은 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 3) {
|
||||
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 2) {
|
||||
alert("사업자등록번호는 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,7 +183,7 @@ export default function CustomerAccount() {
|
||||
}
|
||||
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), tokenPayload?.permission == 0xffffffff);
|
||||
reqAccountList(false, String(resp.data.result.tree[0].key), isGroupAccess);
|
||||
});
|
||||
}
|
||||
@@ -182,7 +208,7 @@ export default function CustomerAccount() {
|
||||
const handleClickGroupAccess = (value: boolean) => {
|
||||
setGroupAccess(value);
|
||||
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (selectedTreeKeys.length <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -225,7 +251,8 @@ export default function CustomerAccount() {
|
||||
new_biz_group_id: "",
|
||||
user_id: "",
|
||||
user_pw: "",
|
||||
nick_name: "",
|
||||
name: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
state: "1"
|
||||
});
|
||||
@@ -264,6 +291,20 @@ export default function CustomerAccount() {
|
||||
reqAccountList(false, selectedTreeKeys[0] as string, isGroupAccess);
|
||||
}
|
||||
//
|
||||
const handleChangeUserId = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUserId(e.target.value);
|
||||
changeListSelectedText(0, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeEmail = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEmail(e.target.value);
|
||||
changeListSelectedText(1, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeNickName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNickName(e.target.value);
|
||||
changeListSelectedText(2, e.target.value);
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -276,14 +317,14 @@ export default function CustomerAccount() {
|
||||
if (tokenPayload == undefined)
|
||||
return;
|
||||
|
||||
if (tokenPayload?.permission == 1)
|
||||
if (tokenPayload?.permission == 0xffffffff)
|
||||
return;
|
||||
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
setGroupAccess(tokenPayload?.permission == 1);
|
||||
setTreeOpen(tokenPayload?.permission == 1);
|
||||
setGroupAccess(tokenPayload?.permission == 0xffffffff);
|
||||
setTreeOpen(tokenPayload?.permission == 0xffffffff);
|
||||
|
||||
reqAccountList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
|
||||
reqAccountList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
});
|
||||
}, [tokenPayload]);
|
||||
|
||||
@@ -293,18 +334,25 @@ export default function CustomerAccount() {
|
||||
title: "등록일",
|
||||
key: "reg_time" ,
|
||||
renderItem: (item:any) => convertDateTime3(item.reg_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "그룹",
|
||||
key: "biz_group_name",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "아이디",
|
||||
key: "user_id",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "닉네임",
|
||||
key: "nick_name",
|
||||
title: "이름",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "휴대폰번호",
|
||||
key: "phone",
|
||||
},
|
||||
{
|
||||
title: "이메일",
|
||||
@@ -359,7 +407,7 @@ export default function CustomerAccount() {
|
||||
onClickSearch={handleClickGroupSearch}
|
||||
onClickManage={handleClickGroupManage}
|
||||
isDisabled={isLoadingGlobal}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH_PX}
|
||||
>
|
||||
<CtTree
|
||||
treeData={treeData}
|
||||
@@ -374,46 +422,55 @@ export default function CustomerAccount() {
|
||||
|
||||
<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>
|
||||
<CtDrawer title="상세 조회" isMobileMode={isMobileMode} setIsMobileMode={setIsMobileMode} isSearchFieldOpen={isSearchFieldOpen} setIsSearchFieldOpen={setIsSearchFieldOpen}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<WithLabel label="아이디" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 아이디를 입력하세요." value={user_id} onChange={handleChangeUserId} />
|
||||
</WithLabel>
|
||||
<WithLabel label="이메일" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 이메일을 입력하세요." value={email} onChange={handleChangeEmail} />
|
||||
</WithLabel>
|
||||
<WithLabel label="닉네임" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 닉네임을 입력하세요." value={nick_name} onChange={handleChangeNickName} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
</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>
|
||||
</CtDrawer>
|
||||
|
||||
<div className="space-y-2 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
<Button size="sm" variant="outline" startIcon={<DocsIcon />} onClick={handleClickDownload}>엑셀파일 다운로드</Button>
|
||||
{ isMobileMode && <Button size="sm" variant="outline" startIcon={<FilterIcon />} onClick={() => setIsSearchFieldOpen(true)}>필터</Button> }
|
||||
<Button size="sm" variant="primary" onClick={handleClickSearch} disabled={isLoadingGlobal}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
{ isTreeOpen && <Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} label="그룹단위로 조회" /> }
|
||||
<PageCountSelector defaultValue={COUNT_PER_PAGE.toString()} onChange={(e) => setCOUNT_PER_PAGE(parseInt(e.target.value))} />
|
||||
</div>
|
||||
|
||||
{ isMobileMode &&
|
||||
<CtSelectedTextField label={"필터:"} listText={listSelectedText} setListText={setListSelectedText}></CtSelectedTextField>
|
||||
}
|
||||
|
||||
<CtTable1 columns={columns} bodyData={tableData}
|
||||
offset={tableOffset} totalCount={tableTotalCount} countPerPage={COUNT_PER_PAGE} pageNumCount={PAGE_NUM_COUNT}
|
||||
onClickPageChange={handlePageChange} />
|
||||
|
||||
@@ -165,7 +165,7 @@ export default function AddModifyDeviceModal({ multiState, onOk, isOpen, closeMo
|
||||
placeholder="선택하세요"
|
||||
defaultValue={multiState.values.biz_group_id}
|
||||
options={multiState.values.optBizGroup}
|
||||
onChange={(value) => {multiState.set("new_biz_group_id", value)}}
|
||||
onChange={(e) => {multiState.set("new_biz_group_id", e.target.value)}}
|
||||
className="dark:bg-dark-900" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -178,7 +178,7 @@ export default function AddModifyDeviceModal({ multiState, onOk, isOpen, closeMo
|
||||
placeholder="선택하세요"
|
||||
defaultValue={multiState.values.optTid.length > 0 ? multiState.values.optTid[0].value : undefined}
|
||||
options={multiState.values.optTid}
|
||||
onChange={(value) => {multiState.set("terminal_id", value)}}
|
||||
onChange={(e) => {multiState.set("terminal_id", e.target.value)}}
|
||||
className="dark:bg-dark-900" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client"
|
||||
import PageBreadcrumb from "@/components/common/PageBreadCrumb2";
|
||||
import ComponentCard from '@/components/common/ComponentCard2';
|
||||
import BizGroupPanel from "@/components/common/BizGroupPanel";
|
||||
import BizGroupPanel from "@/components/BizGroupPanel";
|
||||
import CtTable1 from '@/components/tables/CtTable1';
|
||||
import Button from '@/components/ui/button/Button2';
|
||||
import { DocsIcon, PlusIcon, TrashBinIcon, TableIcon, BoxCubeIcon, PencilIcon } from "@/icons";
|
||||
import { DocsIcon, PlusIcon, TrashBinIcon, TableIcon, BoxCubeIcon, PencilIcon, FilterIcon } from "@/icons";
|
||||
import WithLabel from '@/components/form/WithLabel';
|
||||
import Input from '@/components/form/input/InputField2';
|
||||
import Select from '@/components/form/Select2';
|
||||
@@ -15,7 +15,7 @@ import Badge from "@/components/ui/badge/Badge";
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import api, { useAuthStore, postFileDownload, convertDateTime3, useUiLoadingStore } from '@/lib/_AG';
|
||||
import api, { useAuthStore, postFileDownload, convertDateTime3, useUiLoadingStore, todayYMD, addDaysYMD } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyDeviceModal, { DeviceModalProps } from './AddModifyDeviceModal';
|
||||
@@ -26,10 +26,14 @@ import { useMultiState } from "@/hooks/useMultiState";
|
||||
import CtTree, { CtTreeNode, expandAndSelectTreeNode, getAllNodeKeys, findParentKeys, findNodeByKey, treeToList, addNodeByKey, deleteNodeByKey, findRootNodeByKey } from "@/components/CtTree";
|
||||
import { reqBizGroupTree2, reqBizGroupTree3 } from "@/app/(admin)/biz-group/BizGroupContent";
|
||||
import ManageBizGroupModal from "../biz-group/ManageBizGroupModal";
|
||||
import CtDrawer from "@/components/CtDrawer";
|
||||
import CtSelectedTextField from "@/components/CtSelectedText";
|
||||
import PageCountSelector from "@/components/PageCountSelector";
|
||||
import InputSelectField from "@/components/form/input/InputSelectField";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
//const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -41,23 +45,41 @@ export default function Device() {
|
||||
const [ tableData, setTableData ] = useState<any[]>([]);
|
||||
const [ tableOffset, setTableOffset ] = useState<number>(0);
|
||||
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
|
||||
|
||||
const [ COUNT_PER_PAGE, setCOUNT_PER_PAGE ] = useState<number>(20);
|
||||
|
||||
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
|
||||
const [ isGroupAccess, setGroupAccess ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
const [ isTreeOpen, setTreeOpen ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
|
||||
// group search fields
|
||||
const [ name, setName ] = useState<string>("");
|
||||
const [ biz_reg_num, setBizRegNum ] = useState<string>("");
|
||||
|
||||
// device search fields
|
||||
const [ device_name, setDeivceName ] = useState<string>("");
|
||||
const [ uid1, setUid1 ] = useState<string>("");
|
||||
const [ list_uid1, setListUid1 ] = useState<{label: string; value: string}[]>([]);
|
||||
const [ conn_state, setConnState ] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>("");
|
||||
const [ date_end, setDateEnd ] = useState<string>("");
|
||||
const [ listIdleTerminal, setListIdelTerminal ] = useState([]);
|
||||
const [ listGoods, setListGoods ] = useState([]);
|
||||
const isLoadingGlobal = useUiLoadingStore((s) => s.isLoading);
|
||||
|
||||
const [ isMobileMode, setIsMobileMode ] = useState<boolean>(false);
|
||||
const [ isSearchFieldOpen, setIsSearchFieldOpen ] = useState<boolean>(false);
|
||||
|
||||
const [ listSelectedText, setListSelectedText ] = useState<string[]>([]);
|
||||
const changeListSelectedText = (index: number, text: string) => {
|
||||
setListSelectedText(prev => {
|
||||
const copy = [...prev];
|
||||
copy[index] = text;
|
||||
return copy;
|
||||
});
|
||||
}
|
||||
|
||||
// modal
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
@@ -75,6 +97,27 @@ export default function Device() {
|
||||
|
||||
// function part
|
||||
//
|
||||
function reqUid1List() {
|
||||
api.post('/api/list-uid1.do').then((resp) => {
|
||||
console.log(resp);
|
||||
|
||||
if (resp.data.errCode == 0) {
|
||||
if (resp.data.result.list.length == 0) {
|
||||
setListUid1([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setListUid1(
|
||||
resp.data.result.list.map((item: { uid1: string; name: string }) => ({
|
||||
label: item.uid1 + (item.name !== "" ? " (" + item.name + ")" : ""),
|
||||
value: item.uid1,
|
||||
}))
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err))
|
||||
}
|
||||
|
||||
function reqDeviceList(is_excel: boolean, biz_group_id: string, groupAccess: boolean) {
|
||||
if (!biz_group_id)
|
||||
return;
|
||||
@@ -85,6 +128,9 @@ export default function Device() {
|
||||
offset: tableOffset,
|
||||
limit: COUNT_PER_PAGE,
|
||||
biz_group_id: biz_group_id,
|
||||
device_name: device_name,
|
||||
uid1: uid1,
|
||||
conn_state: conn_state,
|
||||
date_start: date_start,
|
||||
date_end: date_end,
|
||||
}
|
||||
@@ -224,22 +270,22 @@ export default function Device() {
|
||||
const handleManageBizGroupModalClose = () => {
|
||||
closeModalManageBizGroup();
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
reqDeviceList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
|
||||
reqDeviceList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickGroupSearch = () => {
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (name == "" && biz_reg_num == "") {
|
||||
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (name != "" && name.length < 3) {
|
||||
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
|
||||
if (name != "" && name.length < 2) {
|
||||
alert("그룹(사업자)명은 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 3) {
|
||||
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 2) {
|
||||
alert("사업자등록번호는 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,7 +294,9 @@ export default function Device() {
|
||||
}
|
||||
else {
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
//reqDeviceList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
|
||||
reqUid1List();
|
||||
|
||||
//reqDeviceList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
reqDeviceList(false, String(resp.data.result.tree[0].key), isGroupAccess);
|
||||
});
|
||||
}
|
||||
@@ -274,7 +322,7 @@ export default function Device() {
|
||||
const handleClickGroupAccess = (value: boolean) => {
|
||||
setGroupAccess(value);
|
||||
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (selectedTreeKeys.length <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -461,18 +509,36 @@ export default function Device() {
|
||||
};
|
||||
//
|
||||
|
||||
const handleChangeDeivceName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDeivceName(e.target.value);
|
||||
changeListSelectedText(0, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeUid1 = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUid1(e.target.value);
|
||||
changeListSelectedText(1, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeConnState = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setConnState(e.target.value);
|
||||
changeListSelectedText(2, e.target.options[e.target.selectedIndex].text);
|
||||
}
|
||||
//
|
||||
|
||||
useEffect(() => {
|
||||
if (tokenPayload == undefined)
|
||||
return;
|
||||
|
||||
if (tokenPayload?.permission == 1)
|
||||
if (tokenPayload?.permission == 0xffffffff)
|
||||
return;
|
||||
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
setGroupAccess(tokenPayload?.permission == 1);
|
||||
setTreeOpen(tokenPayload?.permission == 1);
|
||||
reqUid1List();
|
||||
|
||||
reqDeviceList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
|
||||
setGroupAccess(tokenPayload?.permission == 0xffffffff);
|
||||
setTreeOpen(tokenPayload?.permission == 0xffffffff);
|
||||
|
||||
reqDeviceList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
});
|
||||
}, [tokenPayload]);
|
||||
|
||||
@@ -487,7 +553,8 @@ export default function Device() {
|
||||
{
|
||||
title: "등록일",
|
||||
key: "reg_time",
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time)
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "그룹",
|
||||
@@ -500,9 +567,8 @@ export default function Device() {
|
||||
{
|
||||
title: "단말기 TID",
|
||||
key: "uid1",
|
||||
renderItem: (item: any, row: number) => {
|
||||
return item.uid1 ? (item.uid1 + " (" + (item.type == 1 ? "KICC" : "NICE") +")") : ""
|
||||
}
|
||||
renderItem: (item: any, row: number) => item.uid1 ? (item.uid1 + " (" + (item.type == 1 ? "KICC" : "NICE") +")") : "",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "상태",
|
||||
@@ -528,6 +594,7 @@ export default function Device() {
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "가동시간",
|
||||
@@ -594,7 +661,7 @@ export default function Device() {
|
||||
onClickSearch={handleClickGroupSearch}
|
||||
onClickManage={handleClickGroupManage}
|
||||
isDisabled={isLoadingGlobal}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH_PX}
|
||||
>
|
||||
<CtTree
|
||||
treeData={treeData}
|
||||
@@ -608,39 +675,65 @@ export default function Device() {
|
||||
</BizGroupPanel>
|
||||
|
||||
<ComponentCard title="무인기기 목록" titleIcon={<TableIcon />} >
|
||||
<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">
|
||||
{ isTreeOpen && <>
|
||||
<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="primary" startIcon={<PlusIcon />} onClick={handleClickTemplateManage}>템플릿 관리</Button>*/}
|
||||
<Button size="sm" variant="outline" startIcon={<DocsIcon />} onClick={handleClickDownload}>엑셀파일 다운로드</Button>
|
||||
<Button size="sm" variant="primary" onClick={handleClickSearch} disabled={isLoadingGlobal}>조회</Button>
|
||||
<CtDrawer title="상세 조회" isMobileMode={isMobileMode} setIsMobileMode={setIsMobileMode} isSearchFieldOpen={isSearchFieldOpen} setIsSearchFieldOpen={setIsSearchFieldOpen}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="무인기기명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="무인기기명을 입력하세요." value={device_name} onChange={handleChangeDeivceName} />
|
||||
</WithLabel>
|
||||
<WithLabel label="단말기 TID" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<InputSelectField type="text" placeholder="TID를 입력하세요." value={uid1} options={list_uid1} onChange={handleChangeUid1} />
|
||||
</WithLabel>
|
||||
<WithLabel label="상태" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Select
|
||||
//placeholder="선택하세요"
|
||||
options={[
|
||||
{ label: "전체", value: "" },
|
||||
{ label: "가동중", value: "1" },
|
||||
{ label: "장애", value: "4" },
|
||||
]}
|
||||
defaultValue={conn_state} onChange={handleChangeConnState}
|
||||
className="dark:bg-dark-900" />
|
||||
</WithLabel>
|
||||
</div>
|
||||
</div>
|
||||
</CtDrawer>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
{/*<Button size="sm" variant="primary" startIcon={<PlusIcon />} onClick={handleClickTemplateManage}>템플릿 관리</Button>*/}
|
||||
<Button size="sm" variant="outline" startIcon={<DocsIcon />} onClick={handleClickDownload}>엑셀파일 다운로드</Button>
|
||||
{ isMobileMode && <Button size="sm" variant="outline" startIcon={<FilterIcon />} onClick={() => setIsSearchFieldOpen(true)}>필터</Button> }
|
||||
<Button size="sm" variant="primary" onClick={handleClickSearch} disabled={isLoadingGlobal}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
{ isTreeOpen && <Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} label="그룹단위로 조회" /> }
|
||||
<PageCountSelector defaultValue={COUNT_PER_PAGE.toString()} onChange={(e) => setCOUNT_PER_PAGE(parseInt(e.target.value))} />
|
||||
</div>
|
||||
|
||||
{ isMobileMode &&
|
||||
<CtSelectedTextField label={"필터:"} listText={listSelectedText} setListText={setListSelectedText}></CtSelectedTextField>
|
||||
}
|
||||
|
||||
<CtTable1 columns={columns} bodyData={tableData}
|
||||
offset={tableOffset} totalCount={tableTotalCount} countPerPage={COUNT_PER_PAGE} pageNumCount={PAGE_NUM_COUNT}
|
||||
onClickPageChange={handlePageChange} />
|
||||
|
||||
@@ -11,7 +11,7 @@ import { PlusIcon, TrashBinIcon } from "@/icons";
|
||||
|
||||
|
||||
interface DeviceGooodsItem {
|
||||
slot: string;
|
||||
column_no: string;
|
||||
goods_id: number;
|
||||
price?: number;
|
||||
inventory?: number;
|
||||
@@ -40,23 +40,23 @@ export default function ManageGoodsModal({ multiState, onOk, isOpen, closeModal
|
||||
|
||||
function computeGoods(modal: GoodsModalProps): GoodsModalProps {
|
||||
// 1 & 2. 우선 slot이 빈 문자열("")이 아닌 아이템들만 추출 (1차 필터링)
|
||||
const filteredPrev = modal.prev_device_goods.filter(item => item.slot !== "");
|
||||
const filteredNew = modal.new_device_goods.filter(item => item.slot !== "");
|
||||
const filteredPrev = modal.prev_device_goods.filter(item => item.column_no !== "");
|
||||
const filteredNew = modal.new_device_goods.filter(item => item.column_no !== "");
|
||||
|
||||
// 3. slot이 같으면서 goods_id까지 같은 아이템은 양쪽에서 제거
|
||||
// 즉, "상대방 배열에 나랑 똑같은(slot & goods_id) 애가 없는" 것들만 남깁니다.
|
||||
// 즉, "상대방 배열에 나랑 똑같은(column_no & goods_id) 애가 없는" 것들만 남깁니다.
|
||||
|
||||
// device_goods 정제
|
||||
const final_device_goods = filteredPrev.filter(prevItem =>
|
||||
!filteredNew.find(newItem =>
|
||||
newItem.slot === prevItem.slot && newItem.goods_id === prevItem.goods_id
|
||||
newItem.column_no === prevItem.column_no && newItem.goods_id === prevItem.goods_id
|
||||
)
|
||||
);
|
||||
|
||||
// new_device_goods 정제
|
||||
const final_new_device_goods = filteredNew.filter(newItem =>
|
||||
!filteredPrev.find(prevItem =>
|
||||
prevItem.slot === newItem.slot && prevItem.goods_id === newItem.goods_id
|
||||
prevItem.column_no === newItem.column_no && prevItem.goods_id === newItem.goods_id
|
||||
)
|
||||
);
|
||||
|
||||
@@ -118,7 +118,7 @@ export default function ManageGoodsModal({ multiState, onOk, isOpen, closeModal
|
||||
return;
|
||||
}
|
||||
|
||||
const newItem: DeviceGooodsItem = { slot: "", goods_id: parseInt(multiState.values.optGoodsList[0].value) }
|
||||
const newItem: DeviceGooodsItem = { column_no: "", goods_id: parseInt(multiState.values.optGoodsList[0].value) }
|
||||
const newArray: DeviceGooodsItem[] = [...multiState.values.new_device_goods, newItem];
|
||||
|
||||
multiState.set("new_device_goods", newArray);
|
||||
@@ -133,7 +133,7 @@ export default function ManageGoodsModal({ multiState, onOk, isOpen, closeModal
|
||||
|
||||
const handleChangeSlot = (index: number, newValue: string) => {
|
||||
const newArray: DeviceGooodsItem[] = [...multiState.values.new_device_goods];
|
||||
newArray[index].slot = newValue;
|
||||
newArray[index].column_no = newValue;
|
||||
|
||||
multiState.set("new_device_goods", newArray);
|
||||
};
|
||||
@@ -165,7 +165,7 @@ export default function ManageGoodsModal({ multiState, onOk, isOpen, closeModal
|
||||
>
|
||||
<div>
|
||||
<Label>사업자원장</Label>
|
||||
<Input type="text" value={multiState.values.biz_group_name + "(" + multiState.values.biz_reg_num + ")"} disabled />
|
||||
<Input type="text" value={multiState.values.biz_group_name + (multiState.values.biz_reg_num ? `(${multiState.values.biz_reg_num})` : "")} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<Label>무인기기명 또는 관리이름</Label>
|
||||
@@ -176,14 +176,14 @@ export default function ManageGoodsModal({ multiState, onOk, isOpen, closeModal
|
||||
<Label>상품정보 {index + 1}</Label>
|
||||
<div className="flex gap-2">
|
||||
<div className={"w-34"}>
|
||||
<Input type="text" placeholder="컬럼번호2" value={multiState.values.new_device_goods[index].slot} onChange={(e) => handleChangeSlot(index, e.target.value)} />
|
||||
<Input type="text" placeholder="컬럼번호2" value={multiState.values.new_device_goods[index].column_no} onChange={(e) => handleChangeSlot(index, e.target.value)} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Select
|
||||
placeholder="상품을 선택하세요"
|
||||
options={multiState.values.optGoodsList}
|
||||
defaultValue={String(multiState.values.new_device_goods[index].goods_id)}
|
||||
onChange={(value) => handleChangeGoods(index, value)}
|
||||
onChange={(e) => handleChangeGoods(index, e.target.value)}
|
||||
className="dark:bg-dark-900" />
|
||||
</div>
|
||||
<div className={"w-34"}>
|
||||
|
||||
@@ -10,7 +10,7 @@ import Button from "@/components/ui/button/Button2";
|
||||
import { PlusIcon, TrashBinIcon } from "@/icons";
|
||||
|
||||
interface TemplateElement {
|
||||
slot: string;
|
||||
column_no: string;
|
||||
goods_id: number;
|
||||
inventory?: number;
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
|
||||
const template = templateMap.get(templateId)!;
|
||||
template.elements.push({
|
||||
slot: item.slot,
|
||||
column_no: item.column_no,
|
||||
goods_id: item.goods_id,
|
||||
inventory: item.inventory
|
||||
});
|
||||
@@ -129,7 +129,7 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
}
|
||||
|
||||
// slot이 빈 문자열인 항목 필터링
|
||||
const validElements = currentElements.filter(item => item.slot !== "");
|
||||
const validElements = currentElements.filter(item => item.column_no !== "");
|
||||
|
||||
if (validElements.length === 0) {
|
||||
alert("컬럼번호를 입력한 상품이 없습니다.");
|
||||
@@ -139,7 +139,7 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
const params = {
|
||||
biz_group_id: multiState.values.biz_group_id,
|
||||
goods_template_elements: validElements.map(item => ({
|
||||
slot: item.slot,
|
||||
column_no: item.column_no,
|
||||
goods_id: item.goods_id,
|
||||
inventory: item.inventory || null
|
||||
}))
|
||||
@@ -174,7 +174,7 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
}
|
||||
|
||||
// slot이 빈 문자열인 항목 필터링
|
||||
const validElements = currentElements.filter(item => item.slot !== "");
|
||||
const validElements = currentElements.filter(item => item.column_no !== "");
|
||||
|
||||
if (validElements.length === 0) {
|
||||
alert("컬럼번호를 입력한 상품이 없습니다.");
|
||||
@@ -185,7 +185,7 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
goods_template_id: selectedTemplateId,
|
||||
goods_template_name: templateName || undefined,
|
||||
goods_template_elements: validElements.map(item => ({
|
||||
slot: item.slot,
|
||||
column_no: item.column_no,
|
||||
goods_id: item.goods_id,
|
||||
inventory: item.inventory || null
|
||||
}))
|
||||
@@ -241,7 +241,7 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
}
|
||||
|
||||
const newItem: TemplateElement = {
|
||||
slot: "",
|
||||
column_no: "",
|
||||
goods_id: parseInt(multiState.values.optGoodsList[0].value)
|
||||
};
|
||||
setCurrentElements([...currentElements, newItem]);
|
||||
@@ -254,7 +254,7 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
|
||||
const handleChangeSlot = (index: number, newValue: string) => {
|
||||
const newArray = [...currentElements];
|
||||
newArray[index].slot = newValue;
|
||||
newArray[index].column_no = newValue;
|
||||
setCurrentElements(newArray);
|
||||
};
|
||||
|
||||
@@ -297,12 +297,12 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
<>
|
||||
<div className="space-y-2 mb-4">
|
||||
{templates.length === 0 ? (
|
||||
<div className="text-center py-4 text-gray-500">등록된 템플릿이 없습니다.</div>
|
||||
<div className="text-center py-4 text-gray-500 dark:text-gray-400">등록된 템플릿이 없습니다.</div>
|
||||
) : (
|
||||
templates.map((template) => (
|
||||
<div
|
||||
key={template.goods_template_id}
|
||||
className="flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer"
|
||||
className="flex items-center justify-between p-3 border border-gray-200 dark:border-gray-800 rounded-lg hover:bg-gray-50 dark:hover:bg-white/5 cursor-pointer"
|
||||
onClick={() => handleClickEdit(template)}
|
||||
>
|
||||
<span className="flex-1">{template.template_name}</span>
|
||||
@@ -349,7 +349,7 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="컬럼번호"
|
||||
value={item.slot}
|
||||
value={item.column_no}
|
||||
onChange={(e) => handleChangeSlot(index, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@@ -358,7 +358,7 @@ export default function ManageTemplateModal({ multiState, onOk, isOpen, closeMod
|
||||
placeholder="상품을 선택하세요"
|
||||
options={multiState.values.optGoodsList}
|
||||
defaultValue={String(item.goods_id)}
|
||||
onChange={(value) => handleChangeGoods(index, value)}
|
||||
onChange={(e) => handleChangeGoods(index, e.target.value)}
|
||||
className="dark:bg-dark-900"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ import DeviceGoodsList from '@/components/DeviceGoodsItem';
|
||||
|
||||
|
||||
interface DeviceGooodsItem {
|
||||
slot: string;
|
||||
column_no: string;
|
||||
goods_id: number;
|
||||
price?: number;
|
||||
inventory?: number;
|
||||
@@ -41,7 +41,7 @@ interface GoodsTemplateItem {
|
||||
goods_template_elements: Array<{
|
||||
goods_template_element_id: number;
|
||||
goods_id: number;
|
||||
slot: string;
|
||||
column_no: string;
|
||||
inventory: number;
|
||||
inventory_max?: number;
|
||||
inventory_alarm?: number;
|
||||
@@ -50,18 +50,18 @@ interface GoodsTemplateItem {
|
||||
}
|
||||
|
||||
function makeParams(device_id: string, prev_device_goods: DeviceGooodsItem[], new_device_goods: DeviceGooodsItem[], isSaveTemplate: boolean, templateName: string, biz_group_id: string | undefined, inventorySetValue: { inventory: number; inventory_max: number; inventory_alarm: number } ) {
|
||||
const filteredPrev = prev_device_goods.filter(item => item.slot !== "" && item.goods_id !== 0);
|
||||
const filteredNew = new_device_goods.filter(item => item.slot !== "" && item.goods_id !== 0);
|
||||
const filteredPrev = prev_device_goods.filter(item => item.column_no !== "" && item.goods_id !== 0);
|
||||
const filteredNew = new_device_goods.filter(item => item.column_no !== "" && item.goods_id !== 0);
|
||||
|
||||
const final_device_goods = filteredPrev.filter(prevItem =>
|
||||
!filteredNew.find(newItem =>
|
||||
newItem.slot === prevItem.slot && newItem.goods_id === prevItem.goods_id && newItem.inventory === prevItem.inventory && newItem.inventory_max === prevItem.inventory_max && newItem.inventory_alarm === prevItem.inventory_alarm
|
||||
newItem.column_no === prevItem.column_no && newItem.goods_id === prevItem.goods_id && newItem.inventory === prevItem.inventory && newItem.inventory_max === prevItem.inventory_max && newItem.inventory_alarm === prevItem.inventory_alarm
|
||||
)
|
||||
);
|
||||
|
||||
const final_new_device_goods = filteredNew.filter(newItem =>
|
||||
!filteredPrev.find(prevItem =>
|
||||
prevItem.slot === newItem.slot && prevItem.goods_id === newItem.goods_id && newItem.inventory === prevItem.inventory && newItem.inventory_max === prevItem.inventory_max && newItem.inventory_alarm === prevItem.inventory_alarm
|
||||
prevItem.column_no === newItem.column_no && prevItem.goods_id === newItem.goods_id && newItem.inventory === prevItem.inventory && newItem.inventory_max === prevItem.inventory_max && newItem.inventory_alarm === prevItem.inventory_alarm
|
||||
)
|
||||
);
|
||||
|
||||
@@ -108,7 +108,7 @@ export default function ManageGoods() {
|
||||
const [ optGoodsList, setOptGoodsList ] = useState<any>([]);
|
||||
|
||||
const [ prev_device_goods, setPrev_device_goods ] = useState<any[]>([]);
|
||||
const [ new_device_goods, setNew_device_goods ] = useState<any[]>(Array.from({ length: 30 }, (_, i) => ({ idx: i + 1, slot: String(i + 1).padStart(2, "0"), goods_id: 0, inventory: 0, inventory_max: 0, inventory_alarm: 0 })));
|
||||
const [ new_device_goods, setNew_device_goods ] = useState<any[]>(Array.from({ length: 30 }, (_, i) => ({ idx: i + 1, column_no: String(i + 1).padStart(2, "0"), goods_id: 0, inventory: 0, inventory_max: 0, inventory_alarm: 0 })));
|
||||
|
||||
const [ isSaveTemplate, setIsSaveTemplate ] = useState<boolean>(false);
|
||||
const [ isModalOpen, setIsModalOpen ] = useState<boolean>(false);
|
||||
@@ -183,7 +183,7 @@ export default function ManageGoods() {
|
||||
|
||||
const handleChangeSlot = (index: number, newValue: string) => {
|
||||
const newArray: DeviceGooodsItem[] = [...new_device_goods];
|
||||
newArray[index].slot = newValue;
|
||||
newArray[index].column_no = newValue;
|
||||
|
||||
setNew_device_goods(newArray);
|
||||
};
|
||||
@@ -221,8 +221,8 @@ export default function ManageGoods() {
|
||||
};
|
||||
|
||||
const handleClickAdd = () => {
|
||||
//const newItem: DeviceGooodsItem = { slot: "", goods_id: parseInt(optGoodsList[0].value) }
|
||||
const newItem: DeviceGooodsItem = { slot: "", goods_id: 0 }
|
||||
//const newItem: DeviceGooodsItem = { column_no: "", goods_id: parseInt(optGoodsList[0].value) }
|
||||
const newItem: DeviceGooodsItem = { column_no: "", goods_id: 0 }
|
||||
const newArray: DeviceGooodsItem[] = [...new_device_goods, newItem];
|
||||
|
||||
setNew_device_goods(newArray);
|
||||
@@ -274,7 +274,7 @@ export default function ManageGoods() {
|
||||
// 기존 값을 모두 비우고 초기 상태로 리셋
|
||||
const resetGoods = Array.from({ length: 30 }, (_, i) => ({
|
||||
idx: i + 1,
|
||||
slot: String(i + 1).padStart(2, "0"),
|
||||
column_no: String(i + 1).padStart(2, "0"),
|
||||
goods_id: 0,
|
||||
inventory: 0
|
||||
}));
|
||||
@@ -284,7 +284,7 @@ export default function ManageGoods() {
|
||||
|
||||
// 템플릿의 goods_template_elements를 DeviceGooodsItem 형태로 변환
|
||||
const mappedGoods: DeviceGooodsItem[] = goodsTemplate.goods_template_elements.map((element) => ({
|
||||
slot: element.slot,
|
||||
column_no: element.column_no,
|
||||
goods_id: Number(element.goods_id),
|
||||
inventory: element.inventory,
|
||||
inventory_max: element.inventory_max,
|
||||
@@ -317,7 +317,7 @@ export default function ManageGoods() {
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-5">
|
||||
<div>
|
||||
<Label>사업자원장</Label>
|
||||
<Input type="text" value={biz_group_name + "(" + biz_reg_num + ")"} disabled />
|
||||
<Input type="text" value={biz_group_name + (biz_reg_num ? "(" + biz_reg_num + ")" : "")} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<Label>무인기기명 또는 관리이름</Label>
|
||||
|
||||
@@ -14,9 +14,10 @@ import Select from '@/components/form/Select2';
|
||||
import React from "react";
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import { addDaysYMD, todayYMD } from "@/lib/_AG";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
@@ -255,25 +256,27 @@ export default function FAQ() {
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<WithLabel label="제목" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="제목" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="제목을 입력하세요." value={title} onChange={(e) => setTitle(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="카테고리" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="카테고리" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="카테고리" value={category} onChange={(e) => setCategory(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
@@ -317,7 +320,7 @@ export default function FAQ() {
|
||||
placeholder="카테고리를 선택하세요."
|
||||
options={FAQ_CATEGORY_OPTIONS}
|
||||
defaultValue={newCategory || ""}
|
||||
onChange={(value) => setNewCategory(value)}
|
||||
onChange={(e) => setNewCategory(e.target.value)}
|
||||
className="dark:bg-dark-900"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
import PageBreadcrumb from "@/components/common/PageBreadCrumb2";
|
||||
import ComponentCard from '@/components/common/ComponentCard2';
|
||||
import BizGroupPanel from "@/components/common/BizGroupPanel";
|
||||
import BizGroupPanel from "@/components/BizGroupPanel";
|
||||
import CtTable1 from '@/components/tables/CtTable1';
|
||||
import Button from '@/components/ui/button/Button2';
|
||||
import { DocsIcon, PlusIcon, TrashBinIcon, TableIcon, PencilIcon } from "@/icons";
|
||||
@@ -15,17 +15,18 @@ import Badge from "@/components/ui/badge/Badge";
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import api, { convertDateTime3, getUrlParam, postFileDownload, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, getUrlParam, postFileDownload, todayYMD, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import { useMultiState } from "@/hooks/useMultiState";
|
||||
|
||||
import CtTree, { CtTreeNode, expandAndSelectTreeNode, getAllNodeKeys, findParentKeys, findNodeByKey, addNodeByKey, deleteNodeByKey, findRootNodeByKey } from "@/components/CtTree";
|
||||
import { reqBizGroupTree2, reqBizGroupTree3 } from "@/app/(admin)/biz-group/BizGroupContent";
|
||||
import PageCountSelector from "@/components/PageCountSelector";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
//const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -37,12 +38,14 @@ export default function GoodsTemplate() {
|
||||
const [ tableData, setTableData ] = useState<any[]>([]);
|
||||
const [ tableOffset, setTableOffset ] = useState<number>(0);
|
||||
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
|
||||
|
||||
const [ COUNT_PER_PAGE, setCOUNT_PER_PAGE ] = useState<number>(20);
|
||||
|
||||
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
|
||||
const [ isGroupAccess, setGroupAccess ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
const [ isTreeOpen, setTreeOpen ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
|
||||
// group search fields
|
||||
const [ name, setName] = useState<string>("");
|
||||
@@ -50,8 +53,8 @@ export default function GoodsTemplate() {
|
||||
|
||||
// template search fields
|
||||
const [ templateName, setTemplateName] = useState<string>("");
|
||||
const [ date_start, setDateStart] = useState<string>("");
|
||||
const [ date_end, setDateEnd] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>("");
|
||||
const [ date_end, setDateEnd ] = useState<string>("");
|
||||
|
||||
|
||||
// function part
|
||||
@@ -122,17 +125,17 @@ export default function GoodsTemplate() {
|
||||
//
|
||||
// tree part //////////
|
||||
const handleClickGroupSearch = () => {
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (name == "" && biz_reg_num == "") {
|
||||
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (name != "" && name.length < 3) {
|
||||
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
|
||||
if (name != "" && name.length < 2) {
|
||||
alert("그룹(사업자)명은 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 3) {
|
||||
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 2) {
|
||||
alert("사업자등록번호는 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -164,7 +167,7 @@ export default function GoodsTemplate() {
|
||||
const handleClickGroupAccess = (value: boolean) => {
|
||||
setGroupAccess(value);
|
||||
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (selectedTreeKeys.length <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -225,12 +228,12 @@ export default function GoodsTemplate() {
|
||||
if (tokenPayload == undefined)
|
||||
return;
|
||||
|
||||
if (tokenPayload?.permission == 1)
|
||||
if (tokenPayload?.permission == 0xffffffff)
|
||||
return;
|
||||
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
setGroupAccess(tokenPayload?.permission == 1);
|
||||
setTreeOpen(tokenPayload?.permission == 1);
|
||||
setGroupAccess(tokenPayload?.permission == 0xffffffff);
|
||||
setTreeOpen(tokenPayload?.permission == 0xffffffff);
|
||||
|
||||
reqGoodsTemplateList(false, String(resp.data.result.tree[0].key), isGroupAccess);
|
||||
});
|
||||
@@ -247,11 +250,13 @@ export default function GoodsTemplate() {
|
||||
{
|
||||
title: "등록일",
|
||||
key: "reg_time",
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.template_reg_time)
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.template_reg_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "템플릿명",
|
||||
key: "template_name",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "설정",
|
||||
@@ -292,7 +297,7 @@ export default function GoodsTemplate() {
|
||||
onChangeBizRegNum={setBizRegNum}
|
||||
onClickSearch={handleClickGroupSearch}
|
||||
isDisabled={isLoadingGlobal}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH_PX}
|
||||
>
|
||||
<CtTree
|
||||
treeData={treeData}
|
||||
@@ -306,42 +311,38 @@ export default function GoodsTemplate() {
|
||||
</BizGroupPanel>
|
||||
|
||||
<ComponentCard title="상품 사전설정 목록" titleIcon={<TableIcon />}>
|
||||
<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">
|
||||
{ isTreeOpen && <>
|
||||
<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 className="space-y-2 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
<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 className="flex items-center justify-end gap-5">
|
||||
{ isTreeOpen && <Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} label="그룹단위로 조회" /> }
|
||||
<PageCountSelector defaultValue={COUNT_PER_PAGE.toString()} onChange={(e) => setCOUNT_PER_PAGE(parseInt(e.target.value))} />
|
||||
</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>
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useRouter, useParams, useSearchParams } from "next/navigation";
|
||||
import api, { convertDateTime2, useAuthStore } from '@/lib/_AG';
|
||||
|
||||
interface DeviceGooodsItem {
|
||||
slot: string;
|
||||
column_no: string;
|
||||
goods_id: number;
|
||||
inventory?: number;
|
||||
inventory_max?: number;
|
||||
@@ -36,7 +36,7 @@ export default function GoodsTemplateEdit() {
|
||||
const router = useRouter();
|
||||
const tokenPayload = useAuthStore((s) => s.tokenPayload);
|
||||
|
||||
const [deviceGoods, setDeviceGoods] = useState<DeviceGooodsItem[]>(Array.from({ length: 30 }, (_, i) => ({ idx: i + 1, slot: String(i + 1).padStart(2, "0"), goods_id: 0, inventory: 0, inventory_max: 0, inventory_alarm: 0 })));
|
||||
const [deviceGoods, setDeviceGoods] = useState<DeviceGooodsItem[]>(Array.from({ length: 30 }, (_, i) => ({ idx: i + 1, column_no: String(i + 1).padStart(2, "0"), goods_id: 0, inventory: 0, inventory_max: 0, inventory_alarm: 0 })));
|
||||
const [optGoodsList, setOptGoodsList] = useState<{ label: string; value: string; }[]>([]);
|
||||
const [goodsTemplate, setGoodsTemplate] = useState<GoodsTemplateData | null>(null);
|
||||
const [inventorySetValue, setInventorySetValue] = useState<{ inventory: number; inventory_max: number; inventory_alarm: number }>({ inventory: 0, inventory_max: 0, inventory_alarm: 0 });
|
||||
@@ -105,7 +105,7 @@ export default function GoodsTemplateEdit() {
|
||||
|
||||
const handleChangeSlot = (index: number, value: string) => {
|
||||
const newGoods = [...deviceGoods];
|
||||
newGoods[index].slot = value;
|
||||
newGoods[index].column_no = value;
|
||||
setDeviceGoods(newGoods);
|
||||
};
|
||||
|
||||
@@ -138,8 +138,8 @@ export default function GoodsTemplateEdit() {
|
||||
};
|
||||
|
||||
const handleClickAdd = () => {
|
||||
//const newItem: DeviceGooodsItem = { slot: "", goods_id: parseInt(optGoodsList[0].value) }
|
||||
const newItem: DeviceGooodsItem = { slot: "", goods_id: 0 }
|
||||
//const newItem: DeviceGooodsItem = { column_no: "", goods_id: parseInt(optGoodsList[0].value) }
|
||||
const newItem: DeviceGooodsItem = { column_no: "", goods_id: 0 }
|
||||
const newArray: DeviceGooodsItem[] = [...deviceGoods, newItem];
|
||||
|
||||
setDeviceGoods(newArray);
|
||||
@@ -152,7 +152,7 @@ export default function GoodsTemplateEdit() {
|
||||
}
|
||||
|
||||
const validGoods = deviceGoods.filter(item =>
|
||||
item.slot && item.goods_id && item.inventory != null && item.inventory > 0
|
||||
item.column_no && item.goods_id && item.inventory != null && item.inventory > 0
|
||||
);
|
||||
|
||||
if (validGoods.length === 0) {
|
||||
@@ -169,7 +169,7 @@ export default function GoodsTemplateEdit() {
|
||||
inventory_alarm: inventorySetValue.inventory_alarm
|
||||
},
|
||||
goods_template_elements: validGoods.map(item => ({
|
||||
slot: item.slot,
|
||||
column_no: item.column_no,
|
||||
goods_id: item.goods_id,
|
||||
inventory: item.inventory,
|
||||
idx: item.idx,
|
||||
|
||||
@@ -118,7 +118,7 @@ export default function AddModifyGoodsModal({ multiState, onOk, isOpen, closeMod
|
||||
>
|
||||
<div>
|
||||
<Label>사업자원장</Label>
|
||||
<Input type="text" value={multiState.values.biz_group_name + "(" + multiState.values.biz_reg_num + ")"} disabled />
|
||||
<Input type="text" value={multiState.values.biz_group_name + (multiState.values.biz_reg_num ? `(${multiState.values.biz_reg_num})` : "")} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
import PageBreadcrumb from "@/components/common/PageBreadCrumb2";
|
||||
import ComponentCard from '@/components/common/ComponentCard2';
|
||||
import BizGroupPanel from "@/components/common/BizGroupPanel";
|
||||
import BizGroupPanel from "@/components/BizGroupPanel";
|
||||
import CtTable1 from '@/components/tables/CtTable1';
|
||||
import Button from '@/components/ui/button/Button2';
|
||||
import { DocsIcon, PlusIcon, TrashBinIcon, TableIcon, PencilIcon } from "@/icons";
|
||||
@@ -15,7 +15,7 @@ import Badge from "@/components/ui/badge/Badge";
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import api, { convertDateTime3, getUrlParam, postFileDownload, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, getUrlParam, postFileDownload, todayYMD, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyGoodsModal, { GoodsModalProps } from './AddModifyGoodsModal';
|
||||
@@ -23,10 +23,11 @@ import { useMultiState } from "@/hooks/useMultiState";
|
||||
|
||||
import CtTree, { CtTreeNode, expandAndSelectTreeNode, getAllNodeKeys, findParentKeys, findNodeByKey, addNodeByKey, deleteNodeByKey, findRootNodeByKey } from "@/components/CtTree";
|
||||
import { reqBizGroupTree2, reqBizGroupTree3 } from "@/app/(admin)/biz-group/BizGroupContent";
|
||||
import PageCountSelector from "@/components/PageCountSelector";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
//const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -38,20 +39,22 @@ export default function Goods() {
|
||||
const [ tableData, setTableData ] = useState<any[]>([]);
|
||||
const [ tableOffset, setTableOffset ] = useState<number>(0);
|
||||
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
|
||||
|
||||
const [ COUNT_PER_PAGE, setCOUNT_PER_PAGE ] = useState<number>(20);
|
||||
|
||||
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
|
||||
const [ isGroupAccess, setGroupAccess ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
const [ isTreeOpen, setTreeOpen ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
|
||||
// group search fields
|
||||
const [ name, setName] = useState<string>("");
|
||||
const [ biz_reg_num, setBizRegNum] = useState<string>("");
|
||||
|
||||
// goods search fields
|
||||
const [ date_start, setDateStart] = useState<string>("");
|
||||
const [ date_end, setDateEnd] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>("");
|
||||
const [ date_end, setDateEnd ] = useState<string>("");
|
||||
|
||||
// modal
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
@@ -129,17 +132,17 @@ export default function Goods() {
|
||||
//
|
||||
// tree part //////////
|
||||
const handleClickGroupSearch = () => {
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (name == "" && biz_reg_num == "") {
|
||||
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (name != "" && name.length < 3) {
|
||||
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
|
||||
if (name != "" && name.length < 2) {
|
||||
alert("그룹(사업자)명은 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 3) {
|
||||
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 2) {
|
||||
alert("사업자등록번호는 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,7 +175,7 @@ export default function Goods() {
|
||||
const handleClickGroupAccess = (value: boolean) => {
|
||||
setGroupAccess(value);
|
||||
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (selectedTreeKeys.length <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -259,14 +262,14 @@ export default function Goods() {
|
||||
if (tokenPayload == undefined)
|
||||
return;
|
||||
|
||||
if (tokenPayload?.permission == 1)
|
||||
if (tokenPayload?.permission == 0xffffffff)
|
||||
return;
|
||||
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
setGroupAccess(tokenPayload?.permission == 1);
|
||||
setTreeOpen(tokenPayload?.permission == 1);
|
||||
setGroupAccess(tokenPayload?.permission == 0xffffffff);
|
||||
setTreeOpen(tokenPayload?.permission == 0xffffffff);
|
||||
|
||||
reqGoodsList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
|
||||
reqGoodsList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
});
|
||||
}, [tokenPayload]);
|
||||
|
||||
@@ -281,7 +284,8 @@ export default function Goods() {
|
||||
{
|
||||
title: "등록일",
|
||||
key: "reg_time",
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time)
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "사업자",
|
||||
@@ -290,10 +294,12 @@ export default function Goods() {
|
||||
{
|
||||
title: "상품코드",
|
||||
key: "code",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "상품명",
|
||||
key: "name",
|
||||
viewMobile: true,
|
||||
},
|
||||
/*
|
||||
{
|
||||
@@ -345,7 +351,7 @@ export default function Goods() {
|
||||
onChangeBizRegNum={setBizRegNum}
|
||||
onClickSearch={handleClickGroupSearch}
|
||||
isDisabled={isLoadingGlobal}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH_PX}
|
||||
>
|
||||
<CtTree
|
||||
treeData={treeData}
|
||||
@@ -359,37 +365,36 @@ export default function Goods() {
|
||||
</BizGroupPanel>
|
||||
|
||||
<ComponentCard title="상품 목록" titleIcon={<TableIcon />}>
|
||||
<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">
|
||||
{ isTreeOpen && <>
|
||||
<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 className="space-y-2 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
|
||||
<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 className="flex items-center justify-end gap-5">
|
||||
{ isTreeOpen && <Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} label="그룹단위로 조회" /> }
|
||||
<PageCountSelector defaultValue={COUNT_PER_PAGE.toString()} onChange={(e) => setCOUNT_PER_PAGE(parseInt(e.target.value))} />
|
||||
</div>
|
||||
|
||||
<CtTable1 columns={columns} bodyData={tableData}
|
||||
offset={tableOffset} totalCount={tableTotalCount} countPerPage={COUNT_PER_PAGE} pageNumCount={PAGE_NUM_COUNT}
|
||||
onClickPageChange={handlePageChange} />
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function AddModifyLocationModal({ multiState, onOk, isOpen, close
|
||||
|
||||
const handleClickAddOrModify = () => {
|
||||
if (multiState.values.name == "") {
|
||||
alert("그룹명(사업자명)을 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명을 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import Input from '@/components/form/input/InputField2';
|
||||
import DatePicker from '@/components/form/date-picker2';
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import api, { convertDateTime3 } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, todayYMD } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyLocationModal, { LocationModalProps } from "./AddModifyLocationModal";
|
||||
@@ -17,7 +17,7 @@ import { useMultiState } from "@/hooks/useMultiState";
|
||||
|
||||
import CtTree, { CtTreeNode, expandAndSelectTreeNode, getAllNodeKeys, findParentKeys, findNodeByKey, addNodeByKey, changeNodeByKey, deleteNodeByKey } from "@/components/CtTree";
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
|
||||
|
||||
export default function Location() {
|
||||
@@ -32,8 +32,8 @@ export default function Location() {
|
||||
const [ name, setName] = useState<string>("");
|
||||
const [ description, setDescription] = useState<string>("");
|
||||
const [ address, setAddress] = useState<string>("");
|
||||
const [ date_start, setDateStart] = useState<string>("");
|
||||
const [ date_end, setDateEnd] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>("");
|
||||
const [ date_end, setDateEnd ] = useState<string>("");
|
||||
//
|
||||
|
||||
// form fields
|
||||
@@ -237,15 +237,15 @@ export default function Location() {
|
||||
<ComponentCard title="위치정보 조회" titleIcon={<TableIcon />}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="위치명" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="위치명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 위치명을 입력하세요." value={name} onChange={(e) => setName(e.target.value)}
|
||||
onKeyDown={(e) => {if (e.key === "Enter") handleClickSearch()}} />
|
||||
</WithLabel>
|
||||
<WithLabel label="설명" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="설명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 설명를 입력하세요." value={description} onChange={(e) => setDescription(e.target.value)}
|
||||
onKeyDown={(e) => {if (e.key === "Enter") handleClickSearch()}} />
|
||||
</WithLabel>
|
||||
<WithLabel label="주소" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="주소" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 주소를 입력하세요." value={address} onChange={(e) => setAddress(e.target.value)}
|
||||
onKeyDown={(e) => {if (e.key === "Enter") handleClickSearch()}} />
|
||||
</WithLabel>
|
||||
|
||||
@@ -10,10 +10,10 @@ import DatePicker from '@/components/form/date-picker2';
|
||||
|
||||
import React from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import api, { convertDateTime, convertDateTime2, convertDateTime3, convertDateTime6, postFileDownload, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime, convertDateTime2, convertDateTime3, convertDateTime6, postFileDownload, todayYMD, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
@@ -97,12 +97,14 @@ export default function Logging() {
|
||||
{
|
||||
title: "날짜",
|
||||
key: "reg_time",
|
||||
renderItem: (item: any, row: number) => convertDateTime2(item.reg_time)
|
||||
renderItem: (item: any, row: number) => convertDateTime2(item.reg_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "유형",
|
||||
key: "type",
|
||||
renderItem: (item : any) => ( item.type === 1 ? "system" : "operating")
|
||||
renderItem: (item : any) => ( item.type === 1 ? "system" : "operating"),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "작업 내용",
|
||||
@@ -123,7 +125,7 @@ export default function Logging() {
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="작업내용 검색어" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="작업내용 검색어" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="작업내용 검색어를 입력하세요."
|
||||
@@ -131,7 +133,7 @@ export default function Logging() {
|
||||
onChange={(e) => setAction(e.target.value)}
|
||||
/>
|
||||
</WithLabel>
|
||||
<WithLabel label="작업자 아이디" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="작업자 아이디" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="조회할 작업자 아이디를 입력하세요."
|
||||
@@ -141,17 +143,19 @@ export default function Logging() {
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
|
||||
@@ -11,14 +11,14 @@ import DatePicker from '@/components/form/date-picker2';
|
||||
import React from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import api, { convertDateTime3 } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, todayYMD } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyNoticeModal, {NoticeModalProps} from './AddModifyNoticeModal';
|
||||
import { useMultiState } from "@/hooks/useMultiState";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
@@ -132,7 +132,8 @@ export default function Notice() {
|
||||
{
|
||||
title: "날짜",
|
||||
key: "reg_time",
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time)
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "홈화면 시작일",
|
||||
@@ -152,6 +153,7 @@ export default function Notice() {
|
||||
{
|
||||
title: "제목",
|
||||
key: "title",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "작성자",
|
||||
@@ -168,33 +170,27 @@ export default function Notice() {
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<WithLabel label="제목" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="제목" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="제목을 입력하세요." value={title} onChange={(e) => setTitle(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="작성자 아이디" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="작성자 아이디" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 작성자 아이디를 입력하세요." value={uid1} onChange={(e) => setUid1(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
// onChange={(dates, currentDateString) => {
|
||||
// // Handle your logic
|
||||
// console.log({ dates, currentDateString });
|
||||
// }}
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
// onChange={(dates, currentDateString) => {
|
||||
// // Handle your logic
|
||||
// console.log({ dates, currentDateString });
|
||||
// }}
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ export default function AddModifyResourceBoardModal({ multiState, onOk, isOpen,
|
||||
|
||||
const handleClickAddOrModify = () => {
|
||||
if (multiState.values.user_id == "") {
|
||||
alert("그룹명(사업자명)을 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명을 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
//else if (multiState.values.user_pw == "") {
|
||||
|
||||
@@ -11,14 +11,14 @@ import DatePicker from '@/components/form/date-picker2';
|
||||
import React from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import api, { convertDateTime3 } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, todayYMD } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyResourceBoardModal, {ResourceBoardModalProps} from './AddModifyResourceBoardModal';
|
||||
import { useMultiState } from "@/hooks/useMultiState";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
@@ -143,7 +143,7 @@ export default function ResourceBoard() {
|
||||
}
|
||||
|
||||
if (multiState.values.title == "") {
|
||||
alert("그룹명(사업자명)을 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명을 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -207,25 +207,27 @@ export default function ResourceBoard() {
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<WithLabel label="제목" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="제목" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="제목을 입력하세요." value={title} onChange={(e) => setTitle(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="작성자 아이디" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="작성자 아이디" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 작성자 아이디를 입력하세요." value={uid1} onChange={(e) => setUid1(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
"use client"
|
||||
import PageBreadcrumb from "@/components/common/PageBreadCrumb2";
|
||||
import ComponentCard from '@/components/common/ComponentCard2';
|
||||
import BizGroupPanel from "@/components/common/BizGroupPanel";
|
||||
import BizGroupPanel from "@/components/BizGroupPanel";
|
||||
import CtTable1 from '@/components/tables/CtTable1';
|
||||
import Button from '@/components/ui/button/Button2';
|
||||
import { DocsIcon, PlusIcon, TableIcon, ChevronDownIcon } from "@/icons";
|
||||
import { DocsIcon, PlusIcon, TableIcon, ChevronDownIcon, FilterIcon } from "@/icons";
|
||||
import WithLabel from '@/components/form/WithLabel';
|
||||
import Input from '@/components/form/input/InputField2';
|
||||
import DatePicker from '@/components/form/date-picker2';
|
||||
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import api, { convertDateTime3, postFileDownload, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
import api, { convertDateTime3, postFileDownload, todayYMD, addDaysYMD, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
import CtTree, { CtTreeNode, findNodeByKey, findRootNodeByKey, getAllNodeKeys } from "@/components/CtTree";
|
||||
import { reqBizGroupTree2, reqBizGroupTree3 } from "../biz-group/BizGroupContent";
|
||||
import Checkbox from "@/components/form/input/Checkbox";
|
||||
import Select from "@/components/form/Select2";
|
||||
import CtDrawer from "@/components/CtDrawer";
|
||||
import CtSelectedTextField from "@/components/CtSelectedText";
|
||||
import PageCountSelector from "@/components/PageCountSelector";
|
||||
import InputSelectField from "@/components/form/input/InputSelectField";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
//const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -27,15 +32,18 @@ export default function Sales() {
|
||||
const tokenPayload = useAuthStore((s) => s.tokenPayload); //Bruce, JWT
|
||||
const isLoadingGlobal = useUiLoadingStore((s) => s.isLoading);
|
||||
|
||||
const [ sumData, setSumData ] = useState<any[]>([]);
|
||||
const [ tableData, setTableData ] = useState<any[]>([]);
|
||||
const [ tableOffset, setTableOffset ] = useState<number>(0);
|
||||
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
|
||||
|
||||
const [ COUNT_PER_PAGE, setCOUNT_PER_PAGE ] = useState<number>(20);
|
||||
|
||||
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
|
||||
const [ isGroupAccess, setGroupAccess ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
const [ isTreeOpen, setTreeOpen ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
|
||||
// group search fields
|
||||
const [ name, setName ] = useState<string>("");
|
||||
@@ -43,18 +51,60 @@ export default function Sales() {
|
||||
|
||||
//search fields
|
||||
const [ device_name, setDeivceName ] = useState<string>("");
|
||||
const [ query_uid1, setQueryUid1 ] = useState<boolean>(false);
|
||||
const [ uid1, setUid1 ] = useState<string>("");
|
||||
const [ list_uid1, setListUid1 ] = useState<{label: string; value: string}[]>([]);
|
||||
const [ approval_type, setApprovalType ] = useState<string>("0");
|
||||
const [ date_type, setDateType ] = useState<string>("1");
|
||||
const [ date_start, setDateStart ] = useState<string>("");
|
||||
const [ date_end, setDateEnd ] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>(addDaysYMD(-7));
|
||||
const [ date_end, setDateEnd ] = useState<string>(todayYMD());
|
||||
|
||||
const [ isMobileMode, setIsMobileMode ] = useState<boolean>(false);
|
||||
const [ isSearchFieldOpen, setIsSearchFieldOpen ] = useState<boolean>(false);
|
||||
|
||||
const [ listSelectedText, setListSelectedText ] = useState<string[]>(["일별집계"]);
|
||||
const changeListSelectedText = (index: number, text: string) => {
|
||||
setListSelectedText(prev => {
|
||||
const copy = [...prev];
|
||||
copy[index] = text;
|
||||
return copy;
|
||||
});
|
||||
}
|
||||
|
||||
const [ show_query_uid1, setShowQueryUid1 ] = useState<boolean>(false);
|
||||
|
||||
|
||||
// function part
|
||||
//
|
||||
function reqUid1List() {
|
||||
api.post('/api/list-uid1.do').then((resp) => {
|
||||
if (resp.data.errCode == 0) {
|
||||
if (resp.data.result.list.length == 0) {
|
||||
setListUid1([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setListUid1(
|
||||
resp.data.result.list.map((item: { uid1: string; name: string }) => ({
|
||||
label: item.uid1 + (item.name !== "" ? " (" + item.name + ")" : ""),
|
||||
value: item.uid1,
|
||||
}))
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err))
|
||||
}
|
||||
|
||||
function reqSalesList(is_excel: boolean, biz_group_id: string, groupAccess: boolean) {
|
||||
if (!biz_group_id)
|
||||
return;
|
||||
|
||||
if (!groupAccess) {
|
||||
const root = findRootNodeByKey(treeData, biz_group_id);
|
||||
if (root)
|
||||
biz_group_id = root.key;
|
||||
}
|
||||
|
||||
const params = {
|
||||
is_group_access: groupAccess,
|
||||
is_excel: is_excel,
|
||||
@@ -62,6 +112,7 @@ export default function Sales() {
|
||||
limit: COUNT_PER_PAGE,
|
||||
biz_group_id: biz_group_id,
|
||||
device_name: device_name,
|
||||
query_uid1: query_uid1,
|
||||
uid1: uid1,
|
||||
approval_type: approval_type,
|
||||
date_type: date_type,
|
||||
@@ -81,11 +132,15 @@ export default function Sales() {
|
||||
if (resp.data.result.list.length == 0) {
|
||||
alert("조회 결과가 없습니다.");
|
||||
setTableData([]);
|
||||
setSumData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setShowQueryUid1(query_uid1);
|
||||
|
||||
setTableTotalCount(resp.data.result.totalCount);
|
||||
setTableData(resp.data.result.list);
|
||||
setSumData(resp.data.result.sum);
|
||||
}
|
||||
else {
|
||||
alert("일시적으로 사용할 수 없습니다 : " + resp.data.errCode);
|
||||
@@ -99,17 +154,26 @@ export default function Sales() {
|
||||
//
|
||||
// tree part //////////
|
||||
const handleClickGroupSearch = () => {
|
||||
if (tokenPayload?.permission == 1) {
|
||||
/*
|
||||
console.log("tokenPayload?.permission 11: ", tokenPayload?.permission);
|
||||
if (tokenPayload?.permission == 0xffffffff)
|
||||
console.log("tokenPayload?.permission 0xffffffff");
|
||||
else
|
||||
console.log("tokenPayload?.permission else......");*/
|
||||
|
||||
|
||||
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (name == "" && biz_reg_num == "") {
|
||||
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (name != "" && name.length < 3) {
|
||||
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
|
||||
if (name != "" && name.length < 2) {
|
||||
alert("그룹(사업자)명은 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 3) {
|
||||
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 2) {
|
||||
alert("사업자등록번호는 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,7 +181,9 @@ export default function Sales() {
|
||||
}
|
||||
else {
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
//reqSalesList(false, String(tokenPayload?.biz_group_id), tokenPayload?.permission == 1);
|
||||
reqUid1List();
|
||||
|
||||
//reqSalesList(false, String(tokenPayload?.biz_group_id), tokenPayload?.permission == 0xffffffff);
|
||||
reqSalesList(false, String(tokenPayload?.biz_group_id), isGroupAccess);
|
||||
});
|
||||
}
|
||||
@@ -143,7 +209,7 @@ export default function Sales() {
|
||||
const handleClickGroupAccess = (value: boolean) => {
|
||||
setGroupAccess(value);
|
||||
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (selectedTreeKeys.length <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -181,19 +247,43 @@ export default function Sales() {
|
||||
//
|
||||
|
||||
|
||||
const handleChangeDateType = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setDateType(e.target.value);
|
||||
changeListSelectedText(0, e.target.options[e.target.selectedIndex].text);
|
||||
}
|
||||
|
||||
const handleChangeApprovalType = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setApprovalType(e.target.value);
|
||||
changeListSelectedText(1, e.target.options[e.target.selectedIndex].text);
|
||||
}
|
||||
|
||||
const handleChangeDeivceName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDeivceName(e.target.value);
|
||||
changeListSelectedText(2, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeUid1 = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUid1(e.target.value);
|
||||
changeListSelectedText(3, e.target.value);
|
||||
}
|
||||
//
|
||||
|
||||
useEffect(() => {
|
||||
if (tokenPayload == undefined)
|
||||
return;
|
||||
|
||||
if (tokenPayload?.permission == 1)
|
||||
if (tokenPayload?.permission == 0xffffffff)
|
||||
return;
|
||||
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
setGroupAccess(tokenPayload?.permission == 1);
|
||||
setTreeOpen(tokenPayload?.permission == 1);
|
||||
reqUid1List();
|
||||
|
||||
reqSalesList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
|
||||
setGroupAccess(tokenPayload?.permission == 0xffffffff);
|
||||
setTreeOpen(tokenPayload?.permission == 0xffffffff);
|
||||
|
||||
reqSalesList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
});
|
||||
|
||||
}, [tokenPayload]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -205,6 +295,7 @@ export default function Sales() {
|
||||
{
|
||||
title: "날짜",
|
||||
key: "date",
|
||||
viewMobile: true,
|
||||
},
|
||||
/*
|
||||
{
|
||||
@@ -215,14 +306,18 @@ export default function Sales() {
|
||||
{
|
||||
title: "무인기기명",
|
||||
key: "device_name",
|
||||
viewMobile: show_query_uid1,
|
||||
hidden: !show_query_uid1,
|
||||
},
|
||||
{
|
||||
title: "단말기TID",
|
||||
key: "uid1",
|
||||
hidden: !show_query_uid1,
|
||||
},
|
||||
{
|
||||
title: "카드건수",
|
||||
key: "card_count",
|
||||
renderItem: (item : any) => (item.card_count.toLocaleString("ko-KR"))
|
||||
},
|
||||
{
|
||||
title: "카드금액",
|
||||
@@ -232,6 +327,7 @@ export default function Sales() {
|
||||
{
|
||||
title: "현금건수",
|
||||
key: "cash_count",
|
||||
renderItem: (item : any) => (item.cash_count.toLocaleString("ko-KR"))
|
||||
},
|
||||
{
|
||||
title: "현금금액",
|
||||
@@ -240,6 +336,7 @@ export default function Sales() {
|
||||
},{
|
||||
title: "T머니건수",
|
||||
key: "tmoney_count",
|
||||
renderItem: (item : any) => (item.tmoney_count.toLocaleString("ko-KR"))
|
||||
},
|
||||
{
|
||||
title: "T머니금액",
|
||||
@@ -248,6 +345,7 @@ export default function Sales() {
|
||||
},{
|
||||
title: "캐시비건수",
|
||||
key: "cbee_count",
|
||||
renderItem: (item : any) => (item.cbee_count.toLocaleString("ko-KR"))
|
||||
},
|
||||
{
|
||||
title: "캐시비금액",
|
||||
@@ -257,10 +355,59 @@ export default function Sales() {
|
||||
{
|
||||
title: "합계",
|
||||
key: "total",
|
||||
renderItem: (item : any) => (item.total.toLocaleString("ko-KR") + "원")
|
||||
renderItem: (item : any) => (item.total.toLocaleString("ko-KR") + "원"),
|
||||
viewMobile: true,
|
||||
}
|
||||
];
|
||||
|
||||
const sumColumns = [
|
||||
{
|
||||
title: "카드건수",
|
||||
key: "card_count",
|
||||
renderItem: (item : any) => (item.card_count.toLocaleString("ko-KR"))
|
||||
},
|
||||
{
|
||||
title: "카드금액",
|
||||
key: "card_amount",
|
||||
renderItem: (item : any) => (item.card_amount.toLocaleString("ko-KR") + "원")
|
||||
},
|
||||
{
|
||||
title: "현금건수",
|
||||
key: "cash_count",
|
||||
renderItem: (item : any) => (item.cash_count.toLocaleString("ko-KR"))
|
||||
},
|
||||
{
|
||||
title: "현금금액",
|
||||
key: "cash_amount",
|
||||
renderItem: (item : any) => (item.cash_amount.toLocaleString("ko-KR") + "원")
|
||||
},{
|
||||
title: "T머니건수",
|
||||
key: "tmoney_count",
|
||||
renderItem: (item : any) => (item.tmoney_count.toLocaleString("ko-KR"))
|
||||
},
|
||||
{
|
||||
title: "T머니금액",
|
||||
key: "tmoney_amount",
|
||||
renderItem: (item : any) => (item.tmoney_amount.toLocaleString("ko-KR") + "원")
|
||||
},{
|
||||
title: "캐시비건수",
|
||||
key: "cbee_count",
|
||||
renderItem: (item : any) => (item.cbee_count.toLocaleString("ko-KR"))
|
||||
},
|
||||
{
|
||||
title: "캐시비금액",
|
||||
key: "cbee_amount",
|
||||
renderItem: (item : any) => (item.cbee_amount.toLocaleString("ko-KR") + "원")
|
||||
},
|
||||
{
|
||||
title: "합계",
|
||||
key: "total",
|
||||
renderItem: (item : any) => (item.total.toLocaleString("ko-KR") + "원"),
|
||||
}
|
||||
];
|
||||
|
||||
const options = ["Option 1", "abcdefg", "Optio333333"];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle1="매출 및 통계" pageTitle2="매출집계" />
|
||||
@@ -276,7 +423,7 @@ export default function Sales() {
|
||||
onChangeBizRegNum={setBizRegNum}
|
||||
onClickSearch={handleClickGroupSearch}
|
||||
isDisabled={isLoadingGlobal}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH_PX}
|
||||
>
|
||||
<CtTree
|
||||
treeData={treeData}
|
||||
@@ -288,72 +435,86 @@ export default function Sales() {
|
||||
onClickExpanded={handleClickTreeExpanded}
|
||||
/>
|
||||
</BizGroupPanel>
|
||||
|
||||
|
||||
<ComponentCard title="매출 집계" titleIcon={<TableIcon />}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="일별/월별" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Select
|
||||
placeholder="선택하세요"
|
||||
options={[
|
||||
{ label: "일별집계", value: "1" },
|
||||
{ label: "월별집계", value: "2" },
|
||||
]}
|
||||
defaultValue={date_type} onChange={(value) => setDateType(value)}
|
||||
className="dark:bg-dark-900" />
|
||||
</WithLabel>
|
||||
<WithLabel label="승인/취소" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Select
|
||||
placeholder="선택하세요"
|
||||
options={[
|
||||
{ label: "전체", value: "0" },
|
||||
{ label: "승인", value: "1" },
|
||||
{ label: "취소", value: "2" },
|
||||
]}
|
||||
defaultValue={approval_type} onChange={(value) => setApprovalType(value)}
|
||||
className="dark:bg-dark-900" />
|
||||
</WithLabel>
|
||||
<WithLabel label="무인기기명" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="승인번호를 입력하세요." value={device_name} onChange={(e) => setDeivceName(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="단말기 TID" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="금액을 입력하세요." value={uid1} onChange={(e) => setUid1(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
|
||||
<CtDrawer title="상세 조회" isMobileMode={isMobileMode} setIsMobileMode={setIsMobileMode} isSearchFieldOpen={isSearchFieldOpen} setIsSearchFieldOpen={setIsSearchFieldOpen}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="일별/월별" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Select
|
||||
//placeholder="선택하세요"
|
||||
options={[
|
||||
{ label: "일별집계", value: "1" },
|
||||
{ label: "월별집계", value: "2" },
|
||||
]}
|
||||
defaultValue={date_type} onChange={handleChangeDateType}
|
||||
className="dark:bg-dark-900" />
|
||||
</WithLabel>
|
||||
<WithLabel label="승인/취소" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Select
|
||||
//placeholder="선택하세요"
|
||||
options={[
|
||||
{ label: "전체", value: "0" },
|
||||
{ label: "승인", value: "1" },
|
||||
{ label: "취소", value: "2" },
|
||||
]}
|
||||
defaultValue={approval_type} onChange={handleChangeApprovalType}
|
||||
className="dark:bg-dark-900" />
|
||||
</WithLabel>
|
||||
<WithLabel label="무인기기명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="무인기기명을 입력하세요." value={device_name} onChange={handleChangeDeivceName} />
|
||||
</WithLabel>
|
||||
<WithLabel label="단말기 TID" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<InputSelectField type="text" placeholder="TID를 입력하세요." value={uid1} options={list_uid1} onChange={handleChangeUid1} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
</div>
|
||||
</CtDrawer>
|
||||
|
||||
{/*<div className="space-y-3">*/}
|
||||
<div className="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">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
<div className="flex items-center gap-3">
|
||||
{ isTreeOpen && <>
|
||||
<Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} />
|
||||
<span className="block font-normal text-gray-700 text-theme-sm dark:text-gray-400">
|
||||
그룹단위로 조회
|
||||
</span>
|
||||
</>}
|
||||
|
||||
<div className="flex items-center gap-5">
|
||||
<Checkbox checked={query_uid1} onChange={(value) => setQueryUid1(value)} label="TID" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
<Button size="sm" variant="outline" startIcon={<DocsIcon />} onClick={handleClickDownload}>엑셀파일 다운로드</Button>
|
||||
{ isMobileMode && <Button size="sm" variant="outline" startIcon={<FilterIcon />} onClick={() => setIsSearchFieldOpen(true)}>필터</Button> }
|
||||
<Button size="sm" variant="primary" onClick={handleClickSearch} disabled={isLoadingGlobal}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/*</div>*/}
|
||||
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
{ isTreeOpen && <Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} label="그룹단위로 조회" /> }
|
||||
<PageCountSelector defaultValue={COUNT_PER_PAGE.toString()} onChange={(e) => setCOUNT_PER_PAGE(parseInt(e.target.value))} />
|
||||
</div>
|
||||
|
||||
<CtTable1 columns={columns} bodyData={tableData}
|
||||
{ isMobileMode &&
|
||||
<CtSelectedTextField label={"필터:"} listText={listSelectedText} setListText={setListSelectedText}></CtSelectedTextField>
|
||||
}
|
||||
|
||||
<CtTable1 columns={columns} sumColumns={sumColumns} sumData={sumData} bodyData={tableData}
|
||||
offset={tableOffset} totalCount={tableTotalCount} countPerPage={COUNT_PER_PAGE} pageNumCount={PAGE_NUM_COUNT}
|
||||
onClickPageChange={handlePageChange} />
|
||||
|
||||
</ComponentCard>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -54,9 +54,11 @@ export default function AddModifyTerminalModal({ multiState, onOk, isOpen, close
|
||||
multiState.set("uid1", value);
|
||||
|
||||
if (isAutoName) {
|
||||
multiState.set("name", multiState.values.biz_group_name as string);
|
||||
//multiState.set("name", multiState.values.biz_group_name + "-" + value); //...
|
||||
//multiState.set("name", prefix + "-" + value); //...
|
||||
multiState.set("name", prefix); //...
|
||||
//multiState.set("name", prefix); //...
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -64,7 +66,7 @@ export default function AddModifyTerminalModal({ multiState, onOk, isOpen, close
|
||||
setPrefix(randomUppercase(7)); //...
|
||||
|
||||
if (multiState.values.name == "") {
|
||||
alert("그룹명(사업자명)을 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명을 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,7 +135,7 @@ export default function AddModifyTerminalModal({ multiState, onOk, isOpen, close
|
||||
>
|
||||
<div>
|
||||
<Label>사업자원장</Label>
|
||||
<Input type="text" value={multiState.values.biz_group_name + "(" + multiState.values.biz_reg_num + ")"} disabled />
|
||||
<Input type="text" value={multiState.values.biz_group_name + (multiState.values.biz_reg_num ? `(${multiState.values.biz_reg_num})` : "")} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -160,7 +162,7 @@ export default function AddModifyTerminalModal({ multiState, onOk, isOpen, close
|
||||
{ label: "NICE", value: "2" }
|
||||
]}
|
||||
defaultValue={multiState.values.type}
|
||||
onChange={(value) => {setOptTerminalType(value)}}
|
||||
onChange={(e) => {setOptTerminalType(e.target.value)}}
|
||||
className="dark:bg-dark-900" />
|
||||
</div>
|
||||
{ multiState.values.isModify == false ? <>
|
||||
@@ -173,7 +175,7 @@ export default function AddModifyTerminalModal({ multiState, onOk, isOpen, close
|
||||
{ label: "카드단말기 교체용으로 추가", value: "2" }
|
||||
]}
|
||||
defaultValue={multiState.values.addType}
|
||||
onChange={(value) => {setOptAddType(value)}}
|
||||
onChange={(e) => {setOptAddType(e.target.value)}}
|
||||
className="dark:bg-dark-900" />
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client"
|
||||
import PageBreadcrumb from "@/components/common/PageBreadCrumb2";
|
||||
import ComponentCard from '@/components/common/ComponentCard2';
|
||||
import BizGroupPanel from "@/components/common/BizGroupPanel";
|
||||
import BizGroupPanel from "@/components/BizGroupPanel";
|
||||
import CtTable1 from '@/components/tables/CtTable1';
|
||||
import Button from '@/components/ui/button/Button2';
|
||||
import { DocsIcon, PlusIcon, TrashBinIcon, TableIcon, PencilIcon } from "@/icons";
|
||||
import { DocsIcon, PlusIcon, TrashBinIcon, TableIcon, PencilIcon, FilterIcon } from "@/icons";
|
||||
import WithLabel from '@/components/form/WithLabel';
|
||||
import Input from '@/components/form/input/InputField2';
|
||||
import Select from '@/components/form/Select2';
|
||||
@@ -15,7 +15,7 @@ import Badge from "@/components/ui/badge/Badge";
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import api, { convertDateTime3, getUrlParam, postFileDownload, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, getUrlParam, postFileDownload, todayYMD, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyTerminalModal, { TerminalModalProps } from './AddModifyTerminalModal';
|
||||
@@ -23,10 +23,13 @@ import { useMultiState } from "@/hooks/useMultiState";
|
||||
|
||||
import CtTree, { CtTreeNode, expandAndSelectTreeNode, getAllNodeKeys, findParentKeys, findNodeByKey, addNodeByKey, deleteNodeByKey, findRootNodeByKey } from "@/components/CtTree";
|
||||
import { reqBizGroupTree2, reqBizGroupTree3 } from "@/app/(admin)/biz-group/BizGroupContent";
|
||||
import PageCountSelector from "@/components/PageCountSelector";
|
||||
import CtDrawer from "@/components/CtDrawer";
|
||||
import InputSelectField from "@/components/form/input/InputSelectField";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
//const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -38,28 +41,68 @@ export default function Terminal() {
|
||||
const [ tableData, setTableData ] = useState<any[]>([]);
|
||||
const [ tableOffset, setTableOffset ] = useState<number>(0);
|
||||
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
|
||||
|
||||
const [ COUNT_PER_PAGE, setCOUNT_PER_PAGE ] = useState<number>(20);
|
||||
|
||||
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
|
||||
const [ isGroupAccess, setGroupAccess ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
const [ isTreeOpen, setTreeOpen ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
|
||||
// group search fields
|
||||
const [ name, setName] = useState<string>("");
|
||||
const [ biz_reg_num, setBizRegNum] = useState<string>("");
|
||||
|
||||
// terminal search fields
|
||||
const [ date_start, setDateStart] = useState<string>("");
|
||||
const [ date_end, setDateEnd] = useState<string>("");
|
||||
const [ group_name, setGroupName] = useState<string>("");
|
||||
const [ device_name, setDeviceName ] = useState<string>("");
|
||||
const [ uid1, setUid1 ] = useState<string>("");
|
||||
const [ list_uid1, setListUid1 ] = useState<{label: string; value: string}[]>([]);
|
||||
const [ state, setState ] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>("");
|
||||
const [ date_end, setDateEnd ] = useState<string>("");
|
||||
|
||||
// modal
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
const modalData = useMultiState<TerminalModalProps>({biz_group_name: "", biz_reg_num: "", isModify: false, addType: "1", terminal_id: "", biz_group_id: "", name: "", uid1: "", type: "" });
|
||||
|
||||
const [ isMobileMode, setIsMobileMode ] = useState<boolean>(false);
|
||||
const [ isSearchFieldOpen, setIsSearchFieldOpen ] = useState<boolean>(false);
|
||||
|
||||
const [ listSelectedText, setListSelectedText ] = useState<string[]>([]);
|
||||
const changeListSelectedText = (index: number, text: string) => {
|
||||
setListSelectedText(prev => {
|
||||
const copy = [...prev];
|
||||
copy[index] = text;
|
||||
return copy;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// function part
|
||||
//
|
||||
function reqUid1List() {
|
||||
api.post('/api/list-uid1.do').then((resp) => {
|
||||
console.log(resp);
|
||||
|
||||
if (resp.data.errCode == 0) {
|
||||
if (resp.data.result.list.length == 0) {
|
||||
setListUid1([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setListUid1(
|
||||
resp.data.result.list.map((item: { uid1: string; name: string }) => ({
|
||||
label: item.uid1 + (item.name !== "" ? " (" + item.name + ")" : ""),
|
||||
value: item.uid1,
|
||||
}))
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err))
|
||||
}
|
||||
|
||||
function reqTerminalList(is_excel: boolean, biz_group_id: string, groupAccess: boolean) {
|
||||
if (!biz_group_id)
|
||||
return;
|
||||
@@ -124,17 +167,17 @@ export default function Terminal() {
|
||||
//
|
||||
// tree part //////////
|
||||
const handleClickGroupSearch = () => {
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (name == "" && biz_reg_num == "") {
|
||||
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (name != "" && name.length < 3) {
|
||||
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
|
||||
if (name != "" && name.length < 2) {
|
||||
alert("그룹(사업자)명은 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 3) {
|
||||
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 2) {
|
||||
alert("사업자등록번호는 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -163,7 +206,7 @@ export default function Terminal() {
|
||||
const handleClickGroupAccess = (value: boolean) => {
|
||||
setGroupAccess(value);
|
||||
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (selectedTreeKeys.length <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -247,19 +290,40 @@ export default function Terminal() {
|
||||
reqTerminalList(false, selectedTreeKeys[0] as string, isGroupAccess);
|
||||
};
|
||||
//
|
||||
const handleChangeGroupName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGroupName(e.target.value);
|
||||
changeListSelectedText(1, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeDeviceName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDeviceName(e.target.value);
|
||||
changeListSelectedText(0, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeUid1 = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUid1(e.target.value);
|
||||
changeListSelectedText(0, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeState = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setState(e.target.value);
|
||||
changeListSelectedText(2, e.target.options[e.target.selectedIndex].text);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (tokenPayload == undefined)
|
||||
return;
|
||||
|
||||
if (tokenPayload?.permission == 1)
|
||||
if (tokenPayload?.permission == 0xffffffff)
|
||||
return;
|
||||
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
setGroupAccess(tokenPayload?.permission == 1);
|
||||
setTreeOpen(tokenPayload?.permission == 1);
|
||||
reqUid1List();
|
||||
|
||||
reqTerminalList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
|
||||
setGroupAccess(tokenPayload?.permission == 0xffffffff);
|
||||
setTreeOpen(tokenPayload?.permission == 0xffffffff);
|
||||
|
||||
reqTerminalList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
});
|
||||
}, [tokenPayload]);
|
||||
|
||||
@@ -274,11 +338,13 @@ export default function Terminal() {
|
||||
{
|
||||
title: "등록일",
|
||||
key: "reg_time",
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time)
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "사업자",
|
||||
key: "biz_group_name",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "기기명",
|
||||
@@ -287,7 +353,8 @@ export default function Terminal() {
|
||||
{
|
||||
title: "단말기 TID",
|
||||
key: "uid1",
|
||||
renderItem: (item: any, row: number) => (item.uid1 + " (" + (item.type == 1 ? "KICC" : "NICE") +")")
|
||||
renderItem: (item: any, row: number) => (item.uid1 + " (" + (item.type == 1 ? "KICC" : "NICE") +")"),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "무인기기 장착 여부",
|
||||
@@ -335,7 +402,7 @@ export default function Terminal() {
|
||||
onChangeBizRegNum={setBizRegNum}
|
||||
onClickSearch={handleClickGroupSearch}
|
||||
isDisabled={isLoadingGlobal}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH_PX}
|
||||
>
|
||||
<CtTree
|
||||
treeData={treeData}
|
||||
@@ -349,33 +416,61 @@ export default function Terminal() {
|
||||
</BizGroupPanel>
|
||||
|
||||
<ComponentCard title="카드단말기 목록" titleIcon={<TableIcon />}>
|
||||
<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>
|
||||
|
||||
<CtDrawer title="상세 조회" isMobileMode={isMobileMode} setIsMobileMode={setIsMobileMode} isSearchFieldOpen={isSearchFieldOpen} setIsSearchFieldOpen={setIsSearchFieldOpen}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="그룹명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="그룹명 입력하세요." value={group_name} onChange={handleChangeGroupName} />
|
||||
</WithLabel>
|
||||
<WithLabel label="무인기기명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="무인기기명을 입력하세요." value={device_name} onChange={handleChangeDeviceName} />
|
||||
</WithLabel>
|
||||
<WithLabel label="단말기 TID" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<InputSelectField type="text" placeholder="TID를 입력하세요." value={uid1} options={list_uid1} onChange={handleChangeUid1} />
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label="기기장착여부" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Select
|
||||
placeholder="전체"
|
||||
options={[
|
||||
{ label: "장착중", value: "1" },
|
||||
{ label: "단말기만", value: "2" },
|
||||
]}
|
||||
defaultValue={state} onChange={handleChangeState}
|
||||
className="dark:bg-dark-900" />
|
||||
</WithLabel>
|
||||
</div>
|
||||
</div>
|
||||
</CtDrawer>
|
||||
|
||||
<div className="space-y-2 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
<Button size="sm" variant="outline" startIcon={<DocsIcon />} onClick={handleClickDownload}>엑셀파일 다운로드</Button>
|
||||
{ isMobileMode && <Button size="sm" variant="outline" startIcon={<FilterIcon />} onClick={() => setIsSearchFieldOpen(true)}>필터</Button> }
|
||||
<Button size="sm" variant="primary" onClick={handleClickSearch} disabled={isLoadingGlobal}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
{ isTreeOpen && <Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} label="그룹단위로 조회" /> }
|
||||
<PageCountSelector defaultValue={COUNT_PER_PAGE.toString()} onChange={(e) => setCOUNT_PER_PAGE(parseInt(e.target.value))} />
|
||||
</div>
|
||||
|
||||
<CtTable1 columns={columns} bodyData={tableData}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
"use client";
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import CtModal from "@/components/CtModal";
|
||||
import { useMultiState } from "@/hooks/useMultiState";
|
||||
import api, { convertDateTime6 } from '@/lib/_AG';
|
||||
import { toPng } from 'html-to-image';
|
||||
import Button from "@/components/ui/button/Button2";
|
||||
import { DocsIcon, DownloadIcon } from "@/icons";
|
||||
|
||||
|
||||
export interface ReceiptModalProps {
|
||||
order_time: string;
|
||||
goods_name: string;
|
||||
amount: number;
|
||||
pay_name: string;
|
||||
approval: string;
|
||||
biz_group_name: string;
|
||||
biz_reg_num: string;
|
||||
ceo: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
|
||||
interface ViewReceiptModalProps {
|
||||
multiState: ReturnType<typeof useMultiState<ReceiptModalProps>>;
|
||||
onOk: () => void;
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
export default function ReceiptModal({ multiState, onOk, isOpen, closeModal }: ViewReceiptModalProps) {
|
||||
const tax = multiState.values.amount / 10;
|
||||
const price = multiState.values.amount - tax;
|
||||
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (elementRef.current === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const dataUrl = await toPng(elementRef.current, { cacheBust: true });
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.download = 'my-custom-image.png';
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
} catch (error) {
|
||||
console.error('이미지 다운로드 중 오류 발생:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CtModal
|
||||
isOpen={isOpen}
|
||||
closeModal={closeModal}
|
||||
className="max-w-[500px] p-6 lg:p-10"
|
||||
>
|
||||
<div className="p-5" ref={elementRef}>
|
||||
<div className="receipt w-[80mm] bg-white text-gray-900 px-5 py-5 mx-auto border border-gray-300 shadow-md text-[13px] leading-relaxed">
|
||||
|
||||
<h2 className="text-center text-lg font-bold mb-1">[영수증]</h2>
|
||||
|
||||
<div className="text-center text-[11px] mb-5">
|
||||
상호: {multiState.values.biz_group_name}<br />
|
||||
주소: {multiState.values.address}<br />
|
||||
사업자등록번호: {multiState.values.biz_reg_num} | 대표자: {multiState.values.ceo}<br />
|
||||
TEL: {multiState.values.phone}<br />
|
||||
{convertDateTime6(multiState.values.order_time)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-dashed border-black my-2.5" />
|
||||
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="border-b border-black text-left pb-1">상품명</th>
|
||||
<th className="border-b border-black text-center pb-1">수량</th>
|
||||
<th className="border-b border-black text-right pb-1">금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="py-1">{multiState.values.goods_name ? multiState.values.goods_name : "물품"}</td>
|
||||
<td className="py-1 text-center">1</td>
|
||||
<td className="py-1 text-right">₩ {multiState.values.amount.toLocaleString("ko-KR")}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="border-t border-dashed border-black my-2.5" />
|
||||
|
||||
<table className="w-full border-collapse">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="py-1">합계 (총 1개)</td>
|
||||
<td className="py-1 text-right text-[15px] font-bold border-t-2 border-b-2 border-black">₩ {price.toLocaleString("ko-KR")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-1">부가세(10%)</td>
|
||||
<td className="py-1 text-right">₩ {tax}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-1 text-[15px] font-bold border-t-2 border-b-2 border-black">최종 결제금액</td>
|
||||
<td className="py-1 text-right text-[15px] font-bold border-t-2 border-b-2 border-black">₩ {multiState.values.amount.toLocaleString("ko-KR")}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="border-t border-dashed border-black my-2.5" />
|
||||
|
||||
<table className="w-full border-collapse">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="py-1">결제수단: 신용카드 (일시불)</td>
|
||||
<td className="py-1 text-right"></td>
|
||||
</tr>
|
||||
{/*<tr>
|
||||
<td className="py-1">카드번호: 1234-****-****-1234</td>
|
||||
<td className="py-1 text-right"></td>
|
||||
</tr>*/}
|
||||
<tr>
|
||||
<td className="py-1">카드사: {multiState.values.pay_name}</td>
|
||||
<td className="py-1 text-right"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-1">승인번호: {multiState.values.approval}</td>
|
||||
<td className="py-1 text-right"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="border-t border-dashed border-black my-2.5" />
|
||||
|
||||
<div className="text-center mt-5 text-[11px]">
|
||||
- 감사합니다. 또 방문해 주세요 -<br />
|
||||
(전자영수증은 앱에서 확인 가능합니다)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-5">
|
||||
<Button size="sm" variant="outline" startIcon={<DocsIcon />} onClick={() => window.print()}>인쇄</Button>
|
||||
<Button size="sm" variant="outline" startIcon={<DownloadIcon />} onClick={handleDownload}>다운로드</Button>
|
||||
</div>
|
||||
</CtModal>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +1,34 @@
|
||||
"use client"
|
||||
import PageBreadcrumb from "@/components/common/PageBreadCrumb2";
|
||||
import ComponentCard from '@/components/common/ComponentCard2';
|
||||
import BizGroupPanel from "@/components/common/BizGroupPanel";
|
||||
import BizGroupPanel from "@/components/BizGroupPanel";
|
||||
import CtTable1 from '@/components/tables/CtTable1';
|
||||
import Button from '@/components/ui/button/Button2';
|
||||
import { DocsIcon, TableIcon, PlusIcon, TrashBinIcon, InfoIcon } from "@/icons";
|
||||
import { DocsIcon, TableIcon, PlusIcon, TrashBinIcon, InfoIcon, FilterIcon } from "@/icons";
|
||||
import WithLabel from '@/components/form/WithLabel';
|
||||
import Input from '@/components/form/input/InputField2';
|
||||
import DatePicker from '@/components/form/date-picker2';
|
||||
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import api, { convertDateTime3, convertDateTime4, convertDateTime6, postFileDownload, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, convertDateTime4, convertDateTime6, postFileDownload, todayYMD, useAuthStore, useUiLoadingStore } from '@/lib/_AG';
|
||||
import api2 from '@/lib/_AG';
|
||||
import CtTree, { CtTreeNode, findNodeByKey, findRootNodeByKey } from "@/components/CtTree";
|
||||
import { reqBizGroupTree2, reqBizGroupTree3 } from "../biz-group/BizGroupContent";
|
||||
import Checkbox from "@/components/form/input/Checkbox";
|
||||
import Badge from "@/components/ui/badge/Badge";
|
||||
import Select from "@/components/form/Select2";
|
||||
import CtSelectedTextField from "@/components/CtSelectedText";
|
||||
import CtDrawer from "@/components/CtDrawer";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import { useMultiState } from "@/hooks/useMultiState";
|
||||
import ReceiptModal, { ReceiptModalProps } from "./ReceiptModal";
|
||||
import PageCountSelector from "@/components/PageCountSelector";
|
||||
import InputSelectField from "@/components/form/input/InputSelectField";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
//const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -29,35 +36,75 @@ export default function Transactions() {
|
||||
const tokenPayload = useAuthStore((s) => s.tokenPayload); //Bruce, JWT
|
||||
const isLoadingGlobal = useUiLoadingStore((s) => s.isLoading);
|
||||
|
||||
const [ sumData, setSumData ] = useState<any[]>([]);
|
||||
const [ tableData, setTableData ] = useState<any[]>([]);
|
||||
const [ tableOffset, setTableOffset ] = useState<number>(0);
|
||||
const [ tableTotalCount, setTableTotalCount ] = useState<number>(0);
|
||||
|
||||
const [ COUNT_PER_PAGE, setCOUNT_PER_PAGE ] = useState<number>(20);
|
||||
|
||||
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
|
||||
const [ isGroupAccess, setGroupAccess ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
const [ isTreeOpen, setTreeOpen ] = useState(tokenPayload?.permission == 0xffffffff); //TempCode
|
||||
|
||||
// group search fields
|
||||
const [ name, setName ] = useState<string>("");
|
||||
const [ biz_reg_num, setBizRegNum ] = useState<string>("");
|
||||
|
||||
//search fields
|
||||
const [ device_name, setDeviceName ] = useState<string>("");
|
||||
const [ uid1, setUid1 ] = useState<string>("");
|
||||
const [ list_uid1, setListUid1 ] = useState<{label: string; value: string}[]>([]);
|
||||
const [ amount, setAmount ] = useState<string>("");
|
||||
const [ type, setType ] = useState<string>("");
|
||||
const [ approval, setApproval ] = useState<string>("");
|
||||
const [ slot, setSlot ] = useState<string>("");
|
||||
const [ column_no, setSlot ] = useState<string>("");
|
||||
const [ code, setCode ] = useState<string>("");
|
||||
const [ goods_name, setGoodsName ] = useState<string>("");
|
||||
const [ pay_vendor, setPayVendor ] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>("");
|
||||
const [ date_end, setDateEnd ] = useState<string>("");
|
||||
const [ pay_name, setPayName ] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>(addDaysYMD(-7));
|
||||
const [ date_end, setDateEnd ] = useState<string>(todayYMD());
|
||||
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
const modalData = useMultiState<ReceiptModalProps>({order_time: "", goods_name: "", amount: 0, pay_name: "", approval: "", biz_group_name: "", biz_reg_num: "", ceo: "", phone: "", address: ""});
|
||||
|
||||
const [ isMobileMode, setIsMobileMode ] = useState<boolean>(false);
|
||||
const [ isSearchFieldOpen, setIsSearchFieldOpen ] = useState<boolean>(false);
|
||||
|
||||
const [ listSelectedText, setListSelectedText ] = useState<string[]>([]);
|
||||
const changeListSelectedText = (index: number, text: string) => {
|
||||
setListSelectedText(prev => {
|
||||
const copy = [...prev];
|
||||
copy[index] = text;
|
||||
return copy;
|
||||
});
|
||||
}
|
||||
|
||||
//function part
|
||||
//
|
||||
function reqUid1List() {
|
||||
api.post('/api/list-uid1.do').then((resp) => {
|
||||
console.log(resp);
|
||||
|
||||
if (resp.data.errCode == 0) {
|
||||
if (resp.data.result.list.length == 0) {
|
||||
setListUid1([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setListUid1(
|
||||
resp.data.result.list.map((item: { uid1: string; name: string }) => ({
|
||||
label: item.uid1 + (item.name !== "" ? " (" + item.name + ")" : ""),
|
||||
value: item.uid1,
|
||||
}))
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err))
|
||||
}
|
||||
|
||||
function reqTransactionList(is_excel: boolean, biz_group_id: string, groupAccess: boolean) {
|
||||
if (!biz_group_id)
|
||||
return;
|
||||
@@ -67,14 +114,15 @@ export default function Transactions() {
|
||||
is_excel: is_excel,
|
||||
offset: tableOffset,
|
||||
limit: COUNT_PER_PAGE,
|
||||
device_name: device_name,
|
||||
uid1: uid1,
|
||||
amount: amount,
|
||||
type: type,
|
||||
approval: approval,
|
||||
slot: slot,
|
||||
column_no: column_no,
|
||||
code: code,
|
||||
goods_name: goods_name,
|
||||
pay_vendor: pay_vendor,
|
||||
pay_name: pay_name,
|
||||
date_start: date_start,
|
||||
date_end: date_end,
|
||||
biz_group_id: biz_group_id,
|
||||
@@ -110,17 +158,17 @@ export default function Transactions() {
|
||||
//
|
||||
// tree part //////////
|
||||
const handleClickGroupSearch = () => {
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (name == "" && biz_reg_num == "") {
|
||||
alert("그룹명(사업자명) 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
alert("그룹(사업자)명 또는 사업자등록번호를 입력하셔야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (name != "" && name.length < 3) {
|
||||
alert("그룹명(사업자명)은 세글자 이상 입력해야 합니다.");
|
||||
if (name != "" && name.length < 2) {
|
||||
alert("그룹(사업자)명은 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 3) {
|
||||
alert("사업자등록번호는 세글자 이상 입력해야 합니다.");
|
||||
if (biz_reg_num != "" && biz_reg_num.length < 2) {
|
||||
alert("사업자등록번호는 두글자 이상 입력해야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,7 +176,9 @@ export default function Transactions() {
|
||||
}
|
||||
else {
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
//reqTransactionList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
|
||||
reqUid1List();
|
||||
|
||||
//reqTransactionList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
reqTransactionList(false, String(resp.data.result.tree[0].key), isGroupAccess);
|
||||
});
|
||||
}
|
||||
@@ -154,7 +204,7 @@ export default function Transactions() {
|
||||
const handleClickGroupAccess = (value: boolean) => {
|
||||
setGroupAccess(value);
|
||||
|
||||
if (tokenPayload?.permission == 1) {
|
||||
if (tokenPayload?.permission == 0xffffffff) {
|
||||
if (selectedTreeKeys.length <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -189,7 +239,25 @@ export default function Transactions() {
|
||||
setTableOffset((page - 1) * COUNT_PER_PAGE);
|
||||
};
|
||||
|
||||
const handleRowClickReceipt = (row: number) => {
|
||||
const node = findRootNodeByKey(treeData, String(tokenPayload?.biz_group_id));
|
||||
modalData.setAll({...tableData[row],
|
||||
biz_group_name: node.name,
|
||||
biz_reg_num: node.biz_reg_num,
|
||||
ceo: node.ceo,
|
||||
phone: node.phone,
|
||||
address: node.address,
|
||||
});
|
||||
openModal();
|
||||
};
|
||||
|
||||
const handleRowClickDelete = (row: number) => {
|
||||
const permission = tokenPayload?.permission ?? 0;
|
||||
if ((permission & 0x8) !== 0x8) {
|
||||
alert("권한이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (tableData[row].type != "D1" && tableData[row].type != "I1" &&
|
||||
tableData[row].type != "TM1" && tableData[row].type != "EB1") {
|
||||
alert("이미 취소 처리된 항목입니다.");
|
||||
@@ -221,21 +289,72 @@ export default function Transactions() {
|
||||
});
|
||||
}
|
||||
};
|
||||
//
|
||||
|
||||
const handleModalOk = () => {
|
||||
closeModal();
|
||||
|
||||
}
|
||||
//
|
||||
const handleChangeDeviceName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDeviceName(e.target.value);
|
||||
changeListSelectedText(0, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeUid1 = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUid1(e.target.value);
|
||||
changeListSelectedText(0, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeAmount = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAmount(e.target.value);
|
||||
changeListSelectedText(1, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeType = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setType(e.target.value);
|
||||
changeListSelectedText(2, e.target.options[e.target.selectedIndex].text);
|
||||
}
|
||||
|
||||
const handleChangeApproval = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setApproval(e.target.value);
|
||||
changeListSelectedText(3, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeSlot = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSlot(e.target.value);
|
||||
changeListSelectedText(4, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeCode = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setCode(e.target.value);
|
||||
changeListSelectedText(5, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangeGoodsName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGoodsName(e.target.value);
|
||||
changeListSelectedText(6, e.target.value);
|
||||
}
|
||||
|
||||
const handleChangePayName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPayName(e.target.value);
|
||||
changeListSelectedText(7, e.target.value);
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (tokenPayload == undefined)
|
||||
return;
|
||||
|
||||
if (tokenPayload?.permission == 1)
|
||||
if (tokenPayload?.permission == 0xffffffff)
|
||||
return;
|
||||
|
||||
reqBizGroupTree3(String(tokenPayload?.biz_group_id), setTreeData, setSelectedTreeKeys, setExpandedTreeKeys, (resp: any) => {
|
||||
setGroupAccess(tokenPayload?.permission == 1);
|
||||
setTreeOpen(tokenPayload?.permission == 1);
|
||||
reqUid1List();
|
||||
|
||||
reqTransactionList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 1);
|
||||
setGroupAccess(tokenPayload?.permission == 0xffffffff);
|
||||
setTreeOpen(tokenPayload?.permission == 0xffffffff);
|
||||
|
||||
reqTransactionList(false, String(resp.data.result.tree[0].key), tokenPayload?.permission == 0xffffffff);
|
||||
});
|
||||
}, [tokenPayload]);
|
||||
|
||||
@@ -255,22 +374,23 @@ export default function Transactions() {
|
||||
{
|
||||
title: "거래시간",
|
||||
key: "order_time",
|
||||
renderItem: (item: any, row: number) => convertDateTime6(item.order_time)
|
||||
renderItem: (item: any, row: number) => convertDateTime6(item.order_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
/*
|
||||
{
|
||||
title: "무인기기명",
|
||||
key: "device_name",
|
||||
},
|
||||
*/
|
||||
{
|
||||
title: "단말기 TID",
|
||||
key: "uid1",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "금액",
|
||||
key: "amount",
|
||||
renderItem: (item : any) => (item.amount.toLocaleString("ko-KR") + "원")
|
||||
renderItem: (item : any) => (item.amount.toLocaleString("ko-KR") + "원"),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "결제유형",
|
||||
@@ -304,7 +424,7 @@ export default function Transactions() {
|
||||
},
|
||||
{
|
||||
title: "컬럼번호",
|
||||
key: "slot",
|
||||
key: "column_no",
|
||||
},
|
||||
{
|
||||
title: "상품코드",
|
||||
@@ -315,31 +435,61 @@ export default function Transactions() {
|
||||
key: "goods_name",
|
||||
},
|
||||
{
|
||||
title: "결제서비스업체",
|
||||
key: "pay_vendor",
|
||||
title: "결제카드",
|
||||
key: "pay_name",
|
||||
renderItem: (item : any) => (
|
||||
item.type === "TM1" ? "티머니" :
|
||||
item.type === "TM4" ? "티머니" :
|
||||
item.type === "EB1" ? "캐시비" :
|
||||
item.type === "EB4" ? "캐시비" :
|
||||
item.pay_vendor
|
||||
item.pay_name
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "영수증",
|
||||
key: "_",
|
||||
renderItem: (item: any, row: number) => (
|
||||
item.type == "D1" ?
|
||||
<div>
|
||||
<DocsIcon
|
||||
className="cursor-pointer hover:fill-error-500 dark:hover:fill-error-500 fill-gray-700 dark:fill-gray-400"
|
||||
onClick={() => {handleRowClickReceipt(row)}} />
|
||||
</div>
|
||||
: ""
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "취소요청",
|
||||
key: "_",
|
||||
renderItem: (item: any, row: number) => (
|
||||
item.type == "D1" ?
|
||||
<div>
|
||||
/*<div>
|
||||
<InfoIcon
|
||||
className="cursor-pointer hover:fill-error-500 dark:hover:fill-error-500 fill-gray-700 dark:fill-gray-400"
|
||||
onClick={() => {handleRowClickDelete(row)}} />
|
||||
</div>
|
||||
<Badge size="sm" color={"error"} >결제취소</Badge>
|
||||
<div className="px-4 py-1.5 text-start text-theme-xs text-gray-600 dark:text-gray-400 transition-colors">
|
||||
<span className="inline-flex items-center py-0.5 justify-center gap-1 rounded-full font-medium text-theme-xs bg-error-50 text-error-600 dark:bg-error-500/15 dark:text-error-500">결제취소</span>
|
||||
</div>*/
|
||||
<button className="inline-flex items-center px-1 py-0.5 justify-center gap-1 rounded font-medium text-theme-xs bg-error-50 text-error-600 dark:bg-error-500/15 dark:text-error-500"
|
||||
onClick={() => {handleRowClickDelete(row)}}>
|
||||
결제취소
|
||||
</button>
|
||||
: ""
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const sumColumns = [
|
||||
{
|
||||
title: "결제 금액 합계",
|
||||
key: "amount",
|
||||
renderItem: (item : any) => (item.amount.toLocaleString("ko-KR") + "원"),
|
||||
viewMobile: true,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle1="매출 및 통계" pageTitle2="거래내역 조회" />
|
||||
@@ -355,7 +505,7 @@ export default function Transactions() {
|
||||
onChangeBizRegNum={setBizRegNum}
|
||||
onClickSearch={handleClickGroupSearch}
|
||||
isDisabled={isLoadingGlobal}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH}
|
||||
searchLabelWidth={SEARCH_LABEL_WIDTH_PX}
|
||||
>
|
||||
<CtTree
|
||||
treeData={treeData}
|
||||
@@ -369,83 +519,96 @@ export default function Transactions() {
|
||||
</BizGroupPanel>
|
||||
|
||||
<ComponentCard title="거래내역 목록" titleIcon={<TableIcon />}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="단말기 TID" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="단말기 TID를 입력하세요." value={uid1} onChange={(e) => setUid1(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="금액" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="금액을 입력하세요." value={amount} onChange={(e) => setAmount(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="결제유형" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Select
|
||||
placeholder="선택하세요"
|
||||
options={[
|
||||
{ label: "신용승인", value: "D1" },
|
||||
{ label: "신용취소", value: "D4" },
|
||||
{ label: "페이승인", value: "I1" },
|
||||
{ label: "페이취소", value: "I4" },
|
||||
{ label: "현금", value: "B1" },
|
||||
{ label: "티머니승인", value: "TM1" },
|
||||
{ label: "캐니비승인", value: "EB1" },
|
||||
]}
|
||||
defaultValue={type} onChange={(value) => setType(value)}
|
||||
className="dark:bg-dark-900" />
|
||||
</WithLabel>
|
||||
<WithLabel label="승인번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="승인번호를 입력하세요." value={approval} onChange={(e) => setApproval(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="컬럼번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="컬럼번호를 입력하세요." value={slot} onChange={(e) => setSlot(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="상품코드" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="상품코드를 입력하세요." value={code} onChange={(e) => setCode(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="상품명" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="상품명을 입력하세요." value={goods_name} onChange={(e) => setGoodsName(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="결제업체" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="결제업체를 입력하세요." value={pay_vendor} onChange={(e) => setPayVendor(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="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">
|
||||
{ isTreeOpen && <>
|
||||
<Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} />
|
||||
<span className="block font-normal text-gray-700 text-theme-sm dark:text-gray-400">
|
||||
그룹단위로 조회
|
||||
</span>
|
||||
</>}
|
||||
|
||||
<CtDrawer title="상세 조회" isMobileMode={isMobileMode} setIsMobileMode={setIsMobileMode} isSearchFieldOpen={isSearchFieldOpen} setIsSearchFieldOpen={setIsSearchFieldOpen}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-5">
|
||||
<WithLabel label="무인기기명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="무인기기명을 입력하세요." value={device_name} onChange={handleChangeDeviceName} />
|
||||
</WithLabel>
|
||||
<WithLabel label="단말기 TID" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<InputSelectField type="text" placeholder="TID를 입력하세요." value={uid1} options={list_uid1} onChange={handleChangeUid1} />
|
||||
</WithLabel>
|
||||
<WithLabel label="금액" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="number" placeholder="금액을 입력하세요." value={amount} onChange={handleChangeAmount} />
|
||||
</WithLabel>
|
||||
<WithLabel label="결제유형" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Select
|
||||
placeholder="전체"
|
||||
options={[
|
||||
{ label: "신용승인", value: "D1" },
|
||||
{ label: "신용취소", value: "D4" },
|
||||
{ label: "페이승인", value: "I1" },
|
||||
{ label: "페이취소", value: "I4" },
|
||||
{ label: "현금", value: "B1" },
|
||||
{ label: "티머니승인", value: "TM1" },
|
||||
{ label: "캐니비승인", value: "EB1" },
|
||||
]}
|
||||
defaultValue={type} onChange={handleChangeType}
|
||||
className="dark:bg-dark-900" />
|
||||
</WithLabel>
|
||||
<WithLabel label="승인번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="승인번호를 입력하세요." value={approval} onChange={handleChangeApproval} />
|
||||
</WithLabel>
|
||||
</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 className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="컬럼번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="컬럼번호를 입력하세요." value={column_no} onChange={handleChangeSlot} />
|
||||
</WithLabel>
|
||||
<WithLabel label="상품코드" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="상품코드를 입력하세요." value={code} onChange={handleChangeCode} />
|
||||
</WithLabel>
|
||||
<WithLabel label="상품명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="상품명을 입력하세요." value={goods_name} onChange={handleChangeGoodsName} />
|
||||
</WithLabel>
|
||||
<WithLabel label="결제카드" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="결제업체를 입력하세요." value={pay_name} onChange={handleChangePayName} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
</div>
|
||||
</CtDrawer>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
<Button size="sm" variant="outline" startIcon={<DocsIcon />} onClick={handleClickDownload}>엑셀파일 다운로드</Button>
|
||||
{ isMobileMode && <Button size="sm" variant="outline" startIcon={<FilterIcon />} onClick={() => setIsSearchFieldOpen(true)}>필터</Button> }
|
||||
<Button size="sm" variant="primary" onClick={handleClickSearch} disabled={isLoadingGlobal}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CtTable1 columns={columns} bodyData={tableData}
|
||||
<div className="flex items-center justify-end gap-5">
|
||||
{ isTreeOpen && <Checkbox checked={isGroupAccess} onChange={(value) => handleClickGroupAccess(value)} label="그룹단위로 조회" /> }
|
||||
<PageCountSelector defaultValue={COUNT_PER_PAGE.toString()} onChange={(e) => setCOUNT_PER_PAGE(parseInt(e.target.value))} />
|
||||
</div>
|
||||
|
||||
{ isMobileMode &&
|
||||
<CtSelectedTextField label={"필터:"} listText={listSelectedText} setListText={setListSelectedText}></CtSelectedTextField>
|
||||
}
|
||||
|
||||
<CtTable1 columns={columns} sumColumns={sumColumns} sumData={sumData} bodyData={tableData}
|
||||
offset={tableOffset} totalCount={tableTotalCount} countPerPage={COUNT_PER_PAGE} pageNumCount={PAGE_NUM_COUNT}
|
||||
onClickPageChange={handlePageChange} />
|
||||
|
||||
</ComponentCard>
|
||||
|
||||
<ReceiptModal multiState={modalData} onOk={handleModalOk} isOpen={isOpen} closeModal={closeModal} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import DatePicker from '@/components/form/date-picker2';
|
||||
|
||||
import React, { Key } from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import api, { convertDateTime3, convertDateTime4, convertDateTime6, useAuthStore } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, convertDateTime4, convertDateTime6, todayYMD, useAuthStore } from '@/lib/_AG';
|
||||
import api2 from '@/lib/_AG';
|
||||
import CtTree, { CtTreeNode, findNodeByKey } from "@/components/CtTree";
|
||||
import { reqBizGroupTree2, reqBizGroupTree3 } from "../biz-group/BizGroupContent";
|
||||
@@ -19,7 +19,7 @@ import Badge from "@/components/ui/badge/Badge";
|
||||
import Select from "@/components/form/Select2";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
@@ -36,10 +36,10 @@ export default function UndefinedTransactions() {
|
||||
const [ amount, setAmount ] = useState<string>("");
|
||||
const [ type, setType ] = useState<string>("");
|
||||
const [ approval, setApproval ] = useState<string>("");
|
||||
const [ slot, setSlot ] = useState<string>("");
|
||||
const [ column_no, setSlot ] = useState<string>("");
|
||||
const [ code, setCode ] = useState<string>("");
|
||||
const [ goods_name, setGoodsName ] = useState<string>("");
|
||||
const [ pay_vendor, setPayVendor ] = useState<string>("");
|
||||
const [ pay_name, setPayName ] = useState<string>("");
|
||||
const [ date_start, setDateStart ] = useState<string>("");
|
||||
const [ date_end, setDateEnd ] = useState<string>("");
|
||||
|
||||
@@ -54,10 +54,10 @@ export default function UndefinedTransactions() {
|
||||
amount: amount,
|
||||
type: type,
|
||||
approval: approval,
|
||||
slot: slot,
|
||||
column_no: column_no,
|
||||
code: code,
|
||||
goods_name: goods_name,
|
||||
pay_vendor: pay_vendor,
|
||||
pay_name: pay_name,
|
||||
date_start: date_start,
|
||||
date_end: date_end,
|
||||
}
|
||||
@@ -152,7 +152,8 @@ export default function UndefinedTransactions() {
|
||||
{
|
||||
title: "거래시간",
|
||||
key: "order_time",
|
||||
renderItem: (item: any, row: number) => convertDateTime6(item.order_time)
|
||||
renderItem: (item: any, row: number) => convertDateTime6(item.order_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
/*
|
||||
{
|
||||
@@ -163,11 +164,13 @@ export default function UndefinedTransactions() {
|
||||
{
|
||||
title: "단말기 TID",
|
||||
key: "uid1",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "금액",
|
||||
key: "amount",
|
||||
renderItem: (item : any) => (item.amount.toLocaleString("ko-KR") + "원")
|
||||
renderItem: (item : any) => (item.amount.toLocaleString("ko-KR") + "원"),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "결제유형",
|
||||
@@ -200,7 +203,7 @@ export default function UndefinedTransactions() {
|
||||
},
|
||||
{
|
||||
title: "컬럼번호",
|
||||
key: "slot",
|
||||
key: "column_no",
|
||||
},
|
||||
{
|
||||
title: "상품코드",
|
||||
@@ -211,14 +214,14 @@ export default function UndefinedTransactions() {
|
||||
key: "goods_name",
|
||||
},
|
||||
{
|
||||
title: "결제서비스업체",
|
||||
key: "pay_vendor",
|
||||
title: "결제카드",
|
||||
key: "pay_name",
|
||||
renderItem: (item : any) => (
|
||||
item.type === "TM1" ? "티머니" :
|
||||
item.type === "TM4" ? "티머니" :
|
||||
item.type === "EB1" ? "캐시비" :
|
||||
item.type === "EB4" ? "캐시비" :
|
||||
item.pay_vendor
|
||||
item.pay_name
|
||||
)
|
||||
},
|
||||
{
|
||||
@@ -245,13 +248,13 @@ export default function UndefinedTransactions() {
|
||||
<ComponentCard title="거래내역 목록" titleIcon={<TableIcon />}>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="단말기 TID" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="단말기 TID" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="단말기 TID를 입력하세요." value={uid1} onChange={(e) => setUid1(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="금액" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="금액" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="금액을 입력하세요." value={amount} onChange={(e) => setAmount(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="결제유형" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="결제유형" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Select
|
||||
placeholder="선택하세요"
|
||||
options={[
|
||||
@@ -263,39 +266,41 @@ export default function UndefinedTransactions() {
|
||||
{ label: "티머니승인", value: "TM1" },
|
||||
{ label: "캐니비승인", value: "EB1" },
|
||||
]}
|
||||
defaultValue={type} onChange={(value) => setType(value)}
|
||||
defaultValue={type} onChange={(e) => setType(e.target.value)}
|
||||
className="dark:bg-dark-900" />
|
||||
</WithLabel>
|
||||
<WithLabel label="승인번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="승인번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="승인번호를 입력하세요." value={approval} onChange={(e) => setApproval(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-4">
|
||||
<WithLabel label="컬럼번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="컬럼번호를 입력하세요." value={slot} onChange={(e) => setSlot(e.target.value)} />
|
||||
<WithLabel label="컬럼번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="컬럼번호를 입력하세요." value={column_no} onChange={(e) => setSlot(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="상품코드" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="상품코드" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="상품코드를 입력하세요." value={code} onChange={(e) => setCode(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="상품명" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="상품명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="상품명을 입력하세요." value={goods_name} onChange={(e) => setGoodsName(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="결제업체" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<Input type="text" placeholder="결제업체를 입력하세요." value={pay_vendor} onChange={(e) => setPayVendor(e.target.value)} />
|
||||
<WithLabel label="결제업체" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="결제카드를 입력하세요." value={pay_name} onChange={(e) => setPayName(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="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">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
|
||||
@@ -12,14 +12,14 @@ import Badge from "@/components/ui/badge/Badge";
|
||||
import React from "react";
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from "next/navigation";
|
||||
import api, { convertDateTime3 } from '@/lib/_AG';
|
||||
import api, { addDaysYMD, convertDateTime3, todayYMD } from '@/lib/_AG';
|
||||
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import AddModifyVocModal, {VocModalProps} from './AddModifyVocModal';
|
||||
import { useMultiState } from "@/hooks/useMultiState";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
@@ -115,7 +115,8 @@ export default function Voc() {
|
||||
{
|
||||
title: "등록일",
|
||||
key: "reg_time",
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time)
|
||||
renderItem: (item: any, row: number) => convertDateTime3(item.reg_time),
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "수정일",
|
||||
@@ -132,6 +133,7 @@ export default function Voc() {
|
||||
{
|
||||
title: "제목",
|
||||
key: "title",
|
||||
viewMobile: true,
|
||||
},
|
||||
{
|
||||
title: "고객명",
|
||||
@@ -182,28 +184,30 @@ export default function Voc() {
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<WithLabel label="제목" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="제목" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="제목을 입력하세요." value={title} onChange={(e) => setTitle(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="고객명" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="고객명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 고객명을 입력하세요." value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</WithLabel>
|
||||
<WithLabel label="작성자 아이디" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="작성자 아이디" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 작성자 아이디를 입력하세요." value={uid1} onChange={(e) => setUid1(e.target.value)} />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
onChange={(dates, currentDateString) => { setDateStart(currentDateString); }}
|
||||
defaultDate={date_start}
|
||||
/>
|
||||
<DatePicker
|
||||
id="date_end-picker"
|
||||
placeholder="조회 종료일"
|
||||
onChange={(dates, currentDateString) => { setDateEnd(currentDateString); }}
|
||||
defaultDate={date_end}
|
||||
/>
|
||||
</div>
|
||||
</WithLabel>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useForm } from 'react-hook-form';
|
||||
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useModal } from "@/hooks/useModal";
|
||||
import AddTest2ViewModal from './AddTest2ViewModal';
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
@@ -128,8 +128,8 @@ export default function Test2View() {
|
||||
{ value: "error", label: "장애" },
|
||||
];
|
||||
|
||||
const handleSelectChange = (value: string) => {
|
||||
console.log("Selected value:", value);
|
||||
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
console.log("Selected value:", e.target.value);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
|
||||
@@ -19,7 +19,7 @@ import AddTest2ViewModal from './AddTest2ViewModal';
|
||||
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
@@ -123,8 +123,8 @@ export default function DeviceStatus() {
|
||||
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
|
||||
const handleSelectChange = (value: string) => {
|
||||
console.log("Selected value:", value);
|
||||
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
console.log("Selected value:", e.target.value);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function AddModifyGroupModal({ isOpen, openModal, closeModal }: A
|
||||
<div className="mt-8"></div>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<Label>그룹명(사업자명)</Label>
|
||||
<Label>그룹(사업자)명</Label>
|
||||
<Input type="text" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -18,7 +18,7 @@ import AddModifyGroupModal from './AddModifyGroupModal';
|
||||
import CtTree, { expandAndSelectTreeNode, getAllNodeKeys, findParentKeys, findNodeByKey, deleteNodeByKey } from "@/components/CtTree";
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
const COUNT_PER_PAGE = 10;
|
||||
const PAGE_NUM_COUNT = 5;
|
||||
|
||||
@@ -30,7 +30,7 @@ const columns = [
|
||||
key: "reg_time" ,
|
||||
},
|
||||
{
|
||||
title: "그룹명(사업자명)",
|
||||
title: "그룹(사업자)명",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
@@ -298,7 +298,7 @@ export default function BizGroup() {
|
||||
};
|
||||
|
||||
|
||||
const SEARCH_LABEL_WIDTH = 24;
|
||||
const SEARCH_LABEL_WIDTH_PX = 80;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -309,27 +309,27 @@ export default function BizGroup() {
|
||||
|
||||
<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="조회할 그룹명(사업자명)을 입력하세요." />
|
||||
<WithLabel label="그룹(사업자)명" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 그룹(사업자)명을 입력하세요." />
|
||||
</WithLabel>
|
||||
<WithLabel label="사업자번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="사업자번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 사업자번호를 입력하세요." />
|
||||
</WithLabel>
|
||||
<WithLabel label="이메일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="이메일" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 이메일를 입력하세요." />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<WithLabel label="전화번호" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="전화번호" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 전화번호를 입력하세요." />
|
||||
</WithLabel>
|
||||
<WithLabel label="주소" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<WithLabel label="주소" label_width={SEARCH_LABEL_WIDTH_PX}>
|
||||
<Input type="text" placeholder="조회할 주소를 입력하세요." />
|
||||
</WithLabel>
|
||||
</div>
|
||||
<div className="space-y-2 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={SEARCH_LABEL_WIDTH}>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="등록일" label_width={50}>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-2">
|
||||
<DatePicker
|
||||
id="date_start-picker"
|
||||
placeholder="조회 시작일"
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
--breakpoint-lg: 1024px;
|
||||
--breakpoint-xl: 1280px;
|
||||
--breakpoint-2xl: 1536px;
|
||||
--text-sm--line-height: 18px;
|
||||
--text-base: 14px;
|
||||
*/
|
||||
--text-sm: 13px;
|
||||
|
||||
|
||||
--breakpoint-*: initial;
|
||||
--breakpoint-2xsm: 640px;
|
||||
@@ -200,7 +204,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
body {
|
||||
@apply relative font-normal font-outfit z-1 bg-gray-50;
|
||||
@apply relative font-normal font-outfit z-1 bg-gray-50 dark:bg-gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import { Outfit } from 'next/font/google';
|
||||
//import { Outfit } from 'next/font/google';
|
||||
import localFont from 'next/font/local';
|
||||
import './globals.css';
|
||||
|
||||
import { SidebarProvider } from '@/context/SidebarContext';
|
||||
import { ThemeProvider } from '@/context/ThemeContext';
|
||||
import CustomAlertProvider from '@/components/common/CustomAlertProvider';
|
||||
|
||||
const outfit = Outfit({
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
//const outfit = Outfit({
|
||||
// subsets: ["latin"],
|
||||
//});
|
||||
const pretendard = localFont({
|
||||
src: '../../public/fonts/PretendardVariable.woff2',
|
||||
display: 'swap',
|
||||
weight: '45 920',
|
||||
})
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
@@ -15,8 +22,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${outfit.className} dark:bg-gray-900`}>
|
||||
<html lang="ko" className={pretendard.className}>
|
||||
<body>
|
||||
<ThemeProvider>
|
||||
<SidebarProvider>
|
||||
<CustomAlertProvider>{children}</CustomAlertProvider>
|
||||
|
||||
+4
-3
@@ -47,12 +47,13 @@ export default function BizGroupPanel({
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<ComponentCard>
|
||||
<div className="flex-1 space-y-6">
|
||||
<div className="flex-1 space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<WithLabel label="그룹명(사업자명)" label_width={searchLabelWidth}>
|
||||
{/*<WithLabel label="그룹(사업자)명" label_width={searchLabelWidth}>*/}
|
||||
<WithLabel label="그룹(사업자)명" label_width={90}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="조회할 그룹명(사업자명) 입력하세요."
|
||||
placeholder="조회할 그룹(사업자)명 입력하세요."
|
||||
value={name}
|
||||
onChange={(e) => onChangeName(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { TableIcon } from "@/icons/index";
|
||||
|
||||
|
||||
interface CtDrawerProps {
|
||||
title?: string;
|
||||
isMobileMode: boolean;
|
||||
setIsMobileMode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isSearchFieldOpen: boolean;
|
||||
setIsSearchFieldOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const CtDrawer: React.FC<CtDrawerProps> = ({
|
||||
title,
|
||||
isMobileMode,
|
||||
setIsMobileMode,
|
||||
isSearchFieldOpen,
|
||||
setIsSearchFieldOpen,
|
||||
children,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setIsMobileMode(window.innerWidth < 768);
|
||||
//console.log("isMobileMode = ", isMobileMode);
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
if (!isMobileMode)
|
||||
return children;
|
||||
|
||||
//if (isSearchFieldOpen)
|
||||
return (<>
|
||||
{/*<div className="fixed inset-0 bg-black/30 bg-opacity-50 z-100000 transition-opacity" aria-hidden="true" />
|
||||
<div className="fixed inset-y-0 right-0 w-90 bg-white shadow-lg p-4 z-100001 transition-transform duration-300 overflow-y-auto overscroll-contain md:hidden">*/}
|
||||
<div
|
||||
className={`fixed inset-0 bg-black/30 z-100000 transition-opacity duration-300 ease-in-out
|
||||
${isSearchFieldOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`}
|
||||
aria-hidden="true"
|
||||
onClick={() => setIsSearchFieldOpen(false)}
|
||||
/>
|
||||
<div
|
||||
className={`fixed inset-y-0 right-0 w-90 bg-white dark:bg-gray-900 shadow-lg p-4 z-100001 transition-transform duration-300 ease-in-out overflow-y-auto overscroll-contain md:hidden
|
||||
${isSearchFieldOpen ? 'translate-x-0' : 'translate-x-full'}`}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-lg font-semibold text-gray-800 dark:text-white/90">{title}</span>
|
||||
<button onClick={() => setIsSearchFieldOpen(false)} className="w-6 h-6 border border-gray-400 dark:border-gray-700 rounded flex items-center justify-center text-gray-600 dark:text-gray-400">✕</button>
|
||||
</div>
|
||||
<div className="border-b border-gray-300 dark:border-gray-800 mt-2 mb-4"></div>
|
||||
{children}
|
||||
</div>
|
||||
</>);
|
||||
|
||||
//return "";
|
||||
};
|
||||
|
||||
export default CtDrawer;
|
||||
@@ -6,7 +6,7 @@ import Input from '@/components/form/input/InputField2';
|
||||
|
||||
|
||||
interface CtModalProps {
|
||||
title: string,
|
||||
title?: string,
|
||||
description?: string,
|
||||
children: ReactNode;
|
||||
okBtnText?: string;
|
||||
@@ -28,7 +28,7 @@ export default function CtModal({ title, description, children, okBtnText, onCli
|
||||
<div className="flex flex-col px-2 overflow-y-auto custom-scrollbar">
|
||||
<div>
|
||||
<h5 className="mb-2 font-semibold text-gray-800 modal-title text-theme-xl dark:text-white/90 lg:text-2xl">
|
||||
{title}
|
||||
{title ? title : ""}
|
||||
</h5>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{description}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Label from "./form/Label2";
|
||||
|
||||
|
||||
|
||||
const CtSelectedText = ({
|
||||
text,
|
||||
remove,
|
||||
} : {
|
||||
text: string;
|
||||
remove?: () => void;
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<div className="group flex items-center justify-center rounded-full border-[0.7px] border-transparent bg-gray-100 py-1 pl-2.5 pr-2 text-sm text-gray-800 hover:border-gray-200 dark:bg-gray-800 dark:text-white/90 dark:hover:border-gray-800">
|
||||
<span className="flex-initial max-w-full">{text}</span>
|
||||
<div className="flex flex-row-reverse flex-auto">
|
||||
{remove &&
|
||||
<div className="pl-2 text-gray-500 cursor-pointer group-hover:text-gray-400 dark:text-gray-400"
|
||||
onClick={() => remove()}>
|
||||
<svg
|
||||
className="fill-current"
|
||||
role="button"
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.40717 4.46881C3.11428 4.17591 3.11428 3.70104 3.40717 3.40815C3.70006 3.11525 4.17494 3.11525 4.46783 3.40815L6.99943 5.93975L9.53095 3.40822C9.82385 3.11533 10.2987 3.11533 10.5916 3.40822C10.8845 3.70112 10.8845 4.17599 10.5916 4.46888L8.06009 7.00041L10.5916 9.53193C10.8845 9.82482 10.8845 10.2997 10.5916 10.5926C10.2987 10.8855 9.82385 10.8855 9.53095 10.5926L6.99943 8.06107L4.46783 10.5927C4.17494 10.8856 3.70006 10.8856 3.40717 10.5927C3.11428 10.2998 3.11428 9.8249 3.40717 9.53201L5.93877 7.00041L3.40717 4.46881Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const CtSelectedTextField = ({
|
||||
label,
|
||||
listText,
|
||||
setListText,
|
||||
} : {
|
||||
label?: string;
|
||||
listText: string[];
|
||||
setListText?: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
}) => {
|
||||
|
||||
if (label)
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<div style={{ width: "36px" }}>
|
||||
<Label className="translate-y-1 text-right">{label}</Label>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
|
||||
<div className="flex flex-wrap flex-auto gap-2">
|
||||
{ listText.map((text, index) => (
|
||||
text !== "" && <CtSelectedText key={index} text={text}></CtSelectedText>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap flex-auto gap-2">
|
||||
{ listText.map((text, index) => (
|
||||
text !== "" && <CtSelectedText key={index} text={text}></CtSelectedText>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CtSelectedTextField;
|
||||
@@ -248,7 +248,7 @@ export default function CtTree({ treeData, selectedKeys, setSelectedKeys, onClic
|
||||
useEffect(() => {
|
||||
const handleGlobalClick = (event: any) => {
|
||||
if (event.target.matches('.rc-tree-treenode')) {
|
||||
console.log("event.target = ", event.target)
|
||||
//console.log("event.target = ", event.target)
|
||||
|
||||
setSelectedKeys([]);
|
||||
}
|
||||
@@ -260,7 +260,7 @@ export default function CtTree({ treeData, selectedKeys, setSelectedKeys, onClic
|
||||
|
||||
|
||||
const switcherIcon = (nodeProps: any) => {
|
||||
console.log("nodeProps = ", nodeProps);
|
||||
//console.log("nodeProps = ", nodeProps);
|
||||
//const hasChildren = nodeProps?.data?.children && nodeProps.data.children.length > 0;
|
||||
//if (hasChildren == undefined || hasChildren == null) return null;
|
||||
if (nodeProps.isLeaf) return null;
|
||||
@@ -284,7 +284,7 @@ export default function CtTree({ treeData, selectedKeys, setSelectedKeys, onClic
|
||||
{loading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50 dark:bg-gray-900/40">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-brand-500"></span>
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 dark:border-gray-700 border-t-brand-500 dark:border-t-brand-500"></span>
|
||||
불러오는 중...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,9 +6,10 @@ import Select from '@/components/form/Select2';
|
||||
import Button from "./ui/button/Button2";
|
||||
import WithLabel from "./form/WithLabel";
|
||||
import { PlusIcon } from "@/icons";
|
||||
import InputSelectField from "./form/input/InputSelectField";
|
||||
|
||||
interface DeviceGoodsItem {
|
||||
slot: string;
|
||||
column_no: string;
|
||||
goods_id: number;
|
||||
price?: number;
|
||||
inventory?: number;
|
||||
@@ -156,18 +157,20 @@ export default function DeviceGoodsList({
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="컬럼번호2"
|
||||
value={item.slot}
|
||||
value={item.column_no}
|
||||
onChange={(e) => onSlotChange(index, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-50">
|
||||
<Select
|
||||
{/*<Select
|
||||
placeholder="상품을 선택하세요"
|
||||
options={optGoodsList}
|
||||
defaultValue={item.goods_id == 0 ? "" : String(item.goods_id)}
|
||||
onChange={(value) => onGoodsChange(index, value)}
|
||||
onChange={(e) => onGoodsChange(index, e.target.value)}
|
||||
className="dark:bg-dark-900"
|
||||
/>
|
||||
/>*/}
|
||||
<InputSelectField type="text" placeholder="상품을 선택하세요." value={item.goods_id == 0 ? "" : optGoodsList.find(opt => opt.value === String(item.goods_id))?.label}
|
||||
options={optGoodsList} selectLabel={true} onChange={(e) => onGoodsChange(index, e.target.value)} />
|
||||
</div>
|
||||
<div className={"w-24"}>
|
||||
<Input
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
import Select from "./form/Select2";
|
||||
|
||||
|
||||
interface PageCountSelectorProps {
|
||||
defaultValue?: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
}
|
||||
|
||||
const PageCountSelector: React.FC<PageCountSelectorProps> = ({
|
||||
defaultValue,
|
||||
onChange,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center justify-end">
|
||||
<Select
|
||||
options={[
|
||||
{ label: "20개씩 보기", value: "20" },
|
||||
{ label: "30개씩 보기", value: "30" },
|
||||
{ label: "40개씩 보기", value: "40" },
|
||||
{ label: "50개씩 보기", value: "50" },
|
||||
]}
|
||||
defaultValue={defaultValue} onChange={onChange} className="dark:bg-dark-900" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageCountSelector;
|
||||
@@ -27,7 +27,7 @@ const ComponentCard: React.FC<ComponentCardProps> = ({
|
||||
>
|
||||
{/* Card Header */}
|
||||
{ title &&
|
||||
<div className="px-6 py-3 flex items-center">
|
||||
<div className="px-4 py-3 flex items-center">
|
||||
<div className="flex items-center gap-1">
|
||||
<h3 className="flex text-base font-medium text-gray-800 dark:text-white/90">
|
||||
<span className="inline-flex gap-2 mr-5">{titleIcon}{title}</span>
|
||||
@@ -41,7 +41,7 @@ const ComponentCard: React.FC<ComponentCardProps> = ({
|
||||
{topUi}
|
||||
{ setOpen &&
|
||||
<button onClick={() => setOpen(!isOpen)}
|
||||
className="ml-5 flex h-10 w-10 items-center justify-center rounded-full bg-gray-100 duration-200 ease-linear dark:bg-white/[0.03] text-gray-800 dark:text-white/90 rotate-180">
|
||||
className="ml-3 flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 duration-200 ease-linear dark:bg-white/[0.03] text-gray-800 dark:text-white/90 rotate-180">
|
||||
<svg className={`stroke-current transform transition-transform duration-300 ease-in-out ${isOpen ? "rotate-0" : "rotate-180"}`}
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.75 8.875L12 15.125L18.25 8.875" stroke="" strokeWidth="2" strokeLinecap="round" ></path>
|
||||
@@ -52,13 +52,19 @@ const ComponentCard: React.FC<ComponentCardProps> = ({
|
||||
}
|
||||
|
||||
{/* Card Body */}
|
||||
{ (isOpen === undefined || isOpen === true) && React.Children.map(children, (item, index) => {
|
||||
return (
|
||||
<div key={index} className="p-4 border-t border-gray-100 dark:border-gray-800 sm:p-6">
|
||||
<div className="space-y-6">{item}</div>
|
||||
{ //(isOpen === undefined || isOpen === true) && React.Children.map(children, (item, index) => {
|
||||
/*(isOpen === undefined || isOpen === true) && React.Children.toArray(children).filter(Boolean).map((item, index) => {
|
||||
return (
|
||||
<div key={index} className="p-4 border-t border-gray-100 dark:border-gray-800 sm:p-6">
|
||||
<div className="space-y-6">{item}</div>
|
||||
</div>
|
||||
)
|
||||
})*/
|
||||
(isOpen === undefined || isOpen === true) &&
|
||||
<div className="p-4 border-t border-gray-100 dark:border-gray-800 sm:p-6 space-y-3">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
text: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
interface InputSelectProps {
|
||||
label: string;
|
||||
options: Option[];
|
||||
defaultSelected?: string[];
|
||||
onChange?: (selected: string[]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const InputSelect: React.FC<InputSelectProps> = ({
|
||||
label,
|
||||
options,
|
||||
defaultSelected = [],
|
||||
onChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [selectedOptions, setSelectedOptions] =
|
||||
useState<string[]>(defaultSelected);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
if (disabled) return;
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleSelect = (optionValue: string) => {
|
||||
const newSelectedOptions = selectedOptions.includes(optionValue)
|
||||
? selectedOptions.filter((value) => value !== optionValue)
|
||||
: [...selectedOptions, optionValue];
|
||||
|
||||
setSelectedOptions(newSelectedOptions);
|
||||
if (onChange) onChange(newSelectedOptions);
|
||||
};
|
||||
|
||||
const removeOption = (index: number, value: string) => {
|
||||
const newSelectedOptions = selectedOptions.filter((opt) => opt !== value);
|
||||
setSelectedOptions(newSelectedOptions);
|
||||
if (onChange) onChange(newSelectedOptions);
|
||||
};
|
||||
|
||||
const selectedValuesText = selectedOptions.map(
|
||||
(value) => options.find((option) => option.value === value)?.text || ""
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
{label}
|
||||
</label>
|
||||
|
||||
<div className="relative z-20 inline-block w-full">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<div onClick={toggleDropdown} className="w-full">
|
||||
<div className="mb-2 flex h-11 rounded-lg border border-gray-300 py-1.5 pl-3 pr-3 shadow-theme-xs outline-hidden transition focus:border-brand-300 focus:shadow-focus-ring dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-300">
|
||||
<div className="flex flex-wrap flex-auto gap-2">
|
||||
<input
|
||||
placeholder="Select option"
|
||||
className="w-full h-full p-1 pr-2 text-sm bg-transparent border-0 outline-hidden appearance-none placeholder:text-gray-800 focus:border-0 focus:outline-hidden focus:ring-0 dark:placeholder:text-white/90"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center py-1 pl-1 pr-1 w-7">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleDropdown}
|
||||
className="w-5 h-5 text-gray-700 outline-hidden cursor-pointer focus:outline-hidden dark:text-gray-400"
|
||||
>
|
||||
<svg
|
||||
className={`stroke-current ${isOpen ? "rotate-180" : ""}`}
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.79175 7.39551L10.0001 12.6038L15.2084 7.39551"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div
|
||||
className="absolute left-0 z-40 w-full overflow-y-auto bg-white rounded-lg shadow-sm top-full max-h-select dark:bg-gray-900"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
{options.map((option, index) => (
|
||||
<div key={index}>
|
||||
<div
|
||||
className={`hover:bg-primary/5 w-full cursor-pointer rounded-t border-b border-gray-200 dark:border-gray-800`}
|
||||
onClick={() => handleSelect(option.value)}
|
||||
>
|
||||
<div
|
||||
className={`relative flex w-full items-center p-2 pl-2 ${
|
||||
selectedOptions.includes(option.value)
|
||||
? "bg-primary/10"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="mx-2 leading-6 text-gray-800 dark:text-white/90">
|
||||
{option.text}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputSelect;
|
||||
@@ -9,14 +9,16 @@ interface Option {
|
||||
interface SelectProps {
|
||||
options: Option[];
|
||||
placeholder?: string;
|
||||
onChange: (value: string) => void;
|
||||
//onChange: (value: string) => void;
|
||||
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
className?: string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
const Select: React.FC<SelectProps> = ({
|
||||
options,
|
||||
placeholder = "Select an option",
|
||||
//placeholder = "Select an option",
|
||||
placeholder,
|
||||
onChange,
|
||||
className = "",
|
||||
defaultValue = "",
|
||||
@@ -27,7 +29,8 @@ const Select: React.FC<SelectProps> = ({
|
||||
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = e.target.value;
|
||||
setSelectedValue(value);
|
||||
onChange(value); // Trigger parent handler
|
||||
//onChange(value); // Trigger parent handler
|
||||
onChange(e); // Trigger parent handler
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +43,7 @@ const Select: React.FC<SelectProps> = ({
|
||||
<div className="relative">
|
||||
|
||||
<select
|
||||
className={`h-11 w-full appearance-none rounded-lg border border-gray-300 px-4 py-2.5 pr-11 text-sm shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 ${
|
||||
className={`h-10 w-full appearance-none rounded-lg border border-gray-300 px-2 py-1.5 pr-11 text-sm shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 ${
|
||||
selectedValue
|
||||
? "text-gray-800 dark:text-white/90"
|
||||
: "text-gray-400 dark:text-gray-400"
|
||||
@@ -49,6 +52,7 @@ const Select: React.FC<SelectProps> = ({
|
||||
onChange={handleChange}
|
||||
>
|
||||
{/* Placeholder option */}
|
||||
{ placeholder &&
|
||||
<option
|
||||
value=""
|
||||
//disabled
|
||||
@@ -56,6 +60,7 @@ const Select: React.FC<SelectProps> = ({
|
||||
>
|
||||
{placeholder}
|
||||
</option>
|
||||
}
|
||||
{/* Map over options */}
|
||||
{options.map((option) => (
|
||||
<option
|
||||
|
||||
@@ -12,7 +12,10 @@ export default function WithLabel({
|
||||
}) {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Label className={`w-${label_width} translate-y-3 text-right`}>{label}</Label>
|
||||
{/*<Label className={`w-${label_width} translate-y-3 text-right`}>{label}</Label>*/}
|
||||
<div style={{ width: `${label_width}px` }}>
|
||||
<Label className="translate-y-3 text-right">{label}</Label>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function DatePicker({
|
||||
<input
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
className="h-11 w-full rounded-lg border appearance-none px-4 py-2.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-brand-500/20 dark:border-gray-700 dark:focus:border-brand-800"
|
||||
className="h-10 w-full rounded-lg border appearance-none px-3 py-1.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-brand-500/20 dark:border-gray-700 dark:focus:border-brand-800"
|
||||
/>
|
||||
|
||||
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
|
||||
|
||||
@@ -19,7 +19,8 @@ const Checkbox: React.FC<CheckboxProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<label
|
||||
className={`flex items-center space-x-3 group cursor-pointer ${
|
||||
//Bruce className={`flex items-center space-x-3 group cursor-pointer ${
|
||||
className={`flex items-center space-x-2 group cursor-pointer ${
|
||||
disabled ? "cursor-not-allowed opacity-60" : ""
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -38,7 +38,7 @@ const Input: FC<InputProps> = ({
|
||||
hint,
|
||||
}) => {
|
||||
// Determine input styles based on state (disabled, success, error)
|
||||
let inputClasses = `h-11 w-full rounded-lg border appearance-none px-4 py-2.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 ${className}`;
|
||||
let inputClasses = `h-10 w-full rounded-lg border appearance-none px-2 py-1.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 ${className}`;
|
||||
|
||||
// Add styles for the different states
|
||||
if (disabled) {
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
import React, { FC, useState, useEffect, useRef } from "react";
|
||||
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface InputSelectFieldProps {
|
||||
type?: "text" | "number" | "email" | "password" | "date" | "time" | string;
|
||||
id?: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
defaultValue?: string | number;
|
||||
value?: string | number;
|
||||
options: Option[];
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
className?: string;
|
||||
min?: string;
|
||||
max?: string;
|
||||
step?: number;
|
||||
selectLabel?: boolean;
|
||||
disableKeyboard?: boolean;
|
||||
disabled?: boolean;
|
||||
success?: boolean;
|
||||
error?: boolean;
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
const InputSelectField: FC<InputSelectFieldProps> = ({
|
||||
type = "text",
|
||||
id,
|
||||
name,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
value,
|
||||
options,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
className = "",
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
selectLabel = false,
|
||||
disableKeyboard = false,
|
||||
disabled = false,
|
||||
success = false,
|
||||
error = false,
|
||||
hint,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
||||
const [inputText, setInputText] = useState<string>(
|
||||
String(value ?? defaultValue ?? "")
|
||||
);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const filteredOptions = options.filter((option) =>
|
||||
disableKeyboard ? option : option.value.toLowerCase().includes(inputText.toLowerCase())
|
||||
);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
if (disabled)
|
||||
return;
|
||||
|
||||
setIsOpen((prev) => !prev);
|
||||
setHighlightedIndex(filteredOptions.length === 1 ? 0 : -1);
|
||||
};
|
||||
|
||||
const fireChange = (optionValue: string) => {
|
||||
if (onChange) {
|
||||
onChange({
|
||||
target: { value: optionValue },
|
||||
currentTarget: { value: optionValue },
|
||||
nativeEvent: new Event('change'),
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
defaultPrevented: false,
|
||||
eventPhase: 0,
|
||||
isTrusted: true,
|
||||
preventDefault: () => {},
|
||||
isDefaultPrevented: () => false,
|
||||
stopPropagation: () => {},
|
||||
isPropagationStopped: () => false,
|
||||
persist: () => {},
|
||||
timeStamp: Date.now(),
|
||||
type: 'change'
|
||||
} as React.ChangeEvent<HTMLInputElement>);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (optionValue: string) => {
|
||||
setInputText(optionValue);
|
||||
fireChange(optionValue);
|
||||
setIsOpen(false);
|
||||
setHighlightedIndex(-1);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setInputText(e.target.value);
|
||||
setIsOpen(true);
|
||||
setHighlightedIndex(-1);
|
||||
onChange?.(e);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
onKeyDown?.(e);
|
||||
|
||||
if (disabled) return;
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
if (!isOpen) {
|
||||
setIsOpen(true);
|
||||
setHighlightedIndex(0);
|
||||
} else if (filteredOptions.length > 1) {
|
||||
setHighlightedIndex((prev) => (prev + 1) % filteredOptions.length);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
if (!isOpen) {
|
||||
setIsOpen(true);
|
||||
setHighlightedIndex(filteredOptions.length - 1);
|
||||
} else if (filteredOptions.length > 1) {
|
||||
setHighlightedIndex(
|
||||
(prev) => (prev - 1 + filteredOptions.length) % filteredOptions.length
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "Enter":
|
||||
e.preventDefault();
|
||||
if (filteredOptions.length === 1) {
|
||||
handleSelect(filteredOptions[0].value);
|
||||
} else if (
|
||||
isOpen &&
|
||||
highlightedIndex >= 0 &&
|
||||
highlightedIndex < filteredOptions.length
|
||||
) {
|
||||
handleSelect(filteredOptions[highlightedIndex].value);
|
||||
}
|
||||
break;
|
||||
|
||||
case "Escape":
|
||||
setIsOpen(false);
|
||||
setHighlightedIndex(-1);
|
||||
break;
|
||||
|
||||
//case "ArrowLeft":
|
||||
//case "ArrowRight":
|
||||
//case "Delete":
|
||||
//case "Backspace":
|
||||
break;
|
||||
|
||||
default:
|
||||
if (disableKeyboard) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
|
||||
setIsOpen(false);
|
||||
setHighlightedIndex(-1);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== undefined) {
|
||||
setInputText(String(value));
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
|
||||
let inputClasses = `h-10 w-full rounded-lg border appearance-none px-2 py-1.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800 ${className}`;
|
||||
|
||||
if (disabled) {
|
||||
inputClasses += ` text-gray-500 border-gray-300 cursor-not-allowed bg-gray-100 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-700`;
|
||||
} else if (error) {
|
||||
inputClasses += ` text-error-800 border-error-500 focus:ring-3 focus:ring-error-500/10 dark:text-error-400 dark:border-error-500`;
|
||||
} else if (success) {
|
||||
inputClasses += ` text-success-500 border-success-400 focus:ring-success-500/10 focus:border-success-300 dark:text-success-400 dark:border-success-500`;
|
||||
} else {
|
||||
inputClasses += ` bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative" ref={wrapperRef}>
|
||||
<input
|
||||
type={type}
|
||||
id={id}
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
value={inputText}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={toggleDropdown}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
disabled={disabled}
|
||||
className={inputClasses}
|
||||
/>
|
||||
|
||||
{hint && (
|
||||
<p
|
||||
className={`mt-1.5 text-xs ${
|
||||
error ? "text-error-500" : success ? "text-success-500" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{hint}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isOpen && filteredOptions.length > 0 && (
|
||||
<div
|
||||
className="absolute left-0 z-40 w-full overflow-y-auto bg-white rounded-lg shadow-sm top-full max-h-70 dark:bg-gray-900"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
{filteredOptions.map((option, index) => (
|
||||
<div key={index}>
|
||||
<div
|
||||
className={`w-full cursor-pointer rounded-t border-b border-gray-200 dark:border-gray-800 ${
|
||||
index === highlightedIndex
|
||||
? "bg-slate-100 dark:bg-gray-800"
|
||||
: "hover:bg-slate-50 dark:hover:bg-white/5"
|
||||
}`}
|
||||
onMouseEnter={() => setHighlightedIndex(index)}
|
||||
onClick={() => handleSelect(selectLabel ? option.label : option.value)}
|
||||
>
|
||||
<div className={`relative flex w-full items-center p-2 pl-1`}>
|
||||
<div className="mx-2 leading-6 text-xs text-gray-800 dark:text-white/90">
|
||||
{option.label}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputSelectField;
|
||||
@@ -59,8 +59,7 @@ export default function UserDropdown() {
|
||||
}
|
||||
|
||||
api.post('/api/error-device.do', params).then((resp) => {
|
||||
console.log(resp);
|
||||
|
||||
//console.log(resp);
|
||||
if (resp.data.result.list.length == 0)
|
||||
return;
|
||||
|
||||
@@ -89,14 +88,14 @@ export default function UserDropdown() {
|
||||
}
|
||||
|
||||
api.post('/api/get-miss.do', params).then((resp) => {
|
||||
console.log(resp);
|
||||
//console.log(resp);
|
||||
|
||||
if (resp.data.result.list.length == 0)
|
||||
return;
|
||||
|
||||
let msg = "";
|
||||
for (const item of resp.data.result.list) {
|
||||
msg += "\n자판기TID: " + item.uid1 + ", 미투척 컬럼번호: " + item.slot;
|
||||
msg += "\n자판기TID: " + item.uid1 + ", 미투척 컬럼번호: " + item.column_no;
|
||||
}
|
||||
|
||||
setErrorMsg(msg);
|
||||
@@ -264,7 +263,7 @@ export default function UserDropdown() {
|
||||
<Modal
|
||||
isOpen={errorModal.isOpen}
|
||||
onClose={errorModal.closeModal}
|
||||
className="max-w-[600px] p-5 lg:p-10"
|
||||
className="max-w-[500px] p-5 lg:p-10"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="relative flex items-center justify-center z-1 mb-7">
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@/components/ui/table";
|
||||
import Pagination from '@/components/tables/Pagination2';
|
||||
import { useUiLoadingStore } from "@/lib/_AG";
|
||||
import { FileIcon } from "@/icons";
|
||||
|
||||
|
||||
interface CtTableDefineProps {
|
||||
@@ -15,10 +16,14 @@ interface CtTableDefineProps {
|
||||
key: string;
|
||||
renderCol?: (item: any, colIndex: number) => React.ReactNode;
|
||||
renderItem?: (item: any, rowIndex: number) => React.ReactNode;
|
||||
viewMobile?: boolean,
|
||||
hidden?:boolean,
|
||||
}
|
||||
|
||||
interface CtTable1Props {
|
||||
columns: CtTableDefineProps[];
|
||||
sumColumns?: CtTableDefineProps[];
|
||||
sumData?: { [key: string]: any };
|
||||
bodyData: any[];
|
||||
offset: number;
|
||||
totalCount: number;
|
||||
@@ -49,7 +54,7 @@ const CtTableRow: React.FC<CtTableRowProps> = ({ children, className, ...props }
|
||||
};
|
||||
|
||||
// Props for TableCell
|
||||
interface CtTableCellProps extends React.HTMLAttributes<HTMLTableCellElement> {
|
||||
interface CtTableCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
@@ -61,6 +66,8 @@ const CtTableCell: React.FC<CtTableCellProps> = ({ children, className, ...props
|
||||
|
||||
const CtTable1: React.FC<CtTable1Props> = ({
|
||||
columns,
|
||||
sumColumns,
|
||||
sumData,
|
||||
bodyData,
|
||||
offset,
|
||||
totalCount,
|
||||
@@ -82,6 +89,23 @@ const CtTable1: React.FC<CtTable1Props> = ({
|
||||
//console.log("columns", columns);
|
||||
const [totalPages, setTotalPages] = useState<number>(0);
|
||||
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||
const [isMobileMode, setIsMobileMode] = useState<boolean>(false);
|
||||
const [arrRowOpen, setArrRowOpen] = useState<boolean[]>(() => bodyData.map(() => false));
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setIsMobileMode(window.innerWidth < 768);
|
||||
//console.log("isMobileMode = ", isMobileMode);
|
||||
};
|
||||
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setArrRowOpen(new Array(bodyData.length).fill(false));
|
||||
}, [bodyData]);
|
||||
|
||||
useEffect(() => {
|
||||
setTotalPages(Math.floor((totalCount - 1) / countPerPage) + 1);
|
||||
@@ -124,8 +148,150 @@ const CtTable1: React.FC<CtTable1Props> = ({
|
||||
onChangeSelectedRows?.(newRows);
|
||||
};
|
||||
|
||||
const onClickRowMobile = (index: number) => {
|
||||
setArrRowOpen(prev => {
|
||||
if (index < 0) return prev;
|
||||
const next = [...prev];
|
||||
if (index >= next.length) return prev;
|
||||
next[index] = !next[index];
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
if (isMobileMode)
|
||||
return (<>
|
||||
{sumColumns && sumData && Object.entries(sumData).length !== 0 &&
|
||||
<div className="col-span-12">
|
||||
<div className="rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
|
||||
<div className="max-w-full">
|
||||
<div className="px-4 py-1.5 text-gray-500 text-center font-bold text-theme-xs dark:text-gray-400 bg-green-50 dark:bg-green-500/10">
|
||||
조회 결과 합산
|
||||
</div>
|
||||
<div className="bg-amber-50/50 dark:bg-amber-500/10">
|
||||
{sumColumns.map((column, colIndex) => (
|
||||
<div key={colIndex} className="flex justify-between">
|
||||
<span className="px-4 py-1.5 text-start text-theme-xs text-gray-600 dark:text-gray-400 transition-colors">{column.title}</span>
|
||||
<div className="px-4 py-1.5 text-start text-theme-xs text-gray-600 dark:text-gray-400 transition-colors">
|
||||
{column.renderItem ? column.renderItem(sumData, colIndex) : sumData[column.key] ? sumData[column.key] : ""}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="col-span-12">
|
||||
<div className="rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
|
||||
<div className="max-w-full">
|
||||
<div className="">
|
||||
<Table>
|
||||
<TableHeader className="border-b border-gray-100 dark:border-white/[0.05]">
|
||||
<TableRow>
|
||||
{columns?.map((column, index) => (
|
||||
column.viewMobile &&
|
||||
<TableCell
|
||||
key={index}
|
||||
isHeader
|
||||
className="px-4 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
{column.title}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="divide-y divide-gray-100 dark:divide-white/[0.05]">
|
||||
{bodyData.map((item, rowIndex) => (<React.Fragment key={rowIndex}>
|
||||
<CtTableRow onClick={() => onClickRowMobile(rowIndex)}>
|
||||
{columns.map((column, colIndex) => (
|
||||
column.viewMobile &&
|
||||
<CtTableCell
|
||||
key={colIndex}
|
||||
className="px-4 py-3 text-start text-theme-sm text-gray-600 dark:text-gray-400 transition-colors"
|
||||
>
|
||||
{column.renderItem ? column.renderItem(item, rowIndex) : item[column.key] ? item[column.key] : ""}
|
||||
</CtTableCell>
|
||||
))}
|
||||
<CtTableCell className="text-start text-theme-sm text-gray-600 dark:text-gray-400 transition-colors">
|
||||
<svg className={`mr-3 stroke-current transform transition-transform duration-300 ease-in-out ${arrRowOpen[rowIndex] ? "rotate-180" : "rotate-0"}`}
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.75 8.875L12 15.125L18.25 8.875" stroke="" strokeWidth="2" strokeLinecap="round"></path>
|
||||
</svg>
|
||||
</CtTableCell>
|
||||
</CtTableRow>
|
||||
<CtTableRow>
|
||||
<CtTableCell colSpan={columns.length}>
|
||||
{arrRowOpen[rowIndex] &&
|
||||
<div className="transition-colors bg-blue-50/40 dark:bg-blue-500/10">
|
||||
{columns.map((column, colIndex) => (
|
||||
!column.hidden &&
|
||||
<div key={colIndex} className="flex justify-between">
|
||||
<span className="px-4 py-1.5 text-start text-theme-xs text-gray-600 dark:text-gray-400 transition-colors">{column.title}</span>
|
||||
<div className="px-4 py-1.5 text-start text-theme-xs text-gray-600 dark:text-gray-400 transition-colors">
|
||||
{column.renderItem ? column.renderItem(item, rowIndex) : item[column.key] ? item[column.key] : ""}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>}
|
||||
</CtTableCell>
|
||||
</CtTableRow>
|
||||
</React.Fragment>))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{bodyData.length <= 0 &&
|
||||
<div className="px-4 py-9 text-center text-theme-sm text-gray-600 dark:text-gray-400 font-bold transition-colors">
|
||||
조회 결과가 없습니다.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b border-gray-100 pt-4 text-center text-md text-gray-500 xl:border-b-0 xl:pb-0 xl:text-left dark:border-gray-800 dark:text-gray-400">
|
||||
총 {totalCount}개 ({totalPages}페이지)
|
||||
</div>
|
||||
<div className="mt-5 flex justify-center">
|
||||
<Pagination currentPage={currentPage} totalPages={totalPages} pageNumCount={pageNumCount} onPageChange={onClickPageChange || (() => {})} />
|
||||
</div>
|
||||
</div>
|
||||
</>);
|
||||
|
||||
return (
|
||||
<div className="col-span-12">
|
||||
{ sumColumns && sumData && Object.entries(sumData).length !== 0 && <>
|
||||
<div className="px-4 py-1.5 text-gray-500 text-left font-bold text-theme-xs dark:text-gray-400">
|
||||
조회 결과 합산
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03] mb-3">
|
||||
<div className="max-w-full overflow-x-auto">
|
||||
<div className="min-w-[1280px]">
|
||||
|
||||
<Table>
|
||||
<TableHeader className="border-b border-gray-100 dark:border-white/[0.05] bg-green-50 dark:bg-green-500/10">
|
||||
<TableRow>
|
||||
{ sumColumns.map((column, index) => (
|
||||
<TableCell key={index} isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
|
||||
{column.title}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="divide-y divide-gray-100 dark:divide-white/[0.05] bg-amber-50/50 dark:bg-amber-500/10">
|
||||
<TableRow>
|
||||
{ sumColumns.map((column, index) => (
|
||||
<TableCell key={index} className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
|
||||
{column.renderItem ? column.renderItem(sumData, index) : sumData[column.key] ? sumData[column.key] : ""}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
|
||||
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
|
||||
<div className="max-w-full overflow-x-auto">
|
||||
<div className="min-w-[1280px]">
|
||||
@@ -142,6 +308,7 @@ const CtTable1: React.FC<CtTable1Props> = ({
|
||||
</TableCell>
|
||||
)}
|
||||
{columns.map((column, index) => (
|
||||
!column.hidden &&
|
||||
<TableCell
|
||||
key={index}
|
||||
isHeader
|
||||
@@ -161,7 +328,8 @@ const CtTable1: React.FC<CtTable1Props> = ({
|
||||
<div className="mx-auto h-3 w-3 animate-pulse rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</CtTableCell>
|
||||
)}
|
||||
{columns.map((_, colIndex) => (
|
||||
{columns.map((column, colIndex) => (
|
||||
!column.hidden &&
|
||||
<CtTableCell
|
||||
key={`skeleton-col-${colIndex}`}
|
||||
className="px-4 py-3 text-start text-theme-sm text-gray-600 dark:text-gray-400"
|
||||
@@ -190,6 +358,7 @@ const CtTable1: React.FC<CtTable1Props> = ({
|
||||
</CtTableCell>
|
||||
)}
|
||||
{columns.map((column, colIndex) => (
|
||||
!column.hidden &&
|
||||
<CtTableCell
|
||||
key={colIndex}
|
||||
className={`px-4 py-3 text-start text-theme-sm text-gray-600 dark:text-gray-400 transition-colors ${hoverCellClassName}`}
|
||||
@@ -204,11 +373,19 @@ const CtTable1: React.FC<CtTable1Props> = ({
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{bodyData.length <= 0 &&
|
||||
<div className="px-4 py-9 text-center text-theme-sm text-gray-600 dark:text-gray-400 font-bold transition-colors">
|
||||
조회 결과가 없습니다.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex justify-end">
|
||||
<Pagination currentPage={currentPage} totalPages={totalPages} pageNumCount={pageNumCount} onPageChange={onClickPageChange || (() => { })} />
|
||||
<div className="mt-5 flex justify-between">
|
||||
<span className="border-b border-gray-100 pb-1 text-center text-md text-gray-500 xl:border-b-0 xl:pb-0 xl:text-left dark:border-gray-800 dark:text-gray-400">
|
||||
총 {totalCount}개 ({totalPages}페이지)
|
||||
</span>
|
||||
<Pagination currentPage={currentPage} totalPages={totalPages} pageNumCount={pageNumCount} onPageChange={onClickPageChange || (() => {})} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,10 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
pageNumCount,
|
||||
onPageChange,
|
||||
}) => {
|
||||
//console.log("currentPage = ", currentPage)
|
||||
//console.log("totalPages = ", totalPages)
|
||||
//console.log("pageNumCount = ", pageNumCount)
|
||||
|
||||
//const pagesAroundCurrent = Array.from(
|
||||
// { length: Math.min(pageNumCount, totalPages) },
|
||||
// (_, i) => i + Math.max(currentPage - 1, 1)
|
||||
@@ -25,11 +29,11 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
//console.log("pagesAroundCurrent = ", pagesAroundCurrent)
|
||||
|
||||
return (
|
||||
<div className="flex items-center ">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
className="mr-2.5 flex items-center h-10 justify-center rounded-lg border border-gray-300 bg-white px-3.5 py-2.5 text-gray-700 shadow-theme-xs hover:bg-gray-50 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] text-sm"
|
||||
className="mr-2.5 flex items-center h-10 justify-center rounded-lg border border-gray-300 bg-white px-2.5 py-1.5px-2.5 py-1.5 text-gray-700 shadow-theme-xs hover:bg-gray-50 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] text-sm"
|
||||
>
|
||||
이전
|
||||
</button>
|
||||
@@ -52,8 +56,8 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
className="ml-2.5 flex items-center justify-center rounded-lg border border-gray-300 bg-white px-3.5 py-2.5 text-gray-700 shadow-theme-xs text-sm hover:bg-gray-50 h-10 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
disabled={totalPages < 1 || currentPage === totalPages}
|
||||
className="ml-2.5 flex items-center justify-center rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-gray-700 shadow-theme-xs text-sm hover:bg-gray-50 h-10 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
다음
|
||||
</button>
|
||||
|
||||
@@ -23,8 +23,8 @@ const Button: React.FC<ButtonProps> = ({
|
||||
}) => {
|
||||
// Size Classes
|
||||
const sizeClasses = {
|
||||
sm: "px-4 py-3 text-sm",
|
||||
md: "px-5 py-3.5 text-sm",
|
||||
sm: "px-3 py-2 text-sm",
|
||||
md: "px-4 py-2.5 text-sm",
|
||||
};
|
||||
|
||||
// Variant Classes
|
||||
|
||||
@@ -16,7 +16,7 @@ export const DropdownItem: React.FC<DropdownItemProps> = ({
|
||||
href,
|
||||
onClick,
|
||||
onItemClick,
|
||||
baseClassName = "block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900",
|
||||
baseClassName = "block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300",
|
||||
className = "",
|
||||
children,
|
||||
}) => {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="0.8">
|
||||
<line x1="3" y1="6" x2="21" y2="6"/>
|
||||
<circle cx="8" cy="6" r="2.5" fill="white"/>
|
||||
<line x1="3" y1="12" x2="21" y2="12"/>
|
||||
<circle cx="15" cy="12" r="2.5" fill="white"/>
|
||||
<line x1="3" y1="18" x2="21" y2="18"/>
|
||||
<circle cx="10" cy="18" r="2.5" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 475 B |
@@ -51,6 +51,8 @@ import HorizontaLDots from "./horizontal-dots.svg";
|
||||
import ChatIcon from "./chat.svg";
|
||||
import MoreDotIcon from "./more-dot.svg";
|
||||
import BellIcon from "./bell.svg";
|
||||
import FilterIcon from "./filter-slider.svg";
|
||||
|
||||
|
||||
export {
|
||||
DownloadIcon,
|
||||
@@ -106,4 +108,5 @@ export {
|
||||
HorizontaLDots,
|
||||
ChevronUpIcon,
|
||||
ChatIcon,
|
||||
FilterIcon,
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ const navItems: NavItem[] = [
|
||||
icon: <ListIcon />,
|
||||
subItems: [
|
||||
{ name: "사업자원장 관리", path: "/biz-group" },
|
||||
{ name: "사업자원장 관리(리스트형식)", path: "/biz-group2" },
|
||||
{ name: "카드단말기 관리", path: "/terminal" },
|
||||
{ name: "계정관리(고객사포함)", path: "/account" },
|
||||
{ name: "미등록 단말기 거래내역", path: "/u-transactions" },
|
||||
@@ -102,9 +103,15 @@ const othersItems: NavItem[] = [
|
||||
const AppSidebar: React.FC = () => {
|
||||
const tokenPayload = useAuthStore((s) => s.tokenPayload); //Bruce, JWT
|
||||
|
||||
const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar();
|
||||
const { isExpanded, isMobileOpen, toggleMobileSidebar, isHovered, setIsHovered } = useSidebar();
|
||||
|
||||
const pathname = usePathname();
|
||||
|
||||
const handleNavigate = () => {
|
||||
if (isMobileOpen)
|
||||
toggleMobileSidebar();
|
||||
}
|
||||
|
||||
const renderMenuItems = (
|
||||
navItems: NavItem[],
|
||||
menuType: "main" | "others"
|
||||
@@ -150,6 +157,7 @@ const AppSidebar: React.FC = () => {
|
||||
href={nav.path}
|
||||
className={`menu-item group ${isActive(nav.path) ? "menu-item-active" : "menu-item-inactive"
|
||||
}`}
|
||||
onNavigate={handleNavigate}
|
||||
>
|
||||
<span
|
||||
className={`${isActive(nav.path)
|
||||
@@ -187,6 +195,7 @@ const AppSidebar: React.FC = () => {
|
||||
? "menu-dropdown-item-active"
|
||||
: "menu-dropdown-item-inactive"
|
||||
}`}
|
||||
onNavigate={handleNavigate}
|
||||
>
|
||||
{subItem.name}
|
||||
<span className="flex items-center gap-1 ml-auto">
|
||||
@@ -289,7 +298,7 @@ const AppSidebar: React.FC = () => {
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`fixed mt-16 flex flex-col lg:mt-0 top-0 px-5 left-0 bg-white dark:bg-gray-900 dark:border-gray-800 text-gray-900 h-screen transition-all duration-300 ease-in-out z-50 border-r border-gray-200
|
||||
className={`fixed mt-16 flex flex-col lg:mt-0 top-0 px-5 left-0 bg-white dark:bg-gray-900 dark:border-gray-800 text-gray-900 dark:text-gray-300 h-screen transition-all duration-300 ease-in-out z-50 border-r border-gray-200
|
||||
${isExpanded || isMobileOpen
|
||||
? "w-[290px]"
|
||||
: isHovered
|
||||
@@ -305,7 +314,7 @@ const AppSidebar: React.FC = () => {
|
||||
className={`py-8 flex ${!isExpanded && !isHovered ? "lg:justify-center" : "justify-start"
|
||||
}`}
|
||||
>
|
||||
<Link href="/">
|
||||
<Link href="/" onNavigate={handleNavigate}>
|
||||
{isExpanded || isHovered || isMobileOpen ? (
|
||||
<>
|
||||
<Image
|
||||
@@ -339,7 +348,7 @@ const AppSidebar: React.FC = () => {
|
||||
<div className="flex flex-col overflow-y-auto duration-300 ease-linear no-scrollbar">
|
||||
<nav className="mb-6">
|
||||
<div className="flex flex-col gap-4">
|
||||
{ tokenPayload?.permission == 1 &&
|
||||
{ tokenPayload?.permission == 0xffffffff &&
|
||||
<div>
|
||||
<h2
|
||||
className={`mb-4 text-xs uppercase flex leading-[20px] text-gray-400 ${!isExpanded && !isHovered
|
||||
|
||||
@@ -250,7 +250,7 @@ export const postFileDownload = (url: string, params: any) => {
|
||||
if (matches != null && matches[1])
|
||||
filename = matches[1].replace(/['"]/g, '');
|
||||
}
|
||||
const blob = new Blob([resp.data], {type: resp.headers['content-type']});
|
||||
const blob = new Blob([resp.data], {type: resp.headers['content-type'] as string | undefined});
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
//link.download = filename;
|
||||
@@ -264,6 +264,25 @@ export const postFileDownload = (url: string, params: any) => {
|
||||
});
|
||||
}
|
||||
|
||||
// YYYY-MM-DD
|
||||
export const todayYMD = () => {
|
||||
const d = new Date();
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
export const addDaysYMD = (days: number) => {
|
||||
const now = new Date();
|
||||
const d = new Date(now);
|
||||
d.setDate(d.getDate() + days);
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
// DB TIMESTAMP DATETIME modify (yyyy-MM-dd HH:mm:ss.0 -> yyyy-MM-dd HH:mm)
|
||||
export const convertDateTime = (tsDateTime: string) => {
|
||||
if (tsDateTime == undefined)
|
||||
@@ -403,8 +422,7 @@ export function formatBusinessNumber(input: string): string {
|
||||
//console.log(isValidBusinessNumber("220-81-62517")); // true (실제 유효 번호 예시)
|
||||
export function isValidBusinessNumber(input: string): boolean {
|
||||
|
||||
return true;
|
||||
|
||||
//return true;
|
||||
|
||||
const digits = input.replace(/\D/g, "");
|
||||
if (digits.length !== 10)
|
||||
|
||||
+237
-613
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user