Merge branch 'main' of github.com:hainatravel/GHHub

release
YCC 2 years ago
commit 4ed34d79c4

2
.gitignore vendored

@ -22,3 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
/package-lock.json

4043
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -17,6 +17,7 @@ import ReservationNewest from "@/views/reservation/Newest";
import ReservationDetail from "@/views/reservation/Detail"; import ReservationDetail from "@/views/reservation/Detail";
import FeedbackIndex from "@/views/feedback/Index"; import FeedbackIndex from "@/views/feedback/Index";
import FeedbackDetail from "@/views/feedback/Detail"; import FeedbackDetail from "@/views/feedback/Detail";
import ReservationPrint from "@/views/reservation/Print";
configure({ configure({
useProxies: "ifavailable", useProxies: "ifavailable",
@ -36,6 +37,7 @@ const router = createBrowserRouter([
{ index: true, element: <Index /> }, { index: true, element: <Index /> },
{ path: "reservation/newest", element: <ReservationNewest />}, { path: "reservation/newest", element: <ReservationNewest />},
{ path: "reservation/:reservationId", element: <ReservationDetail />}, { path: "reservation/:reservationId", element: <ReservationDetail />},
{ path: "reservation/:reservationId/print", element: <ReservationPrint />},
{ path: "feedback", element: <FeedbackIndex />}, { path: "feedback", element: <FeedbackIndex />},
{ path: "feedback/:feedbackId", element: <FeedbackDetail />}, { path: "feedback/:feedbackId", element: <FeedbackDetail />},
] ]

@ -46,17 +46,56 @@ class Reservation {
}); });
} }
reservationList = [ reservationList = [];
customerList = [
{
title: 'Crane / Gemma Chelse',
description: 'Gender: Male Nationality: United States Passport: 655844449 Expiration Date: 2030-09-07 Birth Date: 1979-12-23',
},
{
title: 'McCracken / Ryan Lee',
description: 'Gender: Female Nationality: United States Passport: 655844450 Expiration Date: 2030-09-07 Birth Date: 1983-05-17',
},
{
title: 'Ramlakhan / Darryl',
description: 'Gender: Female Nationality: United States Passport: 661810034 Expiration Date: 2026-03-16 Birth Date: 2006-07-12',
},
{
title: 'Ramlakhan / Reanne',
description: 'Gender: Male Nationality: United States Passport: 593422145 Expiration Date: 2023-04-25 Birth Date: 2012-03-26',
},
{
title: 'Alexander Daich',
description: 'Gender: Male Nationality: United States Passport: 593422145 Expiration Date: 2023-04-25 Birth Date: 2012-03-26s',
},
];
itineraryList = [
{ {
key: '1', key: '1',
id: '1', day: 'Tue 05-Jul-2022',
referenceNumber: '-', placeTransport: 'Bangkok to Chiang Mai, PG217 Dep 12:15 - Arr 13:35 (Economy class)',
arrivalDate: '-', todayActivities: 'Hotel to airport Transfer (Bangkok), Airport to Hotel Transfer (Chiang Mai), Street Food Tour in Chiang Mai (Morning or Afternoon)',
pax: '-', accommodation: 'The Rim Resort **** (Dahla Junior Suite)',
status: '-', meals: 'B,D'
reservationDate: '-', },
guide: '-', {
} key: '2',
day: 'Wed 06-Jul-2022',
placeTransport: 'Chiang Mai',
todayActivities: 'Elephant Jungle Sanctuary Half Day Tour (Private transfer and Join in activity), Chiang Mai City Lifestyle Experiece and Mountain Doi Suthep Half Day Tour with a rickshaw ride',
accommodation: 'The Rim Resort **** (Dahla Junior Suite)',
meals: 'B'
},
{
key: '3',
day: 'Thu 07-Jul-2022',
placeTransport: 'Chiang Mai to Phuket , VZ414 Dep 10:40 - Arr 12:50 (Economy class)',
todayActivities: 'Hotel to airport Transfer (Chiang Mai), Airport to Hotel Transfer (Phuket )',
accommodation: 'Pamookkoo Resort **** (Family room)',
meals: 'B'
},
]; ];
} }

