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

# Conflicts:
#	src/views/reservation/Detail.jsx
feature/price_manager
Jimmy Liow 1 year ago
commit 9071e87bc7

@ -0,0 +1,5 @@
{
"report": {
"GetReport": "Get Report"
}
}

@ -0,0 +1,5 @@
{
"report": {
"GetReport": "获取报告"
}
}

@ -0,0 +1,16 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Button } from 'antd';
import { isNotEmpty } from '@/utils/commons';
const BackBtn = ({to, ...props}) => {
const { t } = useTranslation();
const navigate = useNavigate();
return (
<>
{isNotEmpty(to) ? <Link to={to}>{t('Back')}</Link> : <Button type='link' onClick={() => navigate(-1)}>{t('Back')}</Button>}
</>
);
};
export default BackBtn;

@ -14,10 +14,11 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => {
const [formValues, setFormValues] = useFormStore((state) => [state.formValues, state.setFormValues]); const [formValues, setFormValues] = useFormStore((state) => [state.formValues, state.setFormValues]);
const [formValuesToSub, setFormValuesToSub] = useFormStore((state) => [state.formValuesToSub, state.setFormValuesToSub]); const [formValuesToSub, setFormValuesToSub] = useFormStore((state) => [state.formValuesToSub, state.setFormValuesToSub]);
const [form] = Form.useForm(); const [form] = Form.useForm();
const { sort, hides, shows, fieldProps } = { const { sort, hides, shows, fieldProps, fieldComProps } = {
sort: '', sort: '',
// initialValue: '', // initialValue: '',
fieldProps: '', fieldProps: '',
fieldComProps: '',
hides: [], hides: [],
shows: [], shows: [],
...props.defaultValue, ...props.defaultValue,
@ -87,7 +88,7 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => {
<Form form={form} name='advanced_search' className='orders-search-form' onFinish={onFinish} onValuesChange={onValuesChange}> <Form form={form} name='advanced_search' className='orders-search-form' onFinish={onFinish} onValuesChange={onValuesChange}>
{/* <EditableContext.Provider value={form}> */} {/* <EditableContext.Provider value={form}> */}
<Row gutter={16}> <Row gutter={16}>
{getFields({ sort, initialValue: readValues, hides, shows, fieldProps, form, presets, t })} {getFields({ sort, initialValue: readValues, hides, shows, fieldProps, fieldComProps, form, presets, t })}
{/* 'textAlign': 'right' */} {/* 'textAlign': 'right' */}
<Col flex='1 0 90px' className='flex justify-normal items-start' > <Col flex='1 0 90px' className='flex justify-normal items-start' >
<Space align='center'> <Space align='center'>
@ -107,7 +108,7 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => {
}; };
function getFields(props) { function getFields(props) {
const { fieldProps, form, presets, t } = props; const { fieldProps, fieldComProps, form, presets, t } = props;
const bigCol = 4 * 2; const bigCol = 4 * 2;
const midCol = 6; const midCol = 6;
const layoutProps = { const layoutProps = {
@ -136,43 +137,34 @@ function getFields(props) {
99, 99,
<Form.Item name='referenceNo'> <Form.Item name='referenceNo'>
<Input placeholder={t('group:RefNo')} allowClear /> <Input placeholder={t('group:RefNo')} allowClear />
</Form.Item>,4 </Form.Item>,
fieldProps?.referenceNo?.col || 4
), ),
item( item(
'invoiceStatus', 'invoiceStatus',
99, 99,
<Form.Item name={`invoiceStatus`} initialValue={at(props, 'initialValue.invoiceStatus')[0] || (fieldProps?.invoiceStatus?.show_all ? { key: '-1', label: '所有' } : undefined)}> <Form.Item name={`invoiceStatus`} initialValue={at(props, 'initialValue.invoiceStatus')[0] || { value: '0', label: 'Status' }}>
<Select style={{ width: '100%' }} placeholder='所有状态' labelInValue> <Select
{fieldProps?.invoiceStatus?.show_all && ( style={{ width: '100%' }}
<Select.Option key='所有' value='-1'> labelInValue
所有 options={[
</Select.Option> { value: '0', label: 'Status' },
)} { value: '1', label: 'Not submitted' },
<Select.Option key='已成行' value='1'> { value: '2', label: 'Submitted' },
已成行 { value: '3', label: 'Travel advisor approved' },
</Select.Option> { value: '4', label: 'Finance Dept arrproved' },
<Select.Option key='未成行' value='0'> { value: '5', label: 'Paid' },
未成行 ]}
</Select.Option> />
</Select>
</Form.Item>, </Form.Item>,
3 fieldProps?.referenceNo?.col || 3
), ),
item( item(
'dates', 'dates',
99, 99,
<Form.Item name={'dates'} {...fieldProps.dates} initialValue={at(props, 'initialValue.dates')[0]}> <Form.Item name={'dates'} label={t('group:ArrivalDate')} {...fieldProps.dates} initialValue={at(props, 'initialValue.dates')[0]}>
{/* <DatePickerCharts isform={true} {...fieldProps.dates} form={form} /> */} {/* <DatePickerCharts isform={true} {...fieldProps.dates} form={form} /> */}
<RangePicker <RangePicker allowClear={true} inputReadOnly={true} presets={presets} placeholder={['From', 'Thru']} {...fieldComProps.dates} />
allowClear={true}
inputReadOnly={true}
presets={presets}
// defaultValue={toJS(arrivalDateRange)}
placeholder={['From', 'Thru']}
// onChange={(dateRange) => {
// reservationStore.updatePropertyValue('arrivalDateRange', dateRange == null ? [] : dateRange)
// }}
/>
</Form.Item>, </Form.Item>,
fieldProps?.dates?.col || midCol fieldProps?.dates?.col || midCol
), ),

@ -17,12 +17,16 @@ i18n
backend: { backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json', loadPath: '/locales/{{lng}}/{{ns}}.json',
}, },
ns: ['common', 'group'], ns: ['common', 'group', 'vendor'],
defaultNS: 'common', defaultNS: 'common',
// detection: { detection: {
// convertDetectedLanguage: 'Iso15897', // convertDetectedLanguage: 'Iso15897',
// convertDetectedLanguage: (lng) => lng.replace('-', '_') convertDetectedLanguage: (lng) => {
// }, const langPart = lng.split('-')[0];
return langPart;
// return lng.replace('-', '_');
},
},
supportedLngs: ['en', 'zh'], supportedLngs: ['en', 'zh'],
// resources: { // resources: {
// en: { translation: en }, // en: { translation: en },

@ -85,7 +85,6 @@ const useFeedbackStore = create(
...initialState, ...initialState,
reset: () => set(initialState), reset: () => set(initialState),
setState: (state) => set(state),
setLoading: (loading) => set({ loading }), setLoading: (loading) => set({ loading }),
setFeedbackList: (feedbackList) => set({ feedbackList }), setFeedbackList: (feedbackList) => set({ feedbackList }),

@ -9,7 +9,6 @@ import dayjs from "dayjs";
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools } from 'zustand/middleware'; import { devtools } from 'zustand/middleware';
const initialState = { const initialState = {
invoiceList: [], //账单列表 invoiceList: [], //账单列表
invoicekImages: [], //图片列表 invoicekImages: [], //图片列表
@ -122,9 +121,9 @@ export class Invoice {
}); });
} }
fetchInvoiceDetail(GMDSN, GSN) { fetchInvoiceDetail(VEI_SN, GMDSN, GSN) {
const fetchUrl = prepareUrl(HT_HOST + "/service-cusservice/PTGetZDDetail") const fetchUrl = prepareUrl(HT_HOST + "/service-cusservice/PTGetZDDetail")
.append("VEI_SN", this.root.authStore.login.travelAgencyId) .append("VEI_SN", VEI_SN)
.append("GRI_SN", GSN) .append("GRI_SN", GSN)
.append("GMD_SN", GMDSN) .append("GMD_SN", GMDSN)
.append("LGC", 1) .append("LGC", 1)
@ -228,10 +227,10 @@ export class Invoice {
}); });
} }
postEditInvoiceDetail(GMD_SN, Currency, Cost, PayDate, Pic, Memo) { postEditInvoiceDetail(LMI_SN, GMD_SN, Currency, Cost, PayDate, Pic, Memo) {
let postUrl = HT_HOST + "/service-cusservice/EditSupplierFK"; let postUrl = HT_HOST + "/service-cusservice/EditSupplierFK";
let formData = new FormData(); let formData = new FormData();
formData.append("LMI_SN", this.root.authStore.login.userId); formData.append("LMI_SN", LMI_SN);
formData.append("GMD_SN", GMD_SN); formData.append("GMD_SN", GMD_SN);
formData.append("Currency", Currency); formData.append("Currency", Currency);
formData.append("Cost", Cost); formData.append("Cost", Cost);
@ -246,11 +245,11 @@ export class Invoice {
}); });
} }
postAddInvoice(GRI_SN, Currency, Cost, PayDate, Pic, Memo) { postAddInvoice(LMI_SN, VEI_SN, GRI_SN, Currency, Cost, PayDate, Pic, Memo) {
let postUrl = HT_HOST + "/service-cusservice/AddSupplierFK"; let postUrl = HT_HOST + "/service-cusservice/AddSupplierFK";
let formData = new FormData(); let formData = new FormData();
formData.append("LMI_SN", this.root.authStore.login.userId); formData.append("LMI_SN", LMI_SN);
formData.append("VEI_SN", this.root.authStore.login.travelAgencyId); formData.append("VEI_SN", VEI_SN);
formData.append("GRI_SN", GRI_SN); formData.append("GRI_SN", GRI_SN);
formData.append("Currency", Currency); formData.append("Currency", Currency);
formData.append("Cost", Cost); formData.append("Cost", Cost);

@ -1,113 +1,69 @@
import { makeAutoObservable, runInAction } from "mobx"; import { fetchJSON } from "@/utils/request";
import { fetchJSON, postForm } from "@/utils/request";
import { prepareUrl, isNotEmpty } from "@/utils/commons";
import { HT_HOST } from "@/config"; import { HT_HOST } from "@/config";
import { json } from "react-router-dom"; import { create } from 'zustand';
import * as config from "@/config"; import { devtools } from 'zustand/middleware';
import dayjs from "dayjs";
class Report { const initialState = {
constructor(root) { loading: false,
makeAutoObservable(this, { rootStore: false }); vendorScoresData: [], //地接统计数据集,合计数据,每月数据,地接考核分数
this.root = root; productScoresData: [], //产品体验分析 常用酒店分析, 导游接待情况
} commendScoresData: [], //表扬情况, 投诉情况, 评建议
};
export const useReportStore = create(
devtools((set, get) => ({
...initialState,
reset: () => set(initialState),
vendorScoresData = []; //地接统计数据集,合计数据,每月数据,地接考核分数 setLoading: (loading) => set({ loading }),
productScoresData = []; //产品体验分析 常用酒店分析, 导游接待情况 setVendorScoresData: (vendorScoresData) => set({ vendorScoresData }),
commendScoresData = []; //表扬情况, 投诉情况, 评建议 setProductScoresData: (productScoresData) => set({ productScoresData }),
setCommendScoresData: (commendScoresData) => set({ commendScoresData }),
loading = false; async getHWVendorScores(VEI_SN, StartDate, EndDate) {
search_date_start = dayjs().month(0).startOf("month"); const { setLoading, setVendorScoresData } = get();
search_date_end = dayjs().month(11).endOf("month"); setLoading(true);
const searchParams = {
onDateRangeChange = dates => { VEI_SN,
this.search_date_start = dates == null ? null : dates[0].startOf("month"); StartDate,
this.search_date_end = dates == null ? null : dates[1].endOf("month"); EndDate,
}; StrDEI_SN: '(,-1,)',
OrderType: '-1',
getHWVendorScores(VEI_SN, StartDate, EndDate) { GroupType: '-1',
this.loading = true; };
const fetchUrl = prepareUrl(HT_HOST +"/service-cusservice/PTGetHWVendorScores") const { errcode, ...Result } = await fetchJSON(`${HT_HOST}/service-cusservice/PTGetHWVendorScores`, searchParams);
.append("VEI_SN", VEI_SN) setVendorScoresData(errcode === 0 ? Result : {});
.append("StartDate", StartDate) // setLoading(false);
.append("EndDate", EndDate) },
.append("StrDEI_SN", "(,-1,)") async getHWProductScores(VEI_SN, StartDate, EndDate) {
.append("OrderType", "-1") const { setLoading, setProductScoresData } = get();
.append("GroupType", "-1") setLoading(true);
.append("token", this.root.authStore.login.token) const searchParams = {
.build(); VEI_SN,
StartDate,
return fetchJSON(fetchUrl).then(json => { EndDate,
runInAction(() => { StrDEI_SN: '(,-1,)',
this.loading = false; OrderType: '-1',
if (json.errcode == 0) { GroupType: '-1',
if (isNotEmpty(json)) { };
this.vendorScoresData = json; const { errcode, ...Result } = await fetchJSON(`${HT_HOST}/service-cusservice/PTGetHWProductScores`, searchParams);
} else { setProductScoresData(errcode === 0 ? Result : {});
this.vendorScoresData = []; setLoading(false);
} },
} else { async getHWCommendScores(VEI_SN, StartDate, EndDate) {
throw new Error(json.errmsg + ": " + json.errcode); const { setLoading, setCommendScoresData } = get();
} setLoading(true);
}); const searchParams = {
}); VEI_SN,
} StartDate,
EndDate,
getHWProductScores(VEI_SN, StartDate, EndDate) { StrDEI_SN: '(,-1,)',
this.loading = true; OrderType: '-1',
const fetchUrl = prepareUrl(HT_HOST +"/service-cusservice/PTGetHWProductScores") GroupType: '-1',
.append("VEI_SN", VEI_SN) };
.append("StartDate", StartDate) const { errcode, ...Result } = await fetchJSON(`${HT_HOST}/service-cusservice/PTGetHWCommendScores`, searchParams);
.append("EndDate", EndDate) setCommendScoresData(errcode === 0 ? Result : {});
.append("StrDEI_SN", "(,-1,)") // setLoading(false);
.append("OrderType", "-1") },
.append("GroupType", "-1") }))
.append("token", this.root.authStore.login.token) );
.build(); export default useReportStore;
return fetchJSON(fetchUrl).then(json => {
runInAction(() => {
this.loading = false;
if (json.errcode == 0) {
if (isNotEmpty(json)) {
this.productScoresData = json;
} else {
this.productScoresData = [];
}
} else {
throw new Error(json.errmsg + ": " + json.errcode);
}
});
});
}
getHWCommendScores(VEI_SN, StartDate, EndDate) {
this.loading = true;
const fetchUrl = prepareUrl(HT_HOST +"/service-cusservice/PTGetHWCommendScores")
.append("VEI_SN", VEI_SN)
.append("StartDate", StartDate)
.append("EndDate", EndDate)
.append("StrDEI_SN", "(,-1,)")
.append("OrderType", "-1")
.append("GroupType", "-1")
.append("token", this.root.authStore.login.token)
.build();
return fetchJSON(fetchUrl).then(json => {
runInAction(() => {
this.loading = false;
if (json.errcode == 0) {
if (isNotEmpty(json)) {
this.commendScoresData = json;
} else {
this.commendScoresData = [];
}
} else {
throw new Error(json.errmsg + ": " + json.errcode);
}
});
});
}
}
export default Report;

@ -2,14 +2,12 @@ import { makeAutoObservable } from "mobx";
import { Reservation } from "./Reservation"; import { Reservation } from "./Reservation";
import { Auth } from "./Auth"; import { Auth } from "./Auth";
import {Invoice} from "./Invoice"; import {Invoice} from "./Invoice";
import Report from "./Report";
class Root { class Root {
constructor() { constructor() {
this.reservationStore = new Reservation(this); this.reservationStore = new Reservation(this);
this.authStore = new Auth(this); this.authStore = new Auth(this);
this.invoiceStore = new Invoice(this); this.invoiceStore = new Invoice(this);
this.reportStore = new Report(this);
makeAutoObservable(this); makeAutoObservable(this);
} }

@ -5,6 +5,7 @@ import { PlusOutlined } from '@ant-design/icons';
import * as config from '@/config'; import * as config from '@/config';
import useAuthStore from '@/stores/Auth'; import useAuthStore from '@/stores/Auth';
import { getFeedbackDetail, getCustomerFeedbackDetail, getFeedbackImages, getFeedbackInfo, removeFeedbackImages, postFeedbackInfo } from '@/stores/Feedback'; import { getFeedbackDetail, getCustomerFeedbackDetail, getFeedbackImages, getFeedbackInfo, removeFeedbackImages, postFeedbackInfo } from '@/stores/Feedback';
import BackBtn from '@/components/BackBtn';
const { Title, Text, Paragraph } = Typography; const { Title, Text, Paragraph } = Typography;
@ -79,9 +80,7 @@ function Detail() {
<Row gutter={16}> <Row gutter={16}>
<Col span={20}></Col> <Col span={20}></Col>
<Col span={4}> <Col span={4}>
<Button type='link' onClick={() => navigate('/feedback')}> <BackBtn />
Back
</Button>
</Col> </Col>
</Row> </Row>
<Row gutter={16}> <Row gutter={16}>

@ -5,6 +5,7 @@ import { PlusOutlined } from "@ant-design/icons";
import * as config from "@/config"; import * as config from "@/config";
import useAuthStore from '@/stores/Auth'; import useAuthStore from '@/stores/Auth';
import { getFeedbackDetail, getFeedbackImages, getFeedbackInfo, removeFeedbackImages, postFeedbackInfo } from '@/stores/Feedback'; import { getFeedbackDetail, getFeedbackImages, getFeedbackInfo, removeFeedbackImages, postFeedbackInfo } from '@/stores/Feedback';
import BackBtn from "@/components/BackBtn";
const { Title, Text, Paragraph } = Typography; const { Title, Text, Paragraph } = Typography;
function Detail() { function Detail() {
@ -81,9 +82,7 @@ function Detail() {
</Col> </Col>
<Col span={4}> <Col span={4}>
<Button type="link" onClick={() => navigate("/feedback")}> <BackBtn />
Back
</Button>
</Col> </Col>
</Row> </Row>
<Row gutter={16}> <Row gutter={16}>

@ -8,6 +8,8 @@ import { PlusOutlined, AuditOutlined, SmileOutlined, SolutionOutlined, EditOutli
import { isNotEmpty } from "@/utils/commons"; import { isNotEmpty } from "@/utils/commons";
import * as config from "@/config"; import * as config from "@/config";
import dayjs from "dayjs"; import dayjs from "dayjs";
import useAuthStore from '@/stores/Auth';
import BackBtn from '@/components/BackBtn';
const { Title,Text } = Typography; const { Title,Text } = Typography;
const { TextArea } = Input; const { TextArea } = Input;
@ -15,7 +17,10 @@ const { TextArea } = Input;
function Detail() { function Detail() {
const navigate = useNavigate(); const navigate = useNavigate();
const { GMDSN, GSN } = useParams(); const { GMDSN, GSN } = useParams();
const { invoiceStore, authStore } = useStore(); const { invoiceStore, } = useStore();
const [userId, travelAgencyId, token] = useAuthStore((state) => [state.loginUser.userId,state.loginUser.travelAgencyId, state.loginUser.token]);
const { invoicekImages, invoiceGroupInfo, invoiceProductList, invoiceCurrencyList, invoiceZDDetail } = invoiceStore; const { invoicekImages, invoiceGroupInfo, invoiceProductList, invoiceCurrencyList, invoiceZDDetail } = invoiceStore;
const [form] = Form.useForm(); const [form] = Form.useForm();
const [dataLoading, setDataLoading] = useState(false); const [dataLoading, setDataLoading] = useState(false);
@ -33,9 +38,9 @@ function Detail() {
function defaultShow() { function defaultShow() {
setDataLoading(true); setDataLoading(true);
invoiceStore invoiceStore
.fetchInvoiceDetail(GMDSN, GSN) .fetchInvoiceDetail(travelAgencyId, GMDSN, GSN)
.then(json => { .then(json => {
let ZDDetail = json.ZDDetail; let ZDDetail = json.ZDDetail;
if (isNotEmpty(ZDDetail)) { if (isNotEmpty(ZDDetail)) {
let arrLen = ZDDetail.length; let arrLen = ZDDetail.length;
const formData = ZDDetail.map((data, index) => { const formData = ZDDetail.map((data, index) => {
@ -109,7 +114,7 @@ function Detail() {
console.log("Success:", fieldVaule); console.log("Success:", fieldVaule);
// //
if (fieldVaule) { if (fieldVaule) {
invoiceStore.postEditInvoiceDetail(fieldVaule.info_gmdsn, fieldVaule.info_Currency, fieldVaule.info_money, fieldVaule.info_date, fieldVaule.info_images, "").then(data => { invoiceStore.postEditInvoiceDetail(userId, fieldVaule.info_gmdsn, fieldVaule.info_Currency, fieldVaule.info_money, fieldVaule.info_date, fieldVaule.info_images, "").then(data => {
console.log(data); console.log(data);
runInAction(() => { runInAction(() => {
let param = { info_money: fieldVaule.info_money, info_Currency: fieldVaule.info_Currency, info_date: fieldVaule.info_date }; let param = { info_money: fieldVaule.info_money, info_Currency: fieldVaule.info_Currency, info_date: fieldVaule.info_date };
@ -122,7 +127,7 @@ function Detail() {
description: "Success Submit!", description: "Success Submit!",
placement: "top", placement: "top",
duration: 4, duration: 4,
}); });
} }
}); });
} }
@ -163,7 +168,7 @@ function Detail() {
function addInvoice() { function addInvoice() {
invoiceStore invoiceStore
.postAddInvoice(GSN, "", 0, "", "[]", "") .postAddInvoice(userId, travelAgencyId, GSN, "", 0, "", "[]", "")
.then(data => {}) .then(data => {})
.finally(() => { .finally(() => {
defaultShow(); defaultShow();
@ -272,13 +277,13 @@ function Detail() {
}, },
]} ]}
> >
<DatePicker picker="month" /> <DatePicker picker="month" />
</Form.Item> </Form.Item>
<Text type="secondary">Payment is arranged during the last week of each month. If the invoice is issued after the 20th, please select the following month for payment. For urgent payments, please contact the travel advisor. </Text> <Text type="secondary">Payment is arranged during the last week of each month. If the invoice is issued after the 20th, please select the following month for payment. For urgent payments, please contact the travel advisor. </Text>
<Form.Item name="info_gmdsn" hidden={true}> <Form.Item name="info_gmdsn" hidden={true}>
<input /> <input />
</Form.Item> </Form.Item>
<br/> <br/>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
Submit Submit
@ -290,7 +295,7 @@ function Detail() {
<Upload <Upload
name="ghhfile" name="ghhfile"
multiple={true} multiple={true}
action={config.HT_HOST + `/service-fileServer/FileUpload?GRI_SN=${GSN}&VEI_SN=${authStore.login.travelAgencyId}&FilePathName=invoice&token=${authStore.login.token}`} action={config.HT_HOST + `/service-fileServer/FileUpload?GRI_SN=${GSN}&VEI_SN=${travelAgencyId}&FilePathName=invoice&token=${token}`}
fileList={fileList} fileList={fileList}
listType="picture-card" listType="picture-card"
onChange={handleChange} onChange={handleChange}
@ -333,9 +338,7 @@ function Detail() {
<Title level={4}>Reference Number: {invoiceGroupInfo.VGroupInfo}</Title> <Title level={4}>Reference Number: {invoiceGroupInfo.VGroupInfo}</Title>
</Col> </Col>
<Col span={4}> <Col span={4}>
<Button type="link" onClick={() => navigate("/invoice")}> <BackBtn />
Back
</Button>
</Col> </Col>
</Row> </Row>
<Title level={5}></Title> <Title level={5}></Title>

@ -7,13 +7,19 @@ import { useStore } from "@/stores/StoreContext.js";
import * as config from "@/config"; import * as config from "@/config";
import { formatDate, isNotEmpty } from "@/utils/commons"; import { formatDate, isNotEmpty } from "@/utils/commons";
import { AuditOutlined, SmileOutlined, SolutionOutlined, EditOutlined } from "@ant-design/icons"; import { AuditOutlined, SmileOutlined, SolutionOutlined, EditOutlined } from "@ant-design/icons";
import useAuthStore from '@/stores/Auth';
import usePresets from '@/hooks/usePresets'; import usePresets from '@/hooks/usePresets';
import SearchForm from '@/components/SearchForm';
import dayjs from 'dayjs';
const { Title } = Typography; const { Title } = Typography;
function Index() { function Index() {
const { authStore, invoiceStore } = useStore(); const { authStore, invoiceStore } = useStore();
const { invoiceList, search_date_start, search_date_end } = invoiceStore; const { invoiceList, search_date_start, search_date_end } = invoiceStore;
const [travelAgencyId ] = useAuthStore((state) => [state.loginUser.travelAgencyId]);
const [groupNo, onGroupNoChange] = useState(""); const [groupNo, onGroupNoChange] = useState("");
const [OrderType, onOrderTypeChange] = useState(0); // const [OrderType, onOrderTypeChange] = useState(0); //
const navigate = useNavigate(); const navigate = useNavigate();
@ -76,76 +82,36 @@ function Index() {
return ( return (
<Space direction="vertical" style={{ width: "100%" }}> <Space direction="vertical" style={{ width: "100%" }}>
<Title level={3}></Title> <Row gutter={16}>
<Row gutter={16}> <Col flex="auto">
<Col md={24} lg={4} xxl={4}> <SearchForm
<Input initialValue={{
placeholder="Reference Number" dates: [dayjs().subtract(2, 'M').startOf('M'), dayjs().endOf('M')],
onChange={e => { }}
onGroupNoChange(e.target.value); defaultValue={{
}} shows: ['referenceNo', 'invoiceStatus', 'dates'],
/> fieldProps: {
</Col> referenceNo: { col: 5},
<Col md={24} lg={4} xxl={3}> invoiceStatus: { col: 4},
dates: { col: 10 },
},
}}
onSubmit={(err, formVal, filedsVal) => {
// fetchInvoiceList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.enddate, formVal.invoiceStatus);
invoiceStore.fetchInvoiceList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.enddate, formVal.invoiceStatus)
}}
/>
</Col>
<Col md={24} lg={4} xxl={4}>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/invoice/detail/0/338787`)}>
Misc. Invoice
</Button>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/invoice/paid`)}>
Bank statement
</Button>
</Col>
</Row>
<Select
defaultValue="Status"
style={{
width: 220,
}}
onChange={ value => {onOrderTypeChange(value);}}
options={[
{
value: '0',
label: 'Status',
},
{
value: '1',
label: 'Not submitted',
},
{
value: '2',
label: 'Submitted',
},
{
value: '3',
label: 'Travel advisor approved',
},
{
value: '4',
label: 'Finance Dept arrproved',
},
{
value: '5',
label: 'Paid',
},
]}
/>
</Col>
<Col md={24} lg={8} xxl={6}>
<Space direction="horizontal">
Arrival Date
<DatePicker.RangePicker format={config.DATE_FORMAT} allowClear={true} style={{ width: "100%" }} defaultValue={[search_date_start, search_date_end]} presets={usePresets()} onChange={invoiceStore.onDateRangeChange} allowEmpty={true} />
</Space>
</Col>
<Col md={24} lg={4} xxl={4}>
<Button
type="primary"
loading={invoiceStore.loading}
onClick={() => invoiceStore.fetchInvoiceList(authStore.login.travelAgencyId, groupNo, search_date_start==null?null:search_date_start.format(config.DATE_FORMAT), search_date_end==null?null:search_date_end.format(config.DATE_FORMAT),OrderType)}>
Search
</Button>
</Col>
<Col md={24} lg={4} xxl={4}>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/invoice/detail/0/338787`)}>
Misc. Invoice
</Button>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/invoice/paid`)}>
Bank statement
</Button>
</Col>
</Row>
<Title level={3}></Title>
<Row> <Row>
<Col md={24} lg={24} xxl={24}> <Col md={24} lg={24} xxl={24}>
<Table bordered pagination={{ defaultPageSize: 20, showTotal: showTotal }} columns={invoiceListColumns} dataSource={toJS(invoiceList)} /> <Table bordered pagination={{ defaultPageSize: 20, showTotal: showTotal }} columns={invoiceListColumns} dataSource={toJS(invoiceList)} />

@ -1,121 +1,104 @@
import { NavLink, useNavigate } from "react-router-dom"; import { NavLink, useNavigate } from 'react-router-dom';
import { useEffect, useState } 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, DatePicker, Typography, App, Image } from "antd"; import { Row, Col, Space, Button, Table, Input, DatePicker, Typography, App, Image } from 'antd';
import { useStore } from "@/stores/StoreContext.js"; import { useStore } from '@/stores/StoreContext.js';
import * as config from "@/config"; import * as config from '@/config';
import { formatDate, isNotEmpty } from "@/utils/commons"; import { formatDate, isNotEmpty } from '@/utils/commons';
import usePresets from '@/hooks/usePresets'; import usePresets from '@/hooks/usePresets';
const { Title } = Typography; import SearchForm from '@/components/SearchForm';
import useAuthStore from '@/stores/Auth';
function Paid(){ import dayjs from 'dayjs';
const { authStore, invoiceStore } = useStore(); import BackBtn from '@/components/BackBtn';
const { invoicePaid, search_date_start, search_date_end } = invoiceStore;
const [groupNo, onGroupNoChange] = useState("");
const navigate = useNavigate();
const showTotal = total => `Total ${invoicePaid.length} items`;
useEffect (() => {
invoiceStore.fetchInvoicePaid(authStore.login.travelAgencyId,"","","");
},[]); const { Title } = Typography;
const invoicePaidColumns = [
{
title: "Payment ref.NO",
dataIndex: "fl_finaceNo",
key: "fl_finaceNo",
render: (text, record) => <NavLink to={`/invoice/paid/detail/${record.key}`}>{text}</NavLink>,
},
{
title: "Payment date",
key: "fl_adddate",
dataIndex: "fl_adddate",
render: (text, record) => (isNotEmpty(text) ? formatDate(new Date(text)) : ""),
},
{
title: "Number of bills",
key: "fcount",
dataIndex: "fcount",
},
{
title: "Total amount",
key: "pSum",
dataIndex: "pSum",
//render: (text, record) => (isNotEmpty(record.GMD_Currency) ? record.GMD_Currency + " " + text : text),
},
{
title: "Bank statement",
key: "fl_pic",
dataIndex: "fl_pic",
render: showPIc,
},
];
function showPIc(text,record) {
let strPic = record.fl_pic;
//console.log(JSON.parse(strPic));
if (isNotEmpty(strPic)){
return (
JSON.parse(strPic).map((item,index) => { function Paid() {
return <Image key={index} width={90} src={item.url} />; const { invoiceStore } = useStore();
}) const { invoicePaid, search_date_start, search_date_end } = invoiceStore;
const [travelAgencyId] = useAuthStore((state) => [state.loginUser.travelAgencyId]);
const navigate = useNavigate();
const showTotal = (total) => `Total ${total} items`;
); useEffect(() => {
}else{ invoiceStore.fetchInvoicePaid(travelAgencyId, '', '', '');
return ""; }, []);
} const invoicePaidColumns = [
{
title: 'Payment ref.NO',
dataIndex: 'fl_finaceNo',
key: 'fl_finaceNo',
render: (text, record) => <NavLink to={`/invoice/paid/detail/${record.key}`}>{text}</NavLink>,
},
{
title: 'Payment date',
key: 'fl_adddate',
dataIndex: 'fl_adddate',
render: (text, record) => (isNotEmpty(text) ? formatDate(new Date(text)) : ''),
},
{
title: 'Number of bills',
key: 'fcount',
dataIndex: 'fcount',
},
{
title: 'Total amount',
key: 'pSum',
dataIndex: 'pSum',
//render: (text, record) => (isNotEmpty(record.GMD_Currency) ? record.GMD_Currency + " " + text : text),
},
{
title: 'Bank statement',
key: 'fl_pic',
dataIndex: 'fl_pic',
render: showPIc,
},
];
function showPIc(text, record) {
let strPic = record.fl_pic;
//console.log(JSON.parse(strPic));
if (isNotEmpty(strPic)) {
return JSON.parse(strPic).map((item, index) => {
return <Image key={index} width={90} src={item.url} />;
});
} else {
return '';
} }
}
return (
<Space direction='vertical' style={{ width: '100%' }}>
return ( <Row gutter={16}>
<Space direction="vertical" style={{ width: "100%" }}> <Col span={20}>
<Row gutter={16}> <SearchForm
<Col span={20}> initialValue={{
</Col> dates: [dayjs().subtract(2, 'M').startOf('M'), dayjs().endOf('M')],
<Col span={4}> }}
<Button type="link" onClick={() => navigate("/invoice")}> defaultValue={{
Back shows: ['referenceNo', 'dates'],
</Button> fieldProps: {
</Col> referenceNo: { col: 5 },
</Row> dates: { col: 10, label: 'Date' },
<Title level={3}></Title> },
<Row gutter={16}> }}
<Col md={24} lg={6} xxl={4}> onSubmit={(err, formVal, filedsVal) => {
<Input invoiceStore.fetchInvoicePaid(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.enddate);
placeholder="Reference Number" }}
onChange={e => { />
onGroupNoChange(e.target.value); </Col>
}} <Col span={4}>
/> <BackBtn to={'/invoice'} />
</Col> </Col>
<Col md={24} lg={8} xxl={6}> </Row>
<Space direction="horizontal"> <Row>
Date <Col md={24} lg={24} xxl={24}>
<DatePicker.RangePicker format={config.DATE_FORMAT} allowClear={false} style={{ width: "100%" }} value={[search_date_start, search_date_end]} presets={usePresets()} onChange={invoiceStore.onDateRangeChange} /> <Table bordered columns={invoicePaidColumns} dataSource={toJS(invoicePaid)} />
</Space> </Col>
</Col> </Row>
<Col md={24} lg={4} xxl={4}> </Space>
<Button );
type="primary"
loading={invoiceStore.loading}
onClick={() => invoiceStore.fetchInvoicePaid(authStore.login.travelAgencyId, groupNo, search_date_start.format(config.DATE_FORMAT), search_date_end.format(config.DATE_FORMAT))}>
Search
</Button>
</Col>
</Row>
<Title level={3}></Title>
<Row>
<Col md={24} lg={24} xxl={24}>
<Table bordered columns={invoicePaidColumns} dataSource={toJS(invoicePaid)} />
</Col>
</Row>
</Space>
);
} }
export default observer(Paid); export default observer(Paid);

@ -6,16 +6,20 @@ import { Row, Col, Space, Button, Table, Typography } from "antd";
import { useStore } from "@/stores/StoreContext.js"; import { useStore } from "@/stores/StoreContext.js";
import * as config from "@/config"; import * as config from "@/config";
import { formatDate, isNotEmpty } from "@/utils/commons"; import { formatDate, isNotEmpty } from "@/utils/commons";
import useAuthStore from '@/stores/Auth';
import BackBtn from '@/components/BackBtn';
const { Title } = Typography; const { Title } = Typography;
function PaidDetail(){ function PaidDetail(){
const navigate = useNavigate(); const navigate = useNavigate();
const { authStore, invoiceStore } = useStore(); const { invoiceStore } = useStore();
const { invoicePaidDetail } = invoiceStore; const { invoicePaidDetail } = invoiceStore;
const [travelAgencyId] = useAuthStore((state) => [state.loginUser.travelAgencyId]);
const { flid } = useParams(); const { flid } = useParams();
useEffect(() => { useEffect(() => {
invoiceStore.fetchInvoicePaidDetail(authStore.login.travelAgencyId,flid); invoiceStore.fetchInvoicePaidDetail(travelAgencyId,flid);
},[flid]); },[flid]);
@ -49,9 +53,7 @@ function PaidDetail(){
<Col span={20}> <Col span={20}>
</Col> </Col>
<Col span={4}> <Col span={4}>
<Button type="link" onClick={() => navigate("/invoice/paid")}> <BackBtn />
Back
</Button>
</Col> </Col>
</Row> </Row>
<Title level={3}></Title> <Title level={3}></Title>
@ -64,4 +66,4 @@ function PaidDetail(){
); );
} }
export default observer(PaidDetail); export default observer(PaidDetail);

@ -1,22 +1,23 @@
import { NavLink, useParams } from 'react-router-dom'; import { NavLink, useParams } from 'react-router-dom';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Row, Col, Space, Typography, Divider } from 'antd'; import { Row, Col, Space, Typography, Divider } from 'antd';
import { useStore } from '@/stores/StoreContext.js';
import * as comm from '@/utils/commons'; import * as comm from '@/utils/commons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { fetchNoticeDetail } from '@/stores/Notice'; import { fetchNoticeDetail } from '@/stores/Notice';
import useAuthStore from '@/stores/Auth';
import BackBtn from '@/components/BackBtn';
const { Title, Paragraph } = Typography; const { Title, Paragraph } = Typography;
function Detail() { function Detail() {
const { t } = useTranslation(); const { t } = useTranslation();
const { authStore } = useStore();
const { CCP_BLID } = useParams(); const { CCP_BLID } = useParams();
const [userId, ] = useAuthStore((state) => [state.loginUser.userId]);
const [noticeInfo, setNoticeInfo] = useState({}); const [noticeInfo, setNoticeInfo] = useState({});
useEffect(() => { useEffect(() => {
// console.info("notice detail .useEffect " + CCP_BLID); // console.info("notice detail .useEffect " + CCP_BLID);
fetchNoticeDetail(authStore.login.userId, CCP_BLID).then((res) => { fetchNoticeDetail(userId, CCP_BLID).then((res) => {
setNoticeInfo(res); setNoticeInfo(res);
}); });
}, []); }, []);
@ -33,7 +34,7 @@ function Detail() {
</Paragraph> </Paragraph>
</Col> </Col>
<Col span={4}> <Col span={4}>
<NavLink to='/notice'>{t('Back')}</NavLink> <BackBtn />
</Col> </Col>
</Row> </Row>
</Space> </Space>

@ -1,20 +1,20 @@
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Row, Col, Space, Typography, Badge, List } from "antd"; import { Row, Col, Space, Typography, Badge, List } from "antd";
import { useStore } from "@/stores/StoreContext.js";
import useNoticeStore, { fetchBulletinList } from '@/stores/Notice'; import useNoticeStore, { fetchBulletinList } from '@/stores/Notice';
import useAuthStore from '@/stores/Auth';
function Index() { function Index() {
const { authStore } = useStore(); const [userId, ] = useAuthStore((state) => [state.loginUser.userId]);
const getBulletinUnReadCount = useNoticeStore((state) => state.getBulletinUnReadCount); const getBulletinUnReadCount = useNoticeStore((state) => state.getBulletinUnReadCount);
const [noticeList, setNoticeList] = useState([]); const [noticeList, setNoticeList] = useState([]);
useEffect(() => { useEffect(() => {
// console.info("notice.useEffect", authStore.login.userId); // console.info("notice.useEffect", authStore.login.userId);
fetchBulletinList(authStore.login.userId).then(data => { fetchBulletinList(userId).then(data => {
setNoticeList(data); setNoticeList(data);
}); });
getBulletinUnReadCount(authStore.login.userId); // getBulletinUnReadCount(userId); //
}, []); }, []);
return ( return (

@ -1,505 +1,404 @@
import { useEffect } from "react"; import { Row, Col, Space, Button, Table, Divider, Typography, } from 'antd';
import { observer } from "mobx-react"; import * as comm from '@/utils/commons';
import { Row, Col, Space, Button, Table, Divider, Typography, DatePicker } from "antd"; import { usePDF } from 'react-to-pdf';
import { useStore } from "@/stores/StoreContext.js"; import dayjs from 'dayjs';
import * as config from "@/config"; import SearchForm from '@/components/SearchForm';
import * as comm from "@/utils/commons"; import useAuthStore from '@/stores/Auth';
import { usePDF } from "react-to-pdf"; import useReportStore from '@/stores/Report';
import { useTranslation } from 'react-i18next'
function Index() { function Index() {
const { reportStore, authStore } = useStore(); const { t } = useTranslation();
const { search_date_start, search_date_end, vendorScoresData, productScoresData, commendScoresData } = reportStore;
const evaluationScores = vendorScoresData.EvaluationScores ? vendorScoresData.EvaluationScores[0] : [];
useEffect(() => {}, []);
const columns_month = [ const [travelAgencyId, ] = useAuthStore((state) => [state.loginUser.travelAgencyId]);
{ const [loading, vendorScoresData, getHWVendorScores] = useReportStore((state) => [state.loading, state.vendorScoresData, state.getHWVendorScores]);
title: "Date", const [productScoresData, getHWProductScores] = useReportStore((state) => [state.productScoresData, state.getHWProductScores]);
dataIndex: "VMonth", const [commendScoresData, getHWCommendScores] = useReportStore((state) => [state.commendScoresData, state.getHWCommendScores]);
key: "VMonth",
},
{
title: "Groups",
dataIndex: "Groups",
key: "Groups",
},
{
title: "Number of People",
dataIndex: "PersonNum",
key: "PersonNum",
},
{
title: "Transaction AmountUSD)",
dataIndex: "AmountUSD",
key: "AmountUSD",
render: value => comm.formatPrice(value),
},
{
title: "Evaluation Score",
dataIndex: "EvaluationScore",
key: "EvaluationScore",
},
{
title: "TP Reviews",
dataIndex: "TPReviews",
key: "TPReviews",
},
{
title: "TP Reviews Rate",
dataIndex: "TPReviewRate",
key: "TPReviewRate",
render: value => comm.formatPercent(value),
},
{
title: "Complaints",
dataIndex: "Complaints",
key: "Complaints",
},
{
title: "Complaint Rate",
dataIndex: "ComplaintRate",
key: "ComplaintRate",
render: value => comm.formatPercent(value),
},
];
const columns_guide = [ const evaluationScores = vendorScoresData.EvaluationScores ? vendorScoresData.EvaluationScores[0] : [];
{ const primaryData = vendorScoresData.EvaluationScores
title: "Name", ? [comm.pick(vendorScoresData.EvaluationScores[0], ['Groups', 'PersonNum', 'AmountUSD', 'EvaluationScore', 'TPReviews', 'TPReviewRate', 'Complaints', 'ComplaintRate'])]
dataIndex: "TGI2_Name", : [];
key: "TGI2_Name",
},
{
title: "Average Scores",
dataIndex: "VAverage",
key: "VAverage",
},
{
title: "Group Numbers",
dataIndex: "ReceptionGroups",
key: "ReceptionGroups",
},
{
title: "TP Reviews",
dataIndex: "CommendNum",
key: "CommendNum",
},
{
title: "TP Review Rate",
dataIndex: "CommendRate",
key: "CommendRate",
render: value => comm.formatPercent(value),
},
{
title: "Complaints",
dataIndex: "Complaints",
key: "Complaints",
},
{
title: "Complaint Rate",
dataIndex: "ComplaintRate",
key: "ComplaintRate",
render: value => comm.formatPercent(value),
},
];
const columns_commend = [ const evaluationScoresData = [
{ { category: 'DMC Services', item: 'Guide', value: evaluationScores.FRTGuide, note: evaluationScores.FRTText },
title: "Reference Number", { category: 'DMC Services', item: 'Driver & Vehicle', value: evaluationScores.FRTGriver },
dataIndex: "GRI_No", { category: 'DMC Services', item: 'Food Arrangement', value: evaluationScores.FRTMeal },
key: "GRI_No", { category: 'DMC Services', item: 'Activity', value: evaluationScores.FRTProduct },
}, { category: 'Itinerary Arrangements', item: 'Hotel', value: evaluationScores.FRTHotel, },
{ { category: 'Itinerary Arrangements', item: 'Travel Advisor&rsquo;s Planning', value: evaluationScores.FRTAdvisor },
title: "Tour Guides", ];
dataIndex: "ObjectName", const columns_evaluation = [
key: "ObjectName", { title: 'Category', dataIndex: 'category', key: 'category',align: 'center', onCell: (_, index) => ({ rowSpan: index === 0 ? 4 : index === 4 ? 2 : 0 }) },
}, { title: 'Item', dataIndex: 'item', key: 'item', align: 'center' },
{ { title: 'Your Scores', dataIndex: 'value', key: 'value',align: 'center', },
title: "Essential Comments", { title: 'Note', dataIndex: 'note', key: 'note',align: 'center', onCell: (_, index) => ({ rowSpan: index === 0 ? 7 : 0 })},
dataIndex: "ECI_Content", ];
key: "ECI_Content",
},
];
const { toPDF, targetRef } = usePDF({ const columns_DMC_customer_satisfaction = [
method: "save", { title: 'Customer Satisfaction', dataIndex: 'item', key: 'item',align: 'center', },
filename: "GHH-Report.pdf", { title: '3 scores', dataIndex: 'stand_3', key: 'stand_3', align: 'center', onCell: (_, index) => ({ colSpan: (index === 2 || index===4) ? 3 : 1 }) },
page: { margin: "3", format: "letter" }, //margin: Margin.SMALL, { title: '4 scores', dataIndex: 'stand_4', key: 'stand_4',align: 'center',onCell: (_, index) => ({ colSpan: (index === 2 || index===4) ? 0 : 1 }) },
canvas: { mimeType: "image/jpeg", qualityRatio: 1 }, { title: '5 scores', dataIndex: 'stand_5', key: 'stand_5',align: 'center',onCell: (_, index) => ({ colSpan: (index === 2 || index===4) ? 0 : 1 }) },
overrides: { pdf: { compress: true }, canvas: { useCORS: true } }, { title: 'Your Scores', dataIndex: 'value', key: 'value',align: 'center', },
}); { title: 'Final Scores', dataIndex: 'final_score', key: 'final_score',align: 'center', onCell: (_, index) => ({ rowSpan: index === 0 ? 5 : 0 })},
{ title: 'Note', dataIndex: 'note', key: 'note',align: 'center', },
];
const DMCData_customer_satisfaction = [
{
item: 'TP review rating',
stand_3: '\\',
stand_4: '30%',
stand_5: '60%',
value: evaluationScores.TPReviewRating,
final_score: evaluationScores.AvgCusSatisfaction,
note: evaluationScores.AvgCusSatisfaction,
},
{
item: 'Post tour complaints',
stand_3: '1',
stand_4: '0',
stand_5: '0',
value: evaluationScores.PostTourComplaints,
// final_score: evaluationScores.AvgCusSatisfaction,
note: evaluationScores.PostTourComplaintsText,
},
{
item: 'Complaints resolved during the tour',
stand_3: '3',
// stand_4: '0',
// stand_5: '0',
value: evaluationScores.ComplaintsDuringTour,
note: evaluationScores.ComplaintsDuringTourText,
},
{ item: 'Customer photos', stand_3: '\\', stand_4: '30%', stand_5: '50%', value: evaluationScores.CustomerPhotoRate, note: evaluationScores.CustomerPhotoRateText },
{ item: 'Evaluation scores', stand_3: '4.5', value: evaluationScores.EvaluationFormScore, note: evaluationScores.EvaluationFormScoreText },
];
return ( const columns_DMC_sopport_local = [
<Space direction="vertical" style={{ width: "100%" }}> { title: 'Operator Support & Local resources', dataIndex: 'item', key: 'item',align: 'center', },
<Typography.Title level={3}></Typography.Title> { title: '3 scores', dataIndex: 'stand_3', key: 'stand_3', align: 'center', },
<Row gutter={16}> { title: '4 scores', dataIndex: 'stand_4', key: 'stand_4',align: 'center', },
<Col md={24} lg={8} xxl={6}> { title: '5 scores', dataIndex: 'stand_5', key: 'stand_5',align: 'center', },
<Space direction="horizontal"> { title: 'Your Scores', dataIndex: 'value', key: 'value',align: 'center', },
Select Date { title: 'Final Scores', dataIndex: 'final_score', key: 'final_score',align: 'center', onCell: (_, index) => ({ rowSpan: index === 0 ? 6 : 0 })},
<DatePicker.RangePicker { title: 'Note', dataIndex: 'note', key: 'note',align: 'center', },
format={config.DATE_FORMAT_MONTH} ];
allowClear={true} const DMCData_sopport_local = [
style={{ width: "100%" }} {
defaultValue={[search_date_start, search_date_end]} item: 'Response efficiency',
onChange={reportStore.onDateRangeChange} stand_3: '1d',
allowEmpty={true} stand_4: '6hrs',
picker="month" stand_5: '3hrs',
/> value: evaluationScores.ResponseEfficiency,
</Space> final_score: evaluationScores.AvgLocalResources,
</Col> note: evaluationScores.ResponseEfficiencyText,
<Col md={24} lg={4} xxl={4}> },
<Button {
type="primary" item: 'Provide suggestions and alternatives',
loading={reportStore.loading} stand_3: '\\',
onClick={() => { stand_4: '√',
reportStore.getHWVendorScores( stand_5: '√',
authStore.login.travelAgencyId, value: evaluationScores.ProvideSuggestions,
search_date_start == null ? null : search_date_start.format(config.DATE_FORMAT), // final_score: evaluationScores.AvgCusSatisfaction,
search_date_end == null ? null : search_date_end.format(config.DATE_FORMAT) note: evaluationScores.ProvideSuggestionsText,
); },
reportStore.getHWProductScores( {
authStore.login.travelAgencyId, item: 'Provide local tourism information',
search_date_start == null ? null : search_date_start.format(config.DATE_FORMAT), stand_3: '\\',
search_date_end == null ? null : search_date_end.format(config.DATE_FORMAT) stand_4: '√',
); stand_5: '√',
reportStore.getHWCommendScores( value: evaluationScores.ProvideLocalInfo,
authStore.login.travelAgencyId, note: evaluationScores.ProvideLocalInfoText,
search_date_start == null ? null : search_date_start.format(config.DATE_FORMAT), },
search_date_end == null ? null : search_date_end.format(config.DATE_FORMAT) { item: 'Assist in developing exclusive products',
); stand_3: '\\',
}}> stand_4: '\\',
Get Report stand_5: '√', value: evaluationScores.ExclusiveProducts, note: evaluationScores.ExclusiveProductsText },
</Button> { item: 'Dedicated tour guide team for AH',
</Col> stand_3: '\\',
<Col md={24} lg={10} xxl={11}> stand_4: '√',
<Button onClick={() => toPDF()}>Download PDF</Button> stand_5: '√', value: evaluationScores.DedicatedTourGuide, note: evaluationScores.DedicatedTourGuideText },
</Col> { item: 'Partner hotels with contracted rate',
</Row> stand_3: '\\',
<Typography.Title level={3}></Typography.Title> stand_4: '√',
stand_5: '√', value: evaluationScores.PartnerHotels, note: evaluationScores.PartnerHotelsText },
];
const columns_DMC_pricing = [
{ title: 'Pricing & Settlement 20%', dataIndex: 'item', key: 'item',align: 'center', },
{ title: '3 scores', dataIndex: 'stand_3', key: 'stand_3', align: 'center', },
{ title: '4 scores', dataIndex: 'stand_4', key: 'stand_4',align: 'center', },
{ title: '5 scores', dataIndex: 'stand_5', key: 'stand_5',align: 'center', },
{ title: 'Your Scores', dataIndex: 'value', key: 'value',align: 'center', },
{ title: 'Final Scores', dataIndex: 'final_score', key: 'final_score',align: 'center', onCell: (_, index) => ({ rowSpan: index === 0 ? 3 : 0 })},
{ title: 'Note', dataIndex: 'note', key: 'note',align: 'center', },
];
const DMCData_pricing = [
{
item: 'Quotation',
stand_3: 'Package',
stand_4: 'Day tours',
stand_5: 'Individual services',
value: evaluationScores.Quotation,
final_score: evaluationScores.AvgPricingAndSettlement,
note: evaluationScores.QuotationText,
},
{
item: 'Settlement',
stand_3: 'Prepayment',
stand_4: 'Monthly Prepayment',
stand_5: 'Monthly settlement after the tours',
value: evaluationScores.Settlement,
note: evaluationScores.SettlementText,
},
{
item: 'Cancellation policy',
stand_3: '30 days',
stand_4: '21 days',
stand_5: '1 day',
value: evaluationScores.CancellationPolicy,
note: evaluationScores.CancellationPolicyText,
},];
<Row ref={targetRef}> const columns_primary = [
<Col md={24} lg={24} xxl={16}> {
<Divider orientation="center"> title: 'Groups',
<Typography.Title level={3} type="success">Primary Data</Typography.Title> dataIndex: 'Groups',
</Divider> key: 'Groups',
<div className="ant-table-wrapper ant-spin-nested-loading css-1x2egda css-dev-only-do-not-override-1x2egda "> },
<div className="ant-spin-container ant-table ant-table-bordered"> {
<div className="ant-table-container"> title: 'Number of People',
<div className="ant-table-content"> dataIndex: 'PersonNum',
<table style={{ textAlign: "center" }}> key: 'PersonNum',
<thead className="ant-table-thead"> },
<tr> {
<th scope="col">Groups</th> title: 'Transaction AmountUSD)',
<th scope="col">Number of People</th> dataIndex: 'AmountUSD',
<th scope="col">Transaction Amount (USD)</th> key: 'AmountUSD',
<th scope="col">Evaluation Score</th> render: (value) => comm.formatPrice(value),
<th scope="col">TP Reviews</th> },
<th scope="col">TP Reviews Rate</th> {
<th scope="col">Complaints</th> title: 'Evaluation Score',
<th scope="col">Complaint Rate</th> dataIndex: 'EvaluationScore',
</tr> key: 'EvaluationScore',
</thead> },
<tbody className="ant-table-tbody"> {
<tr className="ant-table-row ant-table-row-level-0"> title: 'TP Reviews',
<td>{evaluationScores.Groups}</td> dataIndex: 'TPReviews',
<td>{evaluationScores.PersonNum}</td> key: 'TPReviews',
<td>{comm.formatPrice(evaluationScores.AmountUSD)}</td> },
<td>{evaluationScores.EvaluationScore}</td> {
<td>{evaluationScores.TPReviews}</td> title: 'TP Reviews Rate',
<td>{comm.formatPercent(evaluationScores.TPReviewRate)}</td> dataIndex: 'TPReviewRate',
<td>{evaluationScores.Complaints}</td> key: 'TPReviewRate',
<td>{comm.formatPercent(evaluationScores.ComplaintRate)}</td> render: (value) => comm.formatPercent(value),
</tr> },
</tbody> {
</table> title: 'Complaints',
</div> dataIndex: 'Complaints',
</div> key: 'Complaints',
</div> },
</div> {
</Col> title: 'Complaint Rate',
dataIndex: 'ComplaintRate',
key: 'ComplaintRate',
render: (value) => comm.formatPercent(value),
},
];
<Col md={24} lg={24} xxl={16}> const columns_month = [{ title: 'Date', dataIndex: 'VMonth', key: 'VMonth' }, ...columns_primary];
<Divider orientation="center">
<Typography.Title level={3} type="success">Monthly Data</Typography.Title>
</Divider>
<Table dataSource={vendorScoresData.MonthlyData} columns={columns_month} pagination={false} bordered />
</Col>
<Col md={24} lg={24} xxl={16}> const columns_guide = [
<Divider orientation="center"> {
<Typography.Title level={3} type="success">DMC Assessment Criteria</Typography.Title> title: 'Name',
</Divider> dataIndex: 'TGI2_Name',
<div className="ant-table-wrapper ant-spin-nested-loading css-1x2egda css-dev-only-do-not-override-1x2egda"> key: 'TGI2_Name',
<div className="ant-spin-container ant-table ant-table-bordered"> },
<div className="ant-table-container"> {
<div className="ant-table-content"> title: 'Average Scores',
<table style={{ textAlign: "center" }}> dataIndex: 'VAverage',
<thead className="ant-table-thead"> key: 'VAverage',
<tr> },
<th scope="col">Customer Satisfaction</th> {
<th scope="col">3 scores</th> title: 'Group Numbers',
<th scope="col">4 scores</th> dataIndex: 'ReceptionGroups',
<th scope="col">5 scores</th> key: 'ReceptionGroups',
<th scope="col">Your Scores</th> },
<th scope="col">Final Scores</th> {
<th scope="col">Note</th> title: 'TP Reviews',
</tr> dataIndex: 'CommendNum',
</thead> key: 'CommendNum',
<tbody className="ant-table-tbody"> },
<tr className="ant-table-row ant-table-row-level-0"> {
<td>TP review rating</td> title: 'TP Review Rate',
<td>\</td> dataIndex: 'CommendRate',
<td>30%</td> key: 'CommendRate',
<td>60%</td> render: (value) => comm.formatPercent(value),
<td>{evaluationScores.TPReviewRating}</td> },
<td rowSpan="5">{evaluationScores.AvgCusSatisfaction}</td> {
<td>{evaluationScores.TPReviewRatingText}</td> title: 'Complaints',
</tr> dataIndex: 'Complaints',
<tr className="ant-table-row ant-table-row-level-0"> key: 'Complaints',
<td>Post tour complaints</td> },
<td>1</td> {
<td>0</td> title: 'Complaint Rate',
<td>0</td> dataIndex: 'ComplaintRate',
<td>{evaluationScores.PostTourComplaints}</td> key: 'ComplaintRate',
<td>{evaluationScores.PostTourComplaintsText}</td> render: (value) => comm.formatPercent(value),
</tr> },
<tr className="ant-table-row ant-table-row-level-0"> ];
<td>Complaints resolved during the tour</td>
<td colSpan={3}>3</td>
<td>{evaluationScores.ComplaintsDuringTour}</td>
<td>{evaluationScores.ComplaintsDuringTourText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Customer photos</td>
<td>\</td>
<td>30%</td>
<td>50%</td>
<td>{evaluationScores.CustomerPhotoRate}</td>
<td>{evaluationScores.CustomerPhotoRateText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Evaluation scores</td>
<td colSpan={3}>4.5</td>
<td>{evaluationScores.EvaluationFormScore}</td>
<td>{evaluationScores.EvaluationFormScoreText}</td>
</tr>
</tbody>
</table>
<br />
<table style={{ textAlign: "center" }}>
<thead className="ant-table-thead">
<tr>
<th scope="col">Operator Support & Local resources</th>
<th scope="col">3 scores</th>
<th scope="col">4 scores</th>
<th scope="col">5 scores</th>
<th scope="col">Your Scores</th>
<th scope="col">Final Scores</th>
<th scope="col">Note</th>
</tr>
</thead>
<tbody className="ant-table-tbody">
<tr className="ant-table-row ant-table-row-level-0">
<td>Response efficiency</td>
<td>1d</td>
<td>6hrs</td>
<td>3hrs</td>
<td>{evaluationScores.ResponseEfficiency}</td>
<td rowSpan="6">{evaluationScores.AvgLocalResources}</td>
<td>{evaluationScores.ResponseEfficiencyText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Provide suggestions and alternatives</td>
<td>\</td>
<td></td>
<td></td>
<td>{evaluationScores.ProvideSuggestions}</td>
<td>{evaluationScores.ProvideSuggestionsText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Provide local tourism information</td>
<td>\</td>
<td></td>
<td></td>
<td>{evaluationScores.ProvideLocalInfo}</td>
<td>{evaluationScores.ProvideLocalInfoText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Assist in developing exclusive products</td>
<td>\</td>
<td>\</td>
<td></td>
<td>{evaluationScores.ExclusiveProducts}</td>
<td>{evaluationScores.ExclusiveProductsText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Dedicated tour guide team for AH</td>
<td>\</td>
<td></td>
<td></td>
<td>{evaluationScores.DedicatedTourGuide}</td>
<td>{evaluationScores.DedicatedTourGuideText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Partner hotels with contracted rate</td>
<td>\</td>
<td></td>
<td></td>
<td>{evaluationScores.PartnerHotels}</td>
<td>{evaluationScores.PartnerHotelsText}</td>
</tr>
</tbody>
</table>
<br />
<table style={{ textAlign: "center" }}>
<thead className="ant-table-thead">
<tr>
<th scope="col">Pricing & Settlement 20%</th>
<th scope="col">3 scores</th>
<th scope="col">4 scores</th>
<th scope="col">5 scores</th>
<th scope="col">Your Scores</th>
<th scope="col">Final Scores</th>
<th scope="col">Note</th>
</tr>
</thead>
<tbody className="ant-table-tbody">
<tr className="ant-table-row ant-table-row-level-0">
<td>Quotation</td>
<td>Package</td>
<td>Day tours</td>
<td>Individual services</td>
<td>{evaluationScores.Quotation}</td>
<td rowSpan="3">{evaluationScores.AvgPricingAndSettlement}</td>
<td>{evaluationScores.QuotationText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Settlement</td>
<td>Prepayment</td>
<td>Monthly Prepayment</td>
<td>Monthly settlement after the tours</td>
<td>{evaluationScores.Settlement}</td>
<td>{evaluationScores.SettlementText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Cancellation policy</td>
<td>30 days</td>
<td>21 days</td>
<td>1 day</td>
<td>{evaluationScores.CancellationPolicy}</td>
<td>{evaluationScores.CancellationPolicyText}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<Divider orientation="right">
<Typography.Title level={3} type="danger">
Final Scores: {evaluationScores.FinalScores}
</Typography.Title>
</Divider>
<br />
<Typography>
<Typography.Title level={3} type="success">
Scoring Rules
</Typography.Title>
<Typography.Paragraph type="success">
<ol>
<li>The maximum score is 5</li>
<li>
The three categories are:
<ul>
<li>Customer satisfaction (40%)</li>
<li>Operator support & local resources (40%)</li>
<li>Pricing & settlement (20%)</li>
</ul>
</li>
<li>For each category, you can only get the corresponding score if you meet the standards of all items under the score.</li>
<li>The final score is the sum of the scores of each category multiplied by the proportion of the category.</li>
</ol>
</Typography.Paragraph>
</Typography>
<br /> const columns_commend = [
<Divider orientation="center"> {
<Typography.Title level={3} type="success">Evaluation Scores</Typography.Title> title: 'Reference Number',
</Divider> dataIndex: 'GRI_No',
<div className="ant-table-wrapper ant-spin-nested-loading css-1x2egda css-dev-only-do-not-override-1x2egda "> key: 'GRI_No',
<div className="ant-spin-container ant-table ant-table-bordered"> },
<div className="ant-table-container"> {
<div className="ant-table-content"> title: 'Tour Guides',
<table style={{ textAlign: "center" }}> dataIndex: 'ObjectName',
<thead className="ant-table-thead"> key: 'ObjectName',
<tr> },
<th scope="col">Category</th> {
<th scope="col">Item</th> title: 'Essential Comments',
<th scope="col">Your Scores</th> dataIndex: 'ECI_Content',
<th scope="col">Note</th> key: 'ECI_Content',
</tr> },
</thead> ];
<tbody className="ant-table-tbody">
<tr className="ant-table-row ant-table-row-level-0">
<td rowSpan="4">DMC Services</td>
<td>Guide</td>
<td>{evaluationScores.FRTGuide}</td>
<td rowSpan="7">{evaluationScores.FRTText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0"> const { toPDF, targetRef } = usePDF({
<td>Driver & Vehicle</td> method: 'save',
<td>{evaluationScores.FRTGriver}</td> filename: 'GHH-Report.pdf',
</tr> page: { margin: '3', format: 'letter' }, //margin: Margin.SMALL,
<tr className="ant-table-row ant-table-row-level-0"> canvas: { mimeType: 'image/jpeg', qualityRatio: 1 },
<td>Food Arrangement</td> overrides: { pdf: { compress: true }, canvas: { useCORS: true } },
<td>{evaluationScores.FRTMeal}</td> });
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Activity</td>
<td>{evaluationScores.FRTProduct}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0"> return (
<td rowSpan="2">Itinerary Arrangements</td> <Space direction='vertical' style={{ width: '100%' }}>
<td>Hotel</td> {/* <Typography.Title level={3}></Typography.Title> */}
<td>{evaluationScores.FRTHotel}</td> <Row gutter={16}>
</tr> <Col md={24} lg={18} xxl={12}>
<tr className="ant-table-row ant-table-row-level-0"> <SearchForm
<td>Travel Advisor&rsquo;s Planning</td> initialValue={{
<td>{evaluationScores.FRTAdvisor}</td> dates: [dayjs().startOf('year'), dayjs().endOf('year')],
</tr> }}
</tbody> defaultValue={{
</table> shows: ['dates'],
</div> fieldProps: {
</div> dates: { label: 'Select Date', col: 12 },
</div> },
</div> fieldComProps: { dates: { picker: 'month', presets: false } },
</Col> }}
<Col md={24} lg={24} xxl={16}> confirmText={t('vendor:report.GetReport')}
<Divider orientation="center"> onSubmit={(err, formVal, filedsVal) => {
<Typography.Title level={3} type="success">Tour Guides Performence</Typography.Title> getHWVendorScores(travelAgencyId, formVal.startdate, formVal.enddate);
</Divider> getHWProductScores(travelAgencyId, formVal.startdate, formVal.enddate);
<Table dataSource={productScoresData.GuideScores} columns={columns_guide} pagination={false} bordered /> getHWCommendScores(travelAgencyId, formVal.startdate, formVal.enddate);
}}
/>
</Col>
<Col md={24} lg={6} xxl={12}>
<Button onClick={() => toPDF()}>Download PDF</Button>
</Col>
</Row>
<Row ref={targetRef}>
<Col md={24} lg={24} xxl={16}>
<Divider orientation='center'>
<Typography.Title level={3} type='success'>
Primary Data
</Typography.Title>
</Divider>
<Table loading={loading} dataSource={primaryData} columns={columns_primary} pagination={false} bordered />
</Col>
<Divider orientation="center"> <Col md={24} lg={24} xxl={16}>
<Typography.Title level={3} type="success">TP Reviews</Typography.Title> <Divider orientation='center'>
</Divider> <Typography.Title level={3} type='success'>
<Table dataSource={commendScoresData.CommendScores} columns={columns_commend} pagination={false} bordered /> Monthly Data
</Typography.Title>
</Divider>
<Table loading={loading} dataSource={vendorScoresData.MonthlyData} columns={columns_month} pagination={false} bordered />
</Col>
<Divider orientation="center"> <Col md={24} lg={24} xxl={16}>
<Typography.Title level={3} type="success">Complaints</Typography.Title> <Divider orientation='center'>
</Divider> <Typography.Title level={3} type='success'>
<Table dataSource={commendScoresData.ComplaintScores} columns={columns_commend} pagination={false} bordered /> DMC Assessment Criteria
</Typography.Title>
</Divider>
<Table key={'cs'} dataSource={DMCData_customer_satisfaction} columns={columns_DMC_customer_satisfaction} pagination={false} bordered />
<br />
<Table key={'sl'} dataSource={DMCData_sopport_local} columns={columns_DMC_sopport_local} pagination={false} bordered />
<br />
<Table key={'p'} dataSource={DMCData_pricing} columns={columns_DMC_pricing} pagination={false} bordered />
<Divider orientation='right'>
<Typography.Title level={3} type='danger'>
Final Scores: {evaluationScores.FinalScores}
</Typography.Title>
</Divider>
<br />
<Typography>
<Typography.Title level={3} type='success'>
Scoring Rules
</Typography.Title>
<Typography.Paragraph type='success'>
<ol>
<li>The maximum score is 5</li>
<li>
The three categories are:
<ul>
<li>Customer satisfaction (40%)</li>
<li>Operator support & local resources (40%)</li>
<li>Pricing & settlement (20%)</li>
</ul>
</li>
<li>For each category, you can only get the corresponding score if you meet the standards of all items under the score.</li>
<li>The final score is the sum of the scores of each category multiplied by the proportion of the category.</li>
</ol>
</Typography.Paragraph>
</Typography>
<Divider orientation="center"> <br />
<Typography.Title level={3} type="success">Suggestions from Customers</Typography.Title> <Divider orientation='center'>
</Divider> <Typography.Title level={3} type='success'>
<Table dataSource={commendScoresData.CriticizeScores} columns={columns_commend} pagination={false} bordered /> Evaluation Scores
</Col> </Typography.Title>
</Row> </Divider>
</Space> <Table dataSource={evaluationScoresData} columns={columns_evaluation} pagination={false} bordered />
); </Col>
<Col md={24} lg={24} xxl={16}>
<Divider orientation='center'>
<Typography.Title level={3} type='success'>
Tour Guides Performence
</Typography.Title>
</Divider>
<Table dataSource={productScoresData.GuideScores} columns={columns_guide} pagination={false} bordered />
<Divider orientation='center'>
<Typography.Title level={3} type='success'>
TP Reviews
</Typography.Title>
</Divider>
<Table dataSource={commendScoresData.CommendScores} columns={columns_commend} pagination={false} bordered />
<Divider orientation='center'>
<Typography.Title level={3} type='success'>
Complaints
</Typography.Title>
</Divider>
<Table dataSource={commendScoresData.ComplaintScores} columns={columns_commend} pagination={false} bordered />
<Divider orientation='center'>
<Typography.Title level={3} type='success'>
Suggestions from Customers
</Typography.Title>
</Divider>
<Table dataSource={commendScoresData.CriticizeScores} columns={columns_commend} pagination={false} bordered />
</Col>
</Row>
</Space>
);
} }
export default observer(Index); export default (Index);

@ -8,7 +8,7 @@ import {
} from '@ant-design/icons'; } from '@ant-design/icons';
import useAuthStore from '@/stores/Auth' import useAuthStore from '@/stores/Auth'
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useReservationStore from '@/stores/Reservation' import BackBtn from '@/components/BackBtn';
const { Title, Paragraph } = Typography; const { Title, Paragraph } = Typography;
const { TextArea } = Input; const { TextArea } = Input;
@ -162,7 +162,7 @@ function Detail() {
<Title level={4}>{t('group:RefNo')}: {reservationDetail.referenceNumber}; {t('group:ArrivalDate')}: {reservationDetail.arrivalDate};</Title> <Title level={4}>{t('group:RefNo')}: {reservationDetail.referenceNumber}; {t('group:ArrivalDate')}: {reservationDetail.arrivalDate};</Title>
</Col> </Col>
<Col span={4}> <Col span={4}>
<Button type="link" onClick={() => navigate('/reservation/newest?back')}>{t('Back')}</Button> <BackBtn to={'/reservation/newest?back'} />
</Col> </Col>
</Row> </Row>
<Row gutter={{ md: 24 }}> <Row gutter={{ md: 24 }}>

Loading…
Cancel
Save