nation 버전 이식
This commit is contained in:
@@ -0,0 +1,788 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Form,
|
||||
Input,
|
||||
Modal,
|
||||
Radio,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Typography,
|
||||
message,
|
||||
} from 'antd'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { PlusOutlined, SearchOutlined, ShopOutlined } from '@ant-design/icons'
|
||||
import client, { apiPrefix, unwrap, type ApiResponse } from '../../api/client'
|
||||
import DraggableModal from '../../components/DraggableModal'
|
||||
import { FormField, FormGrid, FormSection } from '../../components/OrderFormLayout'
|
||||
import BranchFeeFlowModals from './BranchFeeFlowModals'
|
||||
|
||||
type BranchRow = {
|
||||
id: number
|
||||
name?: string
|
||||
officeName?: string
|
||||
userid?: string
|
||||
phone?: string
|
||||
email?: string
|
||||
sido?: string
|
||||
level?: number
|
||||
grant?: number
|
||||
branchGroupId?: number | null
|
||||
branchGroupName?: string | null
|
||||
hasBusinessLicense?: boolean
|
||||
isTax?: number
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
type BranchGroup = { id: number; name: string }
|
||||
|
||||
type BranchDetail = BranchRow & {
|
||||
fax?: string
|
||||
zipcode?: string
|
||||
address?: string
|
||||
addressDetail?: string
|
||||
tel?: string
|
||||
centerName?: string
|
||||
isPersonal?: boolean
|
||||
accountBank?: string
|
||||
accountNumber?: string
|
||||
accountName?: string
|
||||
contactPhone?: string
|
||||
contactNateon?: string
|
||||
acceptPhone?: number
|
||||
acceptNateon?: number
|
||||
memo?: string
|
||||
adminMemo?: string
|
||||
licenseNumber?: string
|
||||
licenseType?: string
|
||||
licenseItem?: string
|
||||
notifyPhones?: string[]
|
||||
permissionOrderDateUpdate?: boolean
|
||||
permissionCalculate?: boolean
|
||||
permissionConfig?: boolean
|
||||
permissionExcelDownload?: boolean
|
||||
permissionChart?: boolean
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
const BANKS = ['국민은행', '우리은행', '신한은행', '하나은행', '기업은행', '농협', 'SC제일', '카카오뱅크', '기타']
|
||||
|
||||
function levelLabel(l?: number) {
|
||||
if (l === 5) return <span style={{ color: '#04A600' }}>센터</span>
|
||||
if (l === 9) return <span style={{ color: '#0F6DC4' }}>관리자</span>
|
||||
if (l === 10) return <span style={{ color: '#cc0000' }}>최고관리자</span>
|
||||
return <span>영업점</span>
|
||||
}
|
||||
|
||||
function EditBranchFormBody({
|
||||
groups,
|
||||
centers,
|
||||
}: {
|
||||
groups: BranchGroup[]
|
||||
centers: { id: number; name: string }[]
|
||||
}) {
|
||||
const isPersonal = Form.useWatch('isPersonal')
|
||||
const level = Form.useWatch('level')
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormSection title="기본 정보">
|
||||
<FormGrid cols={1}>
|
||||
<FormField label="유형" full>
|
||||
<Form.Item name="isPersonal" noStyle>
|
||||
<Radio.Group
|
||||
className="of-antd-choice-row"
|
||||
style={{ padding: 0, border: 'none' }}
|
||||
options={[
|
||||
{ value: true, label: '개인' },
|
||||
{ value: false, label: '사업자 (일반/법인)' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
<FormGrid>
|
||||
<FormField label="영업점 그룹">
|
||||
<Form.Item name="branchGroupId" noStyle>
|
||||
<Select allowClear placeholder="영업점 그룹 선택" options={groups.map((g) => ({ value: g.id, label: g.name }))} />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="수수료 유지">
|
||||
<Form.Item name="keepFee" valuePropName="checked" noStyle>
|
||||
<Checkbox>그룹 변경 시 수수료 유지</Checkbox>
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="레벨">
|
||||
<Form.Item name="level" noStyle>
|
||||
<Select options={[
|
||||
{ value: 1, label: '영업점' },
|
||||
{ value: 5, label: '센터' },
|
||||
{ value: 9, label: '관리자' },
|
||||
{ value: 10, label: '최고관리자' },
|
||||
]} />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="입력처">
|
||||
{level === 5 ? (
|
||||
<Form.Item name="centerName" noStyle>
|
||||
<Select allowClear options={centers.map((c) => ({ value: c.name, label: c.name }))} />
|
||||
</Form.Item>
|
||||
) : (
|
||||
<span className="of-muted">센터 레벨에서 선택</span>
|
||||
)}
|
||||
</FormField>
|
||||
{level === 9 && (
|
||||
<FormField label="권한" full>
|
||||
<Space wrap size={[8, 4]}>
|
||||
<Form.Item name="permissionOrderDateUpdate" valuePropName="checked" noStyle><Checkbox>접수일자 변경</Checkbox></Form.Item>
|
||||
<Form.Item name="permissionCalculate" valuePropName="checked" noStyle><Checkbox>정산</Checkbox></Form.Item>
|
||||
<Form.Item name="permissionConfig" valuePropName="checked" noStyle><Checkbox>환경설정</Checkbox></Form.Item>
|
||||
<Form.Item name="permissionExcelDownload" valuePropName="checked" noStyle><Checkbox>엑셀 다운로드</Checkbox></Form.Item>
|
||||
<Form.Item name="permissionChart" valuePropName="checked" noStyle><Checkbox>통계</Checkbox></Form.Item>
|
||||
</Space>
|
||||
</FormField>
|
||||
)}
|
||||
<FormField label="이름*">
|
||||
<Form.Item name="name" noStyle rules={[{ required: true }]}><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="영업점명*">
|
||||
<Form.Item name="officeName" noStyle rules={[{ required: true }]}><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="아이디">
|
||||
<Form.Item name="userid" noStyle><Input disabled /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="패스워드">
|
||||
<div style={{ display: 'flex', gap: 8, width: '100%' }}>
|
||||
<Form.Item name="password" noStyle style={{ flex: 1, margin: 0 }}>
|
||||
<Input.Password placeholder="변경 시 입력" />
|
||||
</Form.Item>
|
||||
<Form.Item name="passwordConfirmation" noStyle style={{ flex: 1, margin: 0 }}>
|
||||
<Input.Password placeholder="확인" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</FormField>
|
||||
<FormField label="이메일" full>
|
||||
<Form.Item name="email" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="휴대폰">
|
||||
<Form.Item name="phone" noStyle><Input placeholder="010-0000-0000" /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="연락처 / 팩스">
|
||||
<div style={{ display: 'flex', gap: 8, width: '100%' }}>
|
||||
<Form.Item name="tel" noStyle style={{ flex: 1, margin: 0 }}><Input placeholder="전화" /></Form.Item>
|
||||
<Form.Item name="fax" noStyle style={{ flex: 1, margin: 0 }}><Input placeholder="팩스" /></Form.Item>
|
||||
</div>
|
||||
</FormField>
|
||||
<FormField label="우편번호">
|
||||
<Form.Item name="zipcode" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="주소">
|
||||
<Form.Item name="address" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="상세주소" full>
|
||||
<Form.Item name="addressDetail" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
{isPersonal === false && (
|
||||
<>
|
||||
<FormField label="사업자등록번호">
|
||||
<Form.Item name="licenseNumber" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="업태 / 업종">
|
||||
<div style={{ display: 'flex', gap: 8, width: '100%' }}>
|
||||
<Form.Item name="licenseType" noStyle style={{ flex: 1, margin: 0 }}><Input placeholder="업태" /></Form.Item>
|
||||
<Form.Item name="licenseItem" noStyle style={{ flex: 1, margin: 0 }}><Input placeholder="업종" /></Form.Item>
|
||||
</div>
|
||||
</FormField>
|
||||
</>
|
||||
)}
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="결제 정보">
|
||||
<FormGrid>
|
||||
<FormField label="은행">
|
||||
<Form.Item name="accountBank" noStyle>
|
||||
<Select allowClear options={BANKS.map((b) => ({ value: b, label: b }))} />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="계좌번호">
|
||||
<Form.Item name="accountNumber" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="예금주">
|
||||
<div style={{ display: 'flex', gap: 8, width: '100%', alignItems: 'center' }}>
|
||||
<Form.Item name="accountName" noStyle style={{ flex: 1, margin: 0 }}><Input /></Form.Item>
|
||||
<Form.Item name="accountNameIsName" valuePropName="checked" noStyle>
|
||||
<Checkbox>정보동일</Checkbox>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</FormField>
|
||||
<FormField label="세금계산서">
|
||||
<Form.Item name="isTax" noStyle>
|
||||
<Select options={[
|
||||
{ value: 1, label: '세금계산서발행' },
|
||||
{ value: 0, label: '종합소득세공제' },
|
||||
]} />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="업무진행 상황 수신정보">
|
||||
<FormGrid>
|
||||
<FormField label="수신설정" full>
|
||||
<Space wrap>
|
||||
<Form.Item name="acceptPhone" valuePropName="checked" noStyle><Checkbox>핸드폰 문자 받음</Checkbox></Form.Item>
|
||||
<Form.Item name="acceptNateon" valuePropName="checked" noStyle><Checkbox>네이트온 쪽지 받음</Checkbox></Form.Item>
|
||||
</Space>
|
||||
</FormField>
|
||||
<FormField label="휴대폰">
|
||||
<Form.Item name="contactPhone" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="네이트온 아이디">
|
||||
<Form.Item name="contactNateon" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="정책 수신 휴대폰" full>
|
||||
<Form.List name="notifyPhones">
|
||||
{(fields) => (
|
||||
<Space wrap>
|
||||
{fields.map((field) => (
|
||||
<Form.Item {...field} key={field.key} noStyle>
|
||||
<Input style={{ width: 140 }} placeholder={`휴대폰 ${field.name + 1}`} />
|
||||
</Form.Item>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</Form.List>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="메모">
|
||||
<FormGrid cols={1}>
|
||||
<FormField label="메모" full>
|
||||
<Form.Item name="memo" noStyle><Input.TextArea rows={2} /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="관리자 메모" full>
|
||||
<Form.Item name="adminMemo" noStyle><Input.TextArea rows={2} /></Form.Item>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function BranchListPage() {
|
||||
const qc = useQueryClient()
|
||||
const [form] = Form.useForm()
|
||||
const [editForm] = Form.useForm()
|
||||
const [createOpen, setCreateOpen] = useState(false)
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
const [feeOpen, setFeeOpen] = useState(false)
|
||||
const [feeBranch, setFeeBranch] = useState<BranchRow | null>(null)
|
||||
const [editingId, setEditingId] = useState<number | null>(null)
|
||||
const [selectedId, setSelectedId] = useState<number | null>(null)
|
||||
const [page, setPage] = useState(0)
|
||||
const [draft, setDraft] = useState({ fGroup: '', fSido: '', fUserid: '', fOffice: '', fName: '' })
|
||||
const [applied, setApplied] = useState(draft)
|
||||
|
||||
const { data: rows = [], isLoading } = useQuery({
|
||||
queryKey: ['homes-branches'],
|
||||
queryFn: async () => {
|
||||
const { data } = await client.get<ApiResponse<BranchRow[]>>(`${apiPrefix()}/homes/branches`)
|
||||
return unwrap(data) || []
|
||||
},
|
||||
})
|
||||
|
||||
const { data: groups = [] } = useQuery({
|
||||
queryKey: ['homes-branch-groups'],
|
||||
queryFn: async () => {
|
||||
const { data } = await client.get<ApiResponse<BranchGroup[]>>(`${apiPrefix()}/homes/branch-groups`)
|
||||
return unwrap(data) || []
|
||||
},
|
||||
})
|
||||
|
||||
const { data: centers = [] } = useQuery({
|
||||
queryKey: ['homes-branch-centers'],
|
||||
queryFn: async () => {
|
||||
const { data } = await client.get<ApiResponse<{ id: number; name: string }[]>>(`${apiPrefix()}/homes/branch-centers`)
|
||||
return unwrap(data) || []
|
||||
},
|
||||
})
|
||||
|
||||
const sidoOptions = useMemo(() => {
|
||||
const set = new Set<string>()
|
||||
rows.forEach((b) => { if (b.sido) set.add(b.sido) })
|
||||
return Array.from(set).sort()
|
||||
}, [rows])
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const inc = (v: string | null | undefined, q: string) => (v ?? '').toLowerCase().includes(q.toLowerCase())
|
||||
return rows.filter((b) =>
|
||||
(!applied.fGroup || String(b.branchGroupId ?? '') === applied.fGroup)
|
||||
&& (!applied.fSido || b.sido === applied.fSido)
|
||||
&& (!applied.fUserid || inc(b.userid, applied.fUserid))
|
||||
&& (!applied.fOffice || inc(b.officeName, applied.fOffice))
|
||||
&& (!applied.fName || inc(b.name, applied.fName)),
|
||||
)
|
||||
}, [rows, applied])
|
||||
|
||||
const pageRows = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE)
|
||||
|
||||
const grantMut = useMutation({
|
||||
mutationFn: async ({ id, g }: { id: number; g: number }) => {
|
||||
const { data } = await client.post(`${apiPrefix()}/homes/branches/${id}/grant/${g}`)
|
||||
return unwrap(data as ApiResponse)
|
||||
},
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['homes-branches'] })
|
||||
message.success('승인 상태를 변경했습니다.')
|
||||
},
|
||||
onError: (e: Error) => message.error(e.message),
|
||||
})
|
||||
|
||||
const createMut = useMutation({
|
||||
mutationFn: async (values: Record<string, unknown>) => {
|
||||
const params = new URLSearchParams({ level: String(values.level ?? 1) })
|
||||
if (values.branchGroupId) params.set('branchGroupId', String(values.branchGroupId))
|
||||
const { data } = await client.post(
|
||||
`${apiPrefix()}/homes/branches?${params.toString()}`,
|
||||
values,
|
||||
)
|
||||
return unwrap(data as ApiResponse)
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success('영업점을 등록했습니다.')
|
||||
setCreateOpen(false)
|
||||
form.resetFields()
|
||||
qc.invalidateQueries({ queryKey: ['homes-branches'] })
|
||||
},
|
||||
onError: (e: Error) => message.error(e.message),
|
||||
})
|
||||
|
||||
const updateMut = useMutation({
|
||||
mutationFn: async (values: Record<string, unknown>) => {
|
||||
const { data } = await client.put(
|
||||
`${apiPrefix()}/homes/branches/${editingId}/detail`,
|
||||
values,
|
||||
)
|
||||
return unwrap(data as ApiResponse)
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success('수정했습니다.')
|
||||
setEditOpen(false)
|
||||
setEditingId(null)
|
||||
qc.invalidateQueries({ queryKey: ['homes-branches'] })
|
||||
},
|
||||
onError: (e: Error) => message.error(e.message),
|
||||
})
|
||||
|
||||
const deleteMut = useMutation({
|
||||
mutationFn: async (id: number) => {
|
||||
const { data } = await client.delete(`${apiPrefix()}/homes/branches/${id}`)
|
||||
return unwrap(data as ApiResponse)
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success('삭제했습니다.')
|
||||
qc.invalidateQueries({ queryKey: ['homes-branches'] })
|
||||
},
|
||||
onError: (e: Error) => message.error(e.message),
|
||||
})
|
||||
|
||||
const openEdit = async (row: BranchRow) => {
|
||||
try {
|
||||
const { data } = await client.get<ApiResponse<BranchDetail>>(
|
||||
`${apiPrefix()}/homes/branches/${row.id}/detail`,
|
||||
)
|
||||
const detail = unwrap(data)
|
||||
setEditingId(row.id)
|
||||
editForm.setFieldsValue({
|
||||
...detail,
|
||||
isPersonal: detail.isPersonal !== false,
|
||||
notifyPhones: [...(detail.notifyPhones || []), '', '', '', '', '', ''].slice(0, 6),
|
||||
keepFee: false,
|
||||
password: '',
|
||||
passwordConfirmation: '',
|
||||
accountNameIsName: false,
|
||||
acceptPhone: detail.acceptPhone === 1,
|
||||
acceptNateon: detail.acceptNateon === 1,
|
||||
})
|
||||
setEditOpen(true)
|
||||
} catch (e) {
|
||||
message.error((e as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDelete = (row: BranchRow) => {
|
||||
Modal.confirm({
|
||||
title: '영업점 삭제',
|
||||
content: (
|
||||
<div>
|
||||
<p><b>{row.officeName}</b> 영업점을 삭제하시겠습니까?</p>
|
||||
<p className="muted">관련 접수·정산 자료는 삭제되지 않지만, 영업점 계정 정보는 제거됩니다.</p>
|
||||
</div>
|
||||
),
|
||||
okText: '삭제',
|
||||
okButtonProps: { danger: true },
|
||||
cancelText: '취소',
|
||||
onOk: () => deleteMut.mutateAsync(row.id),
|
||||
})
|
||||
}
|
||||
|
||||
const columns: ColumnsType<BranchRow> = [
|
||||
{ title: '그룹', dataIndex: 'branchGroupName', width: 110, ellipsis: true },
|
||||
{ title: '지역', dataIndex: 'sido', width: 70, align: 'center' },
|
||||
{ title: '아이디', dataIndex: 'userid', width: 100, ellipsis: true },
|
||||
{ title: '영업점명', dataIndex: 'officeName', width: 120, ellipsis: true },
|
||||
{ title: '이름', dataIndex: 'name', width: 90, ellipsis: true },
|
||||
{
|
||||
title: '레벨',
|
||||
dataIndex: 'level',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: (v) => levelLabel(v),
|
||||
},
|
||||
{ title: '연락처', dataIndex: 'phone', width: 120 },
|
||||
{
|
||||
title: '사업자',
|
||||
dataIndex: 'hasBusinessLicense',
|
||||
width: 70,
|
||||
align: 'center',
|
||||
render: (v) => (v ? '사업자' : '개인'),
|
||||
},
|
||||
{
|
||||
title: '세금계산서',
|
||||
dataIndex: 'isTax',
|
||||
width: 110,
|
||||
align: 'center',
|
||||
render: (v) => (v === 1 ? '세금계산서발행' : '종합소득세공제'),
|
||||
},
|
||||
{
|
||||
title: '회원가입일',
|
||||
dataIndex: 'createdAt',
|
||||
width: 150,
|
||||
align: 'center',
|
||||
render: (v?: string) => v?.replace('T', ' ').substring(0, 19),
|
||||
},
|
||||
{
|
||||
title: '수수료',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: (_, r) => (
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
className="branch-row-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setFeeBranch(r)
|
||||
setFeeOpen(true)
|
||||
}}
|
||||
>
|
||||
수수료
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '승인여부',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: (_, r) => (
|
||||
r.grant === 1
|
||||
? (
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
className="branch-row-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
grantMut.mutate({ id: r.id, g: 0 })
|
||||
}}
|
||||
>
|
||||
승인
|
||||
</Button>
|
||||
)
|
||||
: (
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
danger
|
||||
className="branch-row-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
grantMut.mutate({ id: r.id, g: 1 })
|
||||
}}
|
||||
>
|
||||
거부
|
||||
</Button>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '수정',
|
||||
width: 70,
|
||||
align: 'center',
|
||||
render: (_, r) => (
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
className="branch-row-btn"
|
||||
onClick={(e) => { e.stopPropagation(); void openEdit(r) }}
|
||||
>
|
||||
수정
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '삭제',
|
||||
width: 70,
|
||||
align: 'center',
|
||||
render: (_, r) => (
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
danger
|
||||
className="branch-row-btn"
|
||||
onClick={(e) => { e.stopPropagation(); confirmDelete(r) }}
|
||||
>
|
||||
삭제
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="stock-page branch-list-page">
|
||||
<div className="stock-title">
|
||||
<div>
|
||||
<h2>
|
||||
<ShopOutlined className="product-title-icon" />
|
||||
영업점 목록
|
||||
</h2>
|
||||
<p>영업점을 검색·등록하고 승인·수수료·상세정보를 관리합니다.</p>
|
||||
</div>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => { form.resetFields(); form.setFieldsValue({ level: 1, isTax: 1 }); setCreateOpen(true) }}>
|
||||
등록
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card className="stock-table-card" size="small">
|
||||
<Space wrap style={{ marginBottom: 12 }} size={8}>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="그룹 선택"
|
||||
style={{ width: 140 }}
|
||||
value={draft.fGroup || undefined}
|
||||
onChange={(v) => setDraft((d) => ({ ...d, fGroup: v ?? '' }))}
|
||||
options={groups.map((g) => ({ value: String(g.id), label: g.name }))}
|
||||
/>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="지역 선택"
|
||||
style={{ width: 120 }}
|
||||
value={draft.fSido || undefined}
|
||||
onChange={(v) => setDraft((d) => ({ ...d, fSido: v ?? '' }))}
|
||||
options={sidoOptions.map((s) => ({ value: s, label: s }))}
|
||||
/>
|
||||
<Input
|
||||
placeholder="아이디"
|
||||
style={{ width: 120 }}
|
||||
value={draft.fUserid}
|
||||
onChange={(e) => setDraft((d) => ({ ...d, fUserid: e.target.value }))}
|
||||
onPressEnter={() => { setApplied(draft); setPage(0) }}
|
||||
/>
|
||||
<Input
|
||||
placeholder="영업점명"
|
||||
style={{ width: 120 }}
|
||||
value={draft.fOffice}
|
||||
onChange={(e) => setDraft((d) => ({ ...d, fOffice: e.target.value }))}
|
||||
onPressEnter={() => { setApplied(draft); setPage(0) }}
|
||||
/>
|
||||
<Input
|
||||
placeholder="이름"
|
||||
style={{ width: 100 }}
|
||||
value={draft.fName}
|
||||
onChange={(e) => setDraft((d) => ({ ...d, fName: e.target.value }))}
|
||||
onPressEnter={() => { setApplied(draft); setPage(0) }}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
onClick={() => { setApplied(draft); setPage(0) }}
|
||||
>
|
||||
검색
|
||||
</Button>
|
||||
<Typography.Text type="secondary">총 {filtered.length}건</Typography.Text>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
size="small"
|
||||
rowKey="id"
|
||||
loading={isLoading}
|
||||
columns={columns}
|
||||
dataSource={pageRows}
|
||||
pagination={{
|
||||
current: page + 1,
|
||||
pageSize: PAGE_SIZE,
|
||||
total: filtered.length,
|
||||
showSizeChanger: false,
|
||||
onChange: (p) => setPage(p - 1),
|
||||
}}
|
||||
onRow={(r) => ({
|
||||
onClick: () => setSelectedId(r.id),
|
||||
className: selectedId === r.id ? 'row-selected' : undefined,
|
||||
})}
|
||||
scroll={{ x: 1400 }}
|
||||
locale={{ emptyText: '영업점이 없습니다.' }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<DraggableModal
|
||||
title="영업점 등록"
|
||||
open={createOpen}
|
||||
onCancel={() => setCreateOpen(false)}
|
||||
onOk={() => form.submit()}
|
||||
confirmLoading={createMut.isPending}
|
||||
okText="등록"
|
||||
width={860}
|
||||
destroyOnClose
|
||||
className="order-form-modal branch-form-modal"
|
||||
styles={{ body: { maxHeight: 'calc(92vh - 56px)', overflowY: 'auto' } }}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
className="order-form-embedded-inner order-form-page reception-apply-form"
|
||||
onFinish={(v) => createMut.mutate(v)}
|
||||
initialValues={{ level: 1, isTax: 1 }}
|
||||
>
|
||||
<FormSection title="계정 정보">
|
||||
<FormGrid>
|
||||
<FormField label="아이디*">
|
||||
<Form.Item name="userid" noStyle rules={[{ required: true, message: '아이디를 입력하세요' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="패스워드*">
|
||||
<Form.Item name="password" noStyle rules={[{ required: true, message: '패스워드를 입력하세요' }]}>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="기본 정보">
|
||||
<FormGrid>
|
||||
<FormField label="상호(영업점명)*">
|
||||
<Form.Item name="officeName" noStyle rules={[{ required: true, message: '영업점명을 입력하세요' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="대표자명*">
|
||||
<Form.Item name="name" noStyle rules={[{ required: true, message: '대표자명을 입력하세요' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="전화">
|
||||
<Form.Item name="tel" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="휴대폰*">
|
||||
<Form.Item name="phone" noStyle rules={[{ required: true, message: '휴대폰을 입력하세요' }]}>
|
||||
<Input placeholder="010-0000-0000" />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="이메일*" full>
|
||||
<Form.Item name="email" noStyle rules={[{ required: true, type: 'email', message: '이메일을 입력하세요' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="주소">
|
||||
<FormGrid>
|
||||
<FormField label="우편번호">
|
||||
<Form.Item name="zipcode" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="주소">
|
||||
<Form.Item name="address" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
<FormField label="상세주소" full>
|
||||
<Form.Item name="addressDetail" noStyle><Input /></Form.Item>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="설정">
|
||||
<FormGrid>
|
||||
<FormField label="레벨">
|
||||
<Form.Item name="level" noStyle>
|
||||
<Select options={[
|
||||
{ value: 1, label: '영업점' },
|
||||
{ value: 5, label: '센터' },
|
||||
{ value: 9, label: '관리자' },
|
||||
{ value: 10, label: '최고관리자' },
|
||||
]} />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="영업점 그룹">
|
||||
<Form.Item name="branchGroupId" noStyle>
|
||||
<Select allowClear placeholder="없음" options={groups.map((g) => ({ value: g.id, label: g.name }))} />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
<FormField label="세금계산서" full>
|
||||
<Form.Item name="isTax" noStyle>
|
||||
<Select options={[
|
||||
{ value: 1, label: '세금계산서발행' },
|
||||
{ value: 0, label: '종합소득세공제' },
|
||||
]} />
|
||||
</Form.Item>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
</FormSection>
|
||||
</Form>
|
||||
</DraggableModal>
|
||||
|
||||
<DraggableModal
|
||||
title="영업점 수정"
|
||||
open={editOpen}
|
||||
onCancel={() => setEditOpen(false)}
|
||||
onOk={() => editForm.submit()}
|
||||
confirmLoading={updateMut.isPending}
|
||||
okText="수정"
|
||||
width={920}
|
||||
destroyOnClose
|
||||
className="order-form-modal branch-form-modal"
|
||||
styles={{ body: { maxHeight: 'calc(92vh - 56px)', overflowY: 'auto' } }}
|
||||
>
|
||||
<Form
|
||||
form={editForm}
|
||||
layout="vertical"
|
||||
className="order-form-embedded-inner order-form-page reception-apply-form"
|
||||
onFinish={(values) => {
|
||||
const phones: string[] = values.notifyPhones || []
|
||||
updateMut.mutate({
|
||||
...values,
|
||||
isPersonal: values.isPersonal,
|
||||
acceptPhone: values.acceptPhone ? 1 : 0,
|
||||
acceptNateon: values.acceptNateon ? 1 : 0,
|
||||
notifyPhones: phones,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<EditBranchFormBody groups={groups} centers={centers} />
|
||||
</Form>
|
||||
</DraggableModal>
|
||||
|
||||
<BranchFeeFlowModals
|
||||
open={feeOpen}
|
||||
branchId={feeBranch?.id ?? null}
|
||||
branchName={feeBranch?.officeName ?? ''}
|
||||
onClose={() => { setFeeOpen(false); setFeeBranch(null) }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user