BillCare 초기 소스 등록
비즈케어 홈즈 홈상품 운용관리(BillCare) 프론트/백엔드/Docker 구성을 exdev git에 등록합니다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import dayjs from 'dayjs'
|
||||
import { Button, Card, Checkbox, Col, DatePicker, Form, Input, InputNumber, Radio, Row, Select, Space, message } from 'antd'
|
||||
import { HomeOutlined } from '@ant-design/icons'
|
||||
import client, { apiPrefix, unwrap, type ApiResponse } from '../../api/client'
|
||||
import { resourceApi } from '../../api/resources'
|
||||
|
||||
const productOptions = [
|
||||
'인터넷', 'TV', '집전화', '인터넷전화', '신용카드', '기타', '홈IOT', '동반', '후결합', '재약정', '소호', '원스톱', '렌탈',
|
||||
]
|
||||
const options = (values: string[]) => values.map((value) => ({ value, label: value }))
|
||||
|
||||
export default function OpenHomeSaleWritePage() {
|
||||
const navigate = useNavigate()
|
||||
const [search] = useSearchParams()
|
||||
const editId = search.get('id') ? Number(search.get('id')) : null
|
||||
const [form] = Form.useForm()
|
||||
|
||||
const companies = useQuery({ queryKey: ['homesale-write-companies'], queryFn: () => resourceApi.list('companies', { size: 200 }) })
|
||||
const inOptions = useMemo(
|
||||
() => (companies.data?.items ?? []).filter((c) => String(c.type) === 'IN').map((c) => ({ value: c.id, label: String(c.name) })),
|
||||
[companies.data],
|
||||
)
|
||||
|
||||
const detail = useQuery({
|
||||
queryKey: ['homesale-detail', editId],
|
||||
queryFn: async () => {
|
||||
const { data } = await client.get<ApiResponse<Record<string, unknown>>>(`${apiPrefix()}/homesales/${editId}`)
|
||||
return unwrap(data)
|
||||
},
|
||||
enabled: Boolean(editId) })
|
||||
|
||||
useEffect(() => {
|
||||
if (!detail.data) return
|
||||
const row = detail.data
|
||||
const phone = String(row.phone ?? '').replace(/\D/g, '')
|
||||
form.setFieldsValue({
|
||||
...row,
|
||||
installDate: row.installDate ? dayjs(String(row.installDate)) : dayjs(),
|
||||
receiptDate: row.receiptDate ? dayjs(String(row.receiptDate)) : dayjs(),
|
||||
products: String(row.products ?? '').split(',').map((v) => v.trim()).filter(Boolean),
|
||||
phone1: phone.slice(0, 3) || '010',
|
||||
phone2: phone.slice(3, 7),
|
||||
phone3: phone.slice(7, 11),
|
||||
status: row.status === '대기' ? '판매대기' : '판매완료' })
|
||||
}, [detail.data, form])
|
||||
|
||||
const save = useMutation({
|
||||
mutationFn: async (values: Record<string, unknown>) => {
|
||||
const products = Array.isArray(values.products) ? values.products.join(',') : String(values.products ?? '')
|
||||
const payload: Record<string, unknown> = {
|
||||
...values,
|
||||
products,
|
||||
status: values.status === '판매대기' ? '대기' : '완료',
|
||||
phone: `${values.phone1}-${values.phone2}-${values.phone3}`,
|
||||
installDate: values.installDate ? dayjs(values.installDate as dayjs.Dayjs).format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD'),
|
||||
receiptDate: values.receiptDate ? dayjs(values.receiptDate as dayjs.Dayjs).format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD'),
|
||||
settleAmount: values.settleAmount ?? 0,
|
||||
margin: values.margin ?? 0,
|
||||
extraPolicy: values.extraPolicy ?? 0 }
|
||||
delete payload.phone1
|
||||
delete payload.phone2
|
||||
delete payload.phone3
|
||||
if (editId) {
|
||||
const { data } = await client.put(`${apiPrefix()}/homesales/${editId}`, payload)
|
||||
return unwrap(data as ApiResponse)
|
||||
}
|
||||
const { data } = await client.post(`${apiPrefix()}/homesales`, payload)
|
||||
return unwrap(data as ApiResponse)
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success(editId ? '수정했습니다.' : '홈상품판매를 등록했습니다.')
|
||||
navigate('/care/open/home')
|
||||
},
|
||||
onError: (e: Error) => message.error(e.message) })
|
||||
|
||||
return (
|
||||
<div className="home-write-page">
|
||||
<div className="stock-title">
|
||||
<div>
|
||||
<h2><HomeOutlined style={{ marginRight: 8 }} />홈상품판매 등록</h2>
|
||||
<p>판매하신 정보를 아래의 양식에 맞게 차례대로 입력해주세요. ('필수' 표시는 필수항목입니다.)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
labelCol={{ flex: '100px' }}
|
||||
wrapperCol={{ flex: 1 }}
|
||||
initialValues={{
|
||||
status: '판매완료',
|
||||
installDate: dayjs(),
|
||||
receiptDate: dayjs(),
|
||||
phone1: '010',
|
||||
missingDocs: '관계없음',
|
||||
products: [],
|
||||
extraPolicy: 0,
|
||||
settleAmount: 0 }}
|
||||
onFinish={(values) => save.mutate(values)}
|
||||
>
|
||||
<Card className="open-section" size="small" title="가입정보">
|
||||
<Row gutter={24}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="status" label="상태" rules={[{ required: true }]}>
|
||||
<Radio.Group options={[{ value: '판매대기', label: '판매대기' }, { value: '판매완료', label: '판매완료' }]} />
|
||||
</Form.Item>
|
||||
<Form.Item name="installDate" label="설치일" rules={[{ required: true }]}>
|
||||
<DatePicker className="full-width" />
|
||||
</Form.Item>
|
||||
<Form.Item name="receiptDate" label="접수일" rules={[{ required: true }]}>
|
||||
<DatePicker className="full-width" />
|
||||
</Form.Item>
|
||||
<Form.Item name="outcompanyName" label="출고처" rules={[{ required: true, message: '출고처를 입력하세요' }]}>
|
||||
<Input placeholder="출고처명을 입력해주세요." />
|
||||
</Form.Item>
|
||||
<Form.Item name="incompanyId" label="입고처">
|
||||
<Select allowClear placeholder="- 선택 -" options={inOptions} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="customerName" label="고객명" rules={[{ required: true, message: '고객명을 입력하세요' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="휴대폰" required>
|
||||
<Space.Compact>
|
||||
<Form.Item name="phone1" noStyle rules={[{ required: true }]}>
|
||||
<Select className="w-100" options={options(['010', '011', '016', '017', '018', '019'])} />
|
||||
</Form.Item>
|
||||
<Form.Item name="phone2" noStyle rules={[{ required: true }]}>
|
||||
<Input className="w-100" maxLength={4} />
|
||||
</Form.Item>
|
||||
<Form.Item name="phone3" noStyle rules={[{ required: true }]}>
|
||||
<Input className="w-100" maxLength={4} />
|
||||
</Form.Item>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
<Form.Item name="telecom" label="통신사">
|
||||
<Select allowClear placeholder="- 선택 -" options={options(['SKT', 'KT', 'LGU+'])} />
|
||||
</Form.Item>
|
||||
<Form.Item name="outCategory" label="출고분류">
|
||||
<Select allowClear options={options(['판매점', '도매'])} />
|
||||
</Form.Item>
|
||||
<Form.Item name="employee" label="직원">
|
||||
<Select allowClear options={options(['demo'])} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item name="address" label="주소">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
<Card className="open-section" size="small" title="상품">
|
||||
<Form.Item name="products" rules={[{ required: true, message: '판매하신 상품을 선택해주세요.' }]}>
|
||||
<Checkbox.Group className="home-product-checks" options={productOptions.map((v) => ({ value: v, label: v }))} />
|
||||
</Form.Item>
|
||||
<p className="home-product-hint">판매하신 상품을 선택해주세요.</p>
|
||||
</Card>
|
||||
|
||||
<Card className="open-section" size="small" title="정산정보">
|
||||
<Row gutter={24}>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="missingDocs" label="미비서류">
|
||||
<Select options={options(['관계없음', '신분증', '통장사본', '기타'])} />
|
||||
</Form.Item>
|
||||
<Form.Item name="extraPolicy" label="추가정책">
|
||||
<InputNumber className="full-width" controls={false} addonAfter="원" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="settleAmount"
|
||||
label="정산금액"
|
||||
extra={<span className="unitcost-extra">* 각 상품 정책금액 + 추가정책 (마이너스 표기)</span>}
|
||||
>
|
||||
<InputNumber className="full-width home-settle-input" controls={false} addonAfter="원" />
|
||||
</Form.Item>
|
||||
<Form.Item name="margin" label="마진">
|
||||
<InputNumber className="full-width" controls={false} addonAfter="원" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
<Form.Item name="memo" label="메모">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<div className="open-actions">
|
||||
<Button onClick={() => navigate('/care/open/home')}>목록</Button>
|
||||
<Button type="primary" htmlType="submit" loading={save.isPending}>{editId ? '수정' : '등록'}</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user