789 lines
28 KiB
TypeScript
789 lines
28 KiB
TypeScript
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>
|
|
)
|
|
}
|