BillCare 초기 소스 등록
비즈케어 홈즈 홈상품 운용관리(BillCare) 프론트/백엔드/Docker 구성을 exdev git에 등록합니다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button, Checkbox, DatePicker, Input, Select, message } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import Modal from './DraggableModal'
|
||||
import { FormField, FormGrid, FormSection } from './OrderFormLayout'
|
||||
import client, { apiPrefix, unwrap, type ApiResponse } from '../api/client'
|
||||
import { options } from '../pages/care/homesShared'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
selectedIds: number[]
|
||||
progressStatuses: string[]
|
||||
giftStatuses: string[]
|
||||
centerOptions?: string[]
|
||||
onCancel: () => void
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
const GIFT_PLACES = [
|
||||
{ value: 'b01', label: '본사요청' },
|
||||
{ value: 'b02', label: '자체발송' },
|
||||
{ value: '본사', label: '본사발송' },
|
||||
{ value: '영업점', label: '영업점발송' },
|
||||
]
|
||||
|
||||
export default function ReceptionBulkModal({
|
||||
open,
|
||||
selectedIds,
|
||||
progressStatuses,
|
||||
giftStatuses,
|
||||
centerOptions = [],
|
||||
onCancel,
|
||||
onSuccess,
|
||||
}: Props) {
|
||||
const [busy, setBusy] = useState(false)
|
||||
|
||||
const [pgAdminOnly, setPgAdminOnly] = useState(false)
|
||||
const [pgStatus, setPgStatus] = useState<string | undefined>()
|
||||
const [pgNotNotice, setPgNotNotice] = useState(false)
|
||||
const [pgCenterId, setPgCenterId] = useState<string | undefined>()
|
||||
const [pgMemo, setPgMemo] = useState('')
|
||||
const [pgOpended, setPgOpended] = useState('')
|
||||
const [pgCanceled, setPgCanceled] = useState('')
|
||||
|
||||
const [gPlace, setGPlace] = useState<string | undefined>()
|
||||
const [gStatus, setGStatus] = useState<string | undefined>()
|
||||
const [gProgressLog, setGProgressLog] = useState('')
|
||||
const [gDeliveryCompany, setGDeliveryCompany] = useState('')
|
||||
const [gTrackingNumber, setGTrackingNumber] = useState('')
|
||||
const [gRequested, setGRequested] = useState('')
|
||||
const [gSended, setGSended] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
setPgAdminOnly(false)
|
||||
setPgStatus(undefined)
|
||||
setPgNotNotice(false)
|
||||
setPgCenterId(undefined)
|
||||
setPgMemo('')
|
||||
setPgOpended('')
|
||||
setPgCanceled('')
|
||||
setGPlace(undefined)
|
||||
setGStatus(undefined)
|
||||
setGProgressLog('')
|
||||
setGDeliveryCompany('')
|
||||
setGTrackingNumber('')
|
||||
setGRequested('')
|
||||
setGSended('')
|
||||
}, [open])
|
||||
|
||||
const need = () => {
|
||||
if (!selectedIds.length) {
|
||||
message.warning('대상 접수를 선택하세요.')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const run = async (fn: () => Promise<void>) => {
|
||||
setBusy(true)
|
||||
try {
|
||||
await fn()
|
||||
onSuccess()
|
||||
} catch (e: unknown) {
|
||||
message.error(e instanceof Error ? e.message : '일괄 처리에 실패했습니다.')
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
const runProgress = () => {
|
||||
if (!need()) return
|
||||
if (!pgStatus) {
|
||||
message.warning('진행상황을 선택하세요.')
|
||||
return
|
||||
}
|
||||
if (!window.confirm(`선택 ${selectedIds.length}건을 일괄 처리하시겠습니까?`)) return
|
||||
void run(async () => {
|
||||
const { data } = await client.post<ApiResponse<{ count: number }>>(`${apiPrefix()}/homes/receptions/bulk-progress`, {
|
||||
ids: selectedIds,
|
||||
status: pgStatus,
|
||||
memo: pgMemo,
|
||||
centerId: pgCenterId || null,
|
||||
opended: pgOpended || null,
|
||||
canceled: pgCanceled || null,
|
||||
adminOnly: pgAdminOnly,
|
||||
notNotice: pgNotNotice,
|
||||
})
|
||||
const res = unwrap(data) as { count: number }
|
||||
message.success(`${res.count}건 진행상황을 변경했습니다.`)
|
||||
})
|
||||
}
|
||||
|
||||
const runGift = () => {
|
||||
if (!need()) return
|
||||
if (!gStatus) {
|
||||
message.warning('사은품진행상황을 선택하세요.')
|
||||
return
|
||||
}
|
||||
if (!window.confirm(`선택 ${selectedIds.length}건을 일괄 처리하시겠습니까?`)) return
|
||||
void run(async () => {
|
||||
const { data } = await client.post<ApiResponse<{ count: number }>>(`${apiPrefix()}/homes/receptions/bulk-gift`, {
|
||||
ids: selectedIds,
|
||||
place: gPlace || null,
|
||||
status: gStatus,
|
||||
giftProgressLog: gProgressLog,
|
||||
deliveryCompany: gDeliveryCompany || null,
|
||||
trackingNumber: gTrackingNumber || null,
|
||||
requested: gRequested || null,
|
||||
sended: gSended || null,
|
||||
})
|
||||
const res = unwrap(data) as { count: number }
|
||||
message.success(`${res.count}건 사은품진행을 변경했습니다.`)
|
||||
})
|
||||
}
|
||||
|
||||
const runCenterWait = () => {
|
||||
if (!need()) return
|
||||
if (!window.confirm('입력처 정산대기로 일괄처리 하시겠습니까?')) return
|
||||
void run(async () => {
|
||||
const { data } = await client.post<ApiResponse<{ count: number }>>(`${apiPrefix()}/homes/receptions/bulk-center-progress`, {
|
||||
ids: selectedIds,
|
||||
})
|
||||
const res = unwrap(data) as { count: number }
|
||||
message.success(`${res.count}건을 입력처 정산대기로 변경했습니다.`)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`일괄진행 (${selectedIds.length}건 선택)`}
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
footer={null}
|
||||
destroyOnHidden
|
||||
width="96%"
|
||||
style={{ maxWidth: 1040 }}
|
||||
className="reception-bulk-modal reception-apply-modal order-form-modal"
|
||||
styles={{ body: { maxHeight: 'calc(92vh - 56px)', overflowY: 'auto' } }}
|
||||
>
|
||||
<div className="order-form-embedded-inner order-form-page reception-apply-form reception-bulk-form">
|
||||
<FormSection title="진행상황 일괄 처리">
|
||||
<FormGrid>
|
||||
<FormField label="관리자 전용로그" full>
|
||||
<div className="order-form-inline">
|
||||
<Checkbox checked={pgAdminOnly} onChange={(e) => setPgAdminOnly(e.target.checked)}>
|
||||
관리자 전용로그
|
||||
</Checkbox>
|
||||
<span className="bulk-hint">■ 관리자만 보이고 영업점은 보이지 않습니다.</span>
|
||||
</div>
|
||||
</FormField>
|
||||
<FormField label="진행상황" full>
|
||||
<div className="order-form-inline">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="진행상황 선택"
|
||||
value={pgStatus}
|
||||
onChange={(v) => setPgStatus(v)}
|
||||
options={options(progressStatuses)}
|
||||
style={{ flex: 1, minWidth: 180 }}
|
||||
/>
|
||||
<Checkbox checked={pgNotNotice} onChange={(e) => setPgNotNotice(e.target.checked)}>
|
||||
하부점에 전달하지 않음
|
||||
</Checkbox>
|
||||
</div>
|
||||
<p className="bulk-hint-line">■ 퀵전송으로 등록된 접수인 경우 진행상황을 하부점에 알리지 않습니다.</p>
|
||||
<p className="bulk-hint-line">■ 진행상황을 1개 선택후 검색하여야 진행상황을 변경할 수 있습니다.</p>
|
||||
</FormField>
|
||||
<FormField label="입력처" full>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="입력처 선택"
|
||||
value={pgCenterId}
|
||||
onChange={(v) => setPgCenterId(v)}
|
||||
options={options(centerOptions.length ? centerOptions : ['본사'])}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="업무진행메모(LOG)" full>
|
||||
<Input.TextArea rows={3} value={pgMemo} onChange={(e) => setPgMemo(e.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="개통일자">
|
||||
<DatePicker
|
||||
value={pgOpended ? dayjs(pgOpended) : null}
|
||||
onChange={(d) => setPgOpended(d ? d.format('YYYY-MM-DD') : '')}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="해지일자">
|
||||
<DatePicker
|
||||
value={pgCanceled ? dayjs(pgCanceled) : null}
|
||||
onChange={(d) => setPgCanceled(d ? d.format('YYYY-MM-DD') : '')}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
<div className="bulk-foot">
|
||||
<button type="button" className="bulk-submit" disabled={busy} onClick={runProgress}>선택 일괄 처리</button>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="본사발송 사은품 일괄 처리">
|
||||
<FormGrid>
|
||||
<FormField label="사은품 지급처">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="지급처 선택"
|
||||
value={gPlace}
|
||||
onChange={(v) => setGPlace(v)}
|
||||
options={GIFT_PLACES}
|
||||
/>
|
||||
<p className="bulk-hint-line">■ 지급처를 검색하세요.</p>
|
||||
</FormField>
|
||||
<FormField label="사은품진행상황">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="사은품 진행상황 선택"
|
||||
value={gStatus}
|
||||
onChange={(v) => setGStatus(v)}
|
||||
options={options(giftStatuses)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="사은품진행메모(LOG)" full>
|
||||
<Input.TextArea rows={3} value={gProgressLog} onChange={(e) => setGProgressLog(e.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="택배회사">
|
||||
<Input value={gDeliveryCompany} onChange={(e) => setGDeliveryCompany(e.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="송장번호">
|
||||
<Input value={gTrackingNumber} onChange={(e) => setGTrackingNumber(e.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="사은품지급요청일">
|
||||
<DatePicker
|
||||
value={gRequested ? dayjs(gRequested) : null}
|
||||
onChange={(d) => setGRequested(d ? d.format('YYYY-MM-DD') : '')}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="사은품지급완료일">
|
||||
<DatePicker
|
||||
value={gSended ? dayjs(gSended) : null}
|
||||
onChange={(d) => setGSended(d ? d.format('YYYY-MM-DD') : '')}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
<div className="bulk-foot">
|
||||
<button type="button" className="bulk-submit" disabled={busy} onClick={runGift}>선택 일괄 처리</button>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection title="입력처 - 정산대기로 일괄처리">
|
||||
<FormGrid cols={1}>
|
||||
<FormField label="안내" full>
|
||||
<p className="bulk-desc-block" style={{ padding: 0, margin: 0 }}>
|
||||
■ 개통완료된 고객중에 입력처 정산이 진행안된 고객은 입력처 정산대기로 일괄처리 진행할수 있습니다.
|
||||
</p>
|
||||
</FormField>
|
||||
</FormGrid>
|
||||
<div className="bulk-foot">
|
||||
<button type="button" className="bulk-submit" disabled={busy} onClick={runCenterWait}>선택 일괄 처리</button>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
|
||||
<div className="balance-modal-footer" style={{ marginTop: 12 }}>
|
||||
<Button onClick={onCancel}>닫기</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user