@ -68,7 +68,7 @@ function App() {
style={{ style={{
minHeight: "100vh", minHeight: "100vh",
}}> }}>
<Header className="header"> <Header className="header" style={{ position: 'sticky', top: 0, zIndex: 1, width: '100%' }}>
<Row gutter={{ md: 24 }} justify="end"> <Row gutter={{ md: 24 }} justify="end">
<Col span={22}> <Col span={22}>
<img src={AppLogo} className="logo" alt="App logo" /> <img src={AppLogo} className="logo" alt="App logo" />

@ -3,71 +3,37 @@ import { useEffect } from 'react';
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { toJS } from "mobx"; import { toJS } from "mobx";
import moment from "moment"; import moment from "moment";
import { Row, Col, Space, Button, Table, Tag, Typography, DatePicker } from 'antd'; import { Row, Col, Space, Button, Table, Input, Typography, List } from 'antd';
import { useStore } from '../../stores/StoreContext.js'; import { useStore } from '../../stores/StoreContext.js';
const { Title } = Typography; const { Title } = Typography;
const { TextArea } = Input;
const dataSource = [ const itineraryListColumns = [
{ {
key: '1', title: 'Day',
name: '2', dataIndex: 'day',
age: '二', key: 'day',
address: '5月2日',
}, },
{ {
key: '2', title: 'Place & Transport',
name: '胡彦祖', dataIndex: 'placeTransport',
age: 42, key: 'placeTransport',
address: '西湖区湖底公园1号',
},
];
const columns = [
{
title: '天数',
dataIndex: 'name',
key: 'name',
},
{
title: '星期',
dataIndex: 'age',
key: 'age',
},
{
title: '日期',
dataIndex: 'address',
key: 'address',
}, },
{ {
title: '始发城市', title: 'Todays Activities',
dataIndex: 'address', dataIndex: 'todayActivities',
key: 'address', key: 'todayActivities',
}, },
{ {
title: '抵达城市', title: 'Accommodation',
dataIndex: 'address', dataIndex: 'accommodation',
key: 'address', key: 'accommodation',
}, },
{ {
title: '交通', title: 'Meals',
dataIndex: 'address', dataIndex: 'meals',
key: 'address', key: 'meals',
},
{
title: '酒店',
dataIndex: 'address',
key: 'address',
},
{
title: '餐饮 [午、晚]',
dataIndex: 'address',
key: 'address',
},
{
title: '景点及旅游服务安排',
dataIndex: 'address',
key: 'address',
}, },
]; ];
@ -75,7 +41,7 @@ function Detail() {
const navigate = useNavigate(); const navigate = useNavigate();
const { reservationId } = useParams(); const { reservationId } = useParams();
const { reservationStore } = useStore(); const { reservationStore } = useStore();
const { reservationList } = reservationStore; const { itineraryList, customerList } = reservationStore;
useEffect(() => { useEffect(() => {
console.info('Detail.useEffect: ' + reservationId); console.info('Detail.useEffect: ' + reservationId);
@ -85,17 +51,62 @@ function Detail() {
<Space direction="vertical" style={{ width: '100%' }}> <Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={{ md: 24 }}> <Row gutter={{ md: 24 }}>
<Col span={20}> <Col span={20}>
<Title level={4}>Reservation Detail 中华游111029-N111025076</Title> <Title level={4}>Booking Number: 220629-W220420009; Arrival date: 2023-12-15; Guide: Giffigan</Title>
</Col> </Col>
<Col span={4}> <Col span={4}>
<Button type="link" onClick={() => navigate('/reservation/newest')}>Return</Button> <Button type="link" onClick={() => navigate('/reservation/newest')}>Return</Button>
</Col> </Col>
</Row> </Row>
<Row>
<Col span={2}>
<Button type="link" onClick={() => navigate(`/reservation/${reservationId}/print`)}>Print Reservation</Button>
</Col>
<Col span={2}>
<Button type="link" onClick={() => console.info('')}>Print Feedback</Button>
</Col>
<Col span={2}>
<Button type="link" onClick={() => console.info('')}>Print Customer Card</Button>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={5}>Itinerary Overview</Title>
</Col>
</Row>
<Row> <Row>
<Col span={24}> <Col span={24}>
<Table dataSource={dataSource} columns={columns} /> <Table
pagination={{
hideOnSinglePage: true
}}
dataSource={toJS(itineraryList)} columns={itineraryListColumns}
/>
</Col> </Col>
</Row> </Row>
<Row>
<Col span={24}>
<List
header={<Title level={5}>Customer Names (Full name as it appears on passport)</Title>}
itemLayout="horizontal"
dataSource={toJS(customerList)}
renderItem={(item, index) => (
<List.Item>
<List.Item.Meta
title={<span>{index + 1}. {item.title}</span>}
description={item.description}
/>
</List.Item>
)}
/>
</Col>
</Row>
<Row>
<Col span={24}>
<TextArea rows={4} placeholder="如贵社对计划中的行程安排有任何建议请联系组团人本人。中华游欢迎大家对我们的工作提出合理化建议。" maxLength={6} />
</Col>
</Row>
<Button type="primary" onClick={() => console.info('submit')}>Submit</Button>
</Space> </Space>
); );
} }

@ -1,100 +1,205 @@
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { toJS } from "mobx"; import { toJS } from "mobx";
import { Row, Col, Space, Button, Table, Input, Typography, DatePicker, Radio } from 'antd'; import { Row, Col, Space, Button, Table, Input, Typography, DatePicker, Radio, Modal } from 'antd';
import { useStore } from '@/stores/StoreContext.js'; import { useStore } from '@/stores/StoreContext.js';
const { Title } = Typography; const { Title } = Typography;
const reservationListColumns = [
{
title: 'Reference number',
dataIndex: 'referenceNumber',
key: 'Reference number',
render: (text, record) => <NavLink to={`/reservation/${record.id}`}>{text}</NavLink>,
},
{
title: 'Arrival date',
dataIndex: 'arrivalDate',
key: 'Arrival date',
},
{
title: 'Pax',
key: 'Pax',
dataIndex: 'pax'
},
{
title: 'Status',
key: 'Status',
dataIndex: 'status'
},
{
title: 'Reservation date',
key: 'Reservation date',
dataIndex: 'reservationDate'
},
{
title: 'Guide',
key: 'Guide',
dataIndex: 'guide'
},
];
function Newest() { function Newest() {
const reservationListColumns = [
{
title: 'Reference number',
dataIndex: 'referenceNumber',
key: 'Reference number',
render: (text, record) => <NavLink to={`/reservation/${record.id}`}>{text}</NavLink>,
},
{
title: 'Arrival date',
dataIndex: 'arrivalDate',
key: 'Arrival date',
},
{
title: 'Pax',
key: 'Pax',
dataIndex: 'pax'
},
{
title: 'Status',
key: 'Status',
dataIndex: 'status'
},
{
title: 'Reservation date',
key: 'Reservation date',
dataIndex: 'reservationDate'
},
{
title: 'Guide',
key: 'Guide',
dataIndex: 'guide',
render: guideRender
},
];
function guideRender(text, record) {
if (record.key === '3') {
return (
<Space size="middle">
<Button type="link" onClick={() => showModal()}>Fill in</Button>
</Space>
);
} else {
return (
<Space size="middle">
<span>{record.guide}</span>
<Button type="link" onClick={() => showModal()}>Modify</Button>
</Space>
);
}
}
const { reservationStore } = useStore(); const { reservationStore } = useStore();
const { reservationList } = reservationStore; const { reservationList } = reservationStore;
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
useEffect(() => { useEffect(() => {
console.info('Newest.useEffect'); console.info('Newest.useEffect');
}, []); }, []);
return ( return (
<Space direction="vertical" style={{ width: '100%' }}> <>
<Title level={3}>Newest Reservations</Title> <Modal
<Row gutter={{ md: 24 }}> title="团导游安排预览"
<Col span={5}> centered
open={isModalOpen} onOk={handleOk} onCancel={handleCancel}
>
<Space direction="vertical" style={{ width: '100%' }}>
<Row>
<Col span={24}>
<Table
bordered
pagination={{
hideOnSinglePage: true
}}
columns={[
{
title: 'City',
dataIndex: 'city',
key: 'city'
},
{
title: 'Guide',
dataIndex: 'guide',
key: 'guide'
}
]}
dataSource={[
{
city: 'Guilin',
guide: 'Giffigan'
}
]}
/>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={5}>Guide</Title>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={16}>
<Input placeholder="Guide" />
</Col>
<Col span={8}>
<Button type='primary' onClick={() => {}}>Search</Button>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Radio.Group options={[
{
label: 'Jim',
value: '1',
},
{
label: 'Giffigan',
value: '2',
},
{
label: 'Herry',
value: '3',
},
{
label: 'Giffigan',
value: '4',
},
{
label: 'Herry',
value: '5',
},
{
label: 'Giffigan',
value: '6',
},
{
label: 'Herry',
value: '7',
},
]} />
</Col>
</Row>
</Space>
</Modal>
<Space direction="vertical" style={{ width: '100%' }}>
<Title level={3}>Newest Reservations</Title>
<Row gutter={{ md: 24 }}>
<Col span={4}>
<Input placeholder="Reference number" /> <Input placeholder="Reference number" />
</Col> </Col>
<Col span={6}> <Col span={6}>
<Space direction="horizontal"> <Space direction="horizontal">
Arrival Date Arrival Date
<DatePicker.RangePicker <DatePicker.RangePicker
allowClear={true} allowClear={true}
inputReadOnly={true} inputReadOnly={true}
placeholder={['Arrival Date', '']} placeholder={['From', 'Thru']}
/> />
</Space> </Space>
</Col> </Col>
<Col span={8}> <Col span={14}>
<Space direction="horizontal"> <Button type='primary' onClick={() => reservationStore.fetchRecent()}>Search</Button>
快速选择 </Col>
<Radio.Group </Row>
options={[ <Row>
{ label: '未确认', value: '1' }, <Col span={24}>
{ label: '已确认', value: '2' }, <Table
{ label: '未录入导游', value: '3' }, title={() => 'Reservations without the tour guide information will be highlighted in red if the arrival date is within 3 days.'}
{ label: '有变更', value: '4' }, bordered
{ label: '已取消', value: '5' }, pagination={{
{ label: '全部', value: '6' }, position: ['bottomCenter'],
]} current: 1,
value={'6'} pageSize: 10,
optionType="button" total: 200
buttonStyle="solid" }}
columns={reservationListColumns} dataSource={toJS(reservationList)}
/> />
</Space> </Col>
</Col> </Row>
<Col span={4}> </Space>
<Button type='primary' onClick={() => reservationStore.fetchRecent()}>Search</Button> </>
</Col>
</Row>
<Row>
<Col span={24}>
<Table columns={reservationListColumns} dataSource={toJS(reservationList)} />
</Col>
</Row>
</Space>
); );
} }

@ -0,0 +1,138 @@
import { useParams, useNavigate } from "react-router-dom";
import { useEffect } from 'react';
import { observer } from "mobx-react";
import { toJS } from "mobx";
import moment from "moment";
import { Row, Col, Space, Table, Typography, List, Watermark } from 'antd';
import { useStore } from '../../stores/StoreContext.js';
const { Title } = Typography;
const itineraryListColumns = [
{
title: 'Day',
dataIndex: 'day',
key: 'day',
},
{
title: 'Place & Transport',
dataIndex: 'placeTransport',
key: 'placeTransport',
},
{
title: 'Todays Activities',
dataIndex: 'todayActivities',
key: 'todayActivities',
},
{
title: 'Accommodation',
dataIndex: 'accommodation',
key: 'accommodation',
},
{
title: 'Meals',
dataIndex: 'meals',
key: 'meals',
},
];
function Print() {
const navigate = useNavigate();
const { reservationId } = useParams();
const { reservationStore } = useStore();
const { itineraryList, customerList } = reservationStore;
useEffect(() => {
console.info('Detail.useEffect: ' + reservationId);
}, [reservationId]);
return (
<Watermark content={['Global Highlights', 'Discovery Your Way!']}>
<Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={3}>Booking for Mr. Prasanna Venkatesa Nathan(United States) trip</Title>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Booking Number: 220629-W220420009</Title>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Customer Names (Full name as it appears on passport):</Title>
</Col>
</Row>
<Row>
<Col span={24}>
<List
itemLayout="horizontal"
dataSource={toJS(customerList)}
renderItem={(item, index) => (
<List.Item>
<List.Item.Meta
title={<span>{item.title}</span>}
description={item.description}
/>
</List.Item>
)}
/>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Hotels and Room:</Title>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Guide Language: English</Title>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Notes:</Title>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Itinerary Overview:</Title>
</Col>
</Row>
<Row>
<Col span={24}>
<Table
pagination={{
hideOnSinglePage: true
}}
dataSource={toJS(itineraryList)} columns={itineraryListColumns}
/>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Detailed Tour Itinerary</Title>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Packaged Price Includes:</Title>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Packaged Price Excludes:</Title>
</Col>
</Row>
<Row gutter={{ md: 24 }}>
<Col span={24}>
<Title level={4}>Asia Highlights Contact:</Title>
</Col>
</Row>
</Space>
</Watermark>
);
}
export default observer(Print);
Loading…
Cancel
Save