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,7 +38,7 @@ 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)) {
@ -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 };
@ -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();
@ -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>

@ -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