03cd93e1f2
비즈케어 홈즈 홈상품 운용관리(BillCare) 프론트/백엔드/Docker 구성을 exdev git에 등록합니다. Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
6.2 KiB
TypeScript
143 lines
6.2 KiB
TypeScript
import { useState } from 'react'
|
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
|
import { useNavigate, useParams } from 'react-router-dom'
|
|
import dayjs, { type Dayjs } from 'dayjs'
|
|
import { Button, Card, DatePicker, Select, Space, Typography, message } from 'antd'
|
|
import CareTable from '../../components/CareTable'
|
|
import { ArrowLeftOutlined, SearchOutlined } from '@ant-design/icons'
|
|
import client, { apiPrefix, unwrap, type ApiResponse } from '../../api/client'
|
|
|
|
const hours = Array.from({ length: 24 }, (_, i) => ({ value: i, label: `${String(i).padStart(2, '0')}시` }))
|
|
const minutes = [0, 10, 20, 30, 40, 50].map((v) => ({ value: v, label: `${String(v).padStart(2, '0')}분` }))
|
|
const money = (v: unknown) => (v == null || v === '' ? '-' : Number(v).toLocaleString())
|
|
|
|
type OpenRow = Record<string, unknown> & { id: number }
|
|
|
|
export default function OpenUnitCostApplyPage() {
|
|
const { id } = useParams()
|
|
const navigate = useNavigate()
|
|
const tableId = Number(id)
|
|
const [from, setFrom] = useState<Dayjs>(dayjs())
|
|
const [to, setTo] = useState<Dayjs>(dayjs())
|
|
const [fromHour, setFromHour] = useState<number | undefined>()
|
|
const [fromMinute, setFromMinute] = useState<number | undefined>()
|
|
const [toHour, setToHour] = useState<number | undefined>()
|
|
const [toMinute, setToMinute] = useState<number | undefined>()
|
|
const [searched, setSearched] = useState(false)
|
|
const [filters, setFilters] = useState<Record<string, unknown>>({})
|
|
const [selected, setSelected] = useState<number[]>([])
|
|
|
|
const enabled = searched && !!filters.dateFrom && !!filters.dateTo
|
|
const query = useQuery({
|
|
queryKey: ['unitcost-apply', tableId, filters],
|
|
queryFn: async () => {
|
|
const { data } = await client.get<ApiResponse<{ total: number; list: OpenRow[]; table: Record<string, unknown> }>>(
|
|
`${apiPrefix()}/unitcosts/${tableId}/apply-search`,
|
|
{ params: filters },
|
|
)
|
|
return unwrap(data)
|
|
},
|
|
enabled: enabled && Number.isFinite(tableId) })
|
|
|
|
const apply = useMutation({
|
|
mutationFn: async () => {
|
|
if (!selected.length) throw new Error('적용할 개통을 선택하세요.')
|
|
const { data } = await client.post(`${apiPrefix()}/unitcosts/${tableId}/apply`, { ids: selected })
|
|
return unwrap(data as ApiResponse<{ updated: number }>)
|
|
},
|
|
onSuccess: (data) => {
|
|
message.success(`${data.updated}건에 정책단가를 적용했습니다.`)
|
|
setSelected([])
|
|
query.refetch()
|
|
},
|
|
onError: (e: Error) => message.error(e.message) })
|
|
|
|
const onSearch = () => {
|
|
if (!from || !to) {
|
|
message.warning('개통일을 선택하세요.')
|
|
return
|
|
}
|
|
if (to.diff(from, 'day') > 2) {
|
|
message.warning('최대 3일까지 조회할 수 있습니다.')
|
|
return
|
|
}
|
|
setSearched(true)
|
|
setSelected([])
|
|
setFilters({
|
|
dateFrom: from.format('YYYY-MM-DD'),
|
|
dateTo: to.format('YYYY-MM-DD'),
|
|
fromHour,
|
|
fromMinute,
|
|
toHour,
|
|
toMinute })
|
|
}
|
|
|
|
const tableLabel = query.data?.table
|
|
? `${query.data.table.telecom ?? ''} (${query.data.table.incompanyName ?? ''})`
|
|
: ''
|
|
|
|
const columns = [
|
|
{ title: '개통일', dataIndex: 'opendate', width: 100 },
|
|
{ title: '통신사', dataIndex: 'telecom', width: 70 },
|
|
{ title: '출고처', dataIndex: 'outcompanyName', width: 110, ellipsis: true },
|
|
{ title: '고객명', dataIndex: 'name', width: 90 },
|
|
{ title: '종류', dataIndex: 'openhow', width: 80 },
|
|
{ title: '모델명', dataIndex: 'pmodel', width: 140, ellipsis: true },
|
|
{ title: '요금제', dataIndex: 'plan', width: 120, ellipsis: true },
|
|
{ title: '기본정책', dataIndex: 'basicprice1', width: 100, align: 'right' as const, render: money },
|
|
]
|
|
|
|
return (
|
|
<div className="stock-page unitcost-apply-page">
|
|
<div className="stock-title">
|
|
<div>
|
|
<h2>정책단가표</h2>
|
|
<p>통신사별로 정책단가표를 작성하여 기본정책금액을 일괄적으로 적용하실 수 있습니다.</p>
|
|
</div>
|
|
<Button icon={<ArrowLeftOutlined />} onClick={() => navigate('/care/open/unitcost')}>목록</Button>
|
|
</div>
|
|
|
|
<Card className="stock-filter-card" size="small" title={`${tableLabel} 정책적용`}>
|
|
<Space wrap>
|
|
<DatePicker value={from} onChange={(v) => v && setFrom(v)} />
|
|
<Select allowClear placeholder="-시-" className="w-100" options={hours} value={fromHour} onChange={setFromHour} />
|
|
<Select allowClear placeholder="-분-" className="w-100" options={minutes} value={fromMinute} onChange={setFromMinute} />
|
|
<span>~</span>
|
|
<DatePicker value={to} onChange={(v) => v && setTo(v)} />
|
|
<Select allowClear placeholder="-시-" className="w-100" options={hours} value={toHour} onChange={setToHour} />
|
|
<Select allowClear placeholder="-분-" className="w-100" options={minutes} value={toMinute} onChange={setToMinute} />
|
|
<Button type="primary" icon={<SearchOutlined />} onClick={onSearch}>검색</Button>
|
|
<Typography.Text type="danger">* 최대 3일</Typography.Text>
|
|
</Space>
|
|
</Card>
|
|
|
|
<Card className="stock-table-card sheet-result-card" size="small">
|
|
{!enabled ? (
|
|
<div className="sheet-empty">
|
|
<div>단가표를 적용할 개통일자를 선택 후 검색해주세요.</div>
|
|
<div className="unitcost-apply-sub">(신규/MNP/기변/보상 개통자료만 검색됩니다. 유심개통제외)</div>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="recovery-table-head">
|
|
<Typography.Text>검색된 개통 <b>{query.data?.total ?? 0}</b> 건</Typography.Text>
|
|
<Button type="primary" loading={apply.isPending} onClick={() => apply.mutate()}>선택 개통에 정책적용</Button>
|
|
</div>
|
|
<CareTable
|
|
rowKey="id"
|
|
size="small"
|
|
loading={query.isLoading}
|
|
dataSource={query.data?.list}
|
|
columns={columns}
|
|
rowSelection={{
|
|
selectedRowKeys: selected,
|
|
onChange: (keys) => setSelected(keys.map(Number)) }}
|
|
pagination={{ pageSize: 15, showTotal: (total) => `총 ${total}건` }}
|
|
/>
|
|
</>
|
|
)}
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|