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 [formValuesToSub, setFormValuesToSub] = useFormStore((state) => [state.formValuesToSub, state.setFormValuesToSub]);
const [form] = Form.useForm();
const { sort, hides, shows, fieldProps } = {
const { sort, hides, shows, fieldProps, fieldComProps } = {
sort: '',
// initialValue: '',
fieldProps: '',
fieldComProps: '',
hides: [],
shows: [],
...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}>
{/* <EditableContext.Provider value={form}> */}
<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' */}
<Col flex='1 0 90px' className='flex justify-normal items-start' >
<Space align='center'>
@ -107,7 +108,7 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => {
};
function getFields(props) {
const { fieldProps, form, presets, t } = props;
const { fieldProps, fieldComProps, form, presets, t } = props;
const bigCol = 4 * 2;
const midCol = 6;
const layoutProps = {
@ -136,43 +137,34 @@ function getFields(props) {
99,
<Form.Item name='referenceNo'>
<Input placeholder={t('group:RefNo')} allowClear />
</Form.Item>,4
</Form.Item>,
fieldProps?.referenceNo?.col || 4
),
item(
'invoiceStatus',
99,
<Form.Item name={`invoiceStatus`} initialValue={at(props, 'initialValue.invoiceStatus')[0] || (fieldProps?.invoiceStatus?.show_all ? { key: '-1', label: '所有' } : undefined)}>
<Select style={{ width: '100%' }} placeholder='所有状态' labelInValue>
{fieldProps?.invoiceStatus?.show_all && (
<Select.Option key='所有' value='-1'>
所有
</Select.Option>
)}
<Select.Option key='已成行' value='1'>
已成行
</Select.Option>
<Select.Option key='未成行' value='0'>
未成行
</Select.Option>
</Select>
<Form.Item name={`invoiceStatus`} initialValue={at(props, 'initialValue.invoiceStatus')[0] || { value: '0', label: 'Status' }}>
<Select
style={{ width: '100%' }}
labelInValue
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' },
]}
/>
</Form.Item>,
3
fieldProps?.referenceNo?.col || 3
),
item(
'dates',
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} /> */}
<RangePicker
allowClear={true}
inputReadOnly={true}
presets={presets}
// defaultValue={toJS(arrivalDateRange)}
placeholder={['From', 'Thru']}
// onChange={(dateRange) => {
// reservationStore.updatePropertyValue('arrivalDateRange', dateRange == null ? [] : dateRange)
// }}
/>
<RangePicker allowClear={true} inputReadOnly={true} presets={presets} placeholder={['From', 'Thru']} {...fieldComProps.dates} />
</Form.Item>,
fieldProps?.dates?.col || midCol
),

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

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

@ -9,7 +9,6 @@ import dayjs from "dayjs";
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
const initialState = {
invoiceList: [], //账单列表
invoicekImages: [], //图片列表
@ -122,9 +121,9 @@ export class Invoice {
});
}
fetchInvoiceDetail(GMDSN, GSN) {
fetchInvoiceDetail(VEI_SN, GMDSN, GSN) {
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("GMD_SN", GMDSN)
.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 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("Currency", Currency);
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 formData = new FormData();
formData.append("LMI_SN", this.root.authStore.login.userId);
formData.append("VEI_SN", this.root.authStore.login.travelAgencyId);
formData.append("LMI_SN", LMI_SN);
formData.append("VEI_SN", VEI_SN);
formData.append("GRI_SN", GRI_SN);
formData.append("Currency", Currency);
formData.append("Cost", Cost);

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

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

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

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

@ -8,6 +8,8 @@ import { PlusOutlined, AuditOutlined, SmileOutlined, SolutionOutlined, EditOutli
import { isNotEmpty } from "@/utils/commons";
import * as config from "@/config";
import dayjs from "dayjs";
import useAuthStore from '@/stores/Auth';
import BackBtn from '@/components/BackBtn';
const { Title,Text } = Typography;
const { TextArea } = Input;
@ -15,7 +17,10 @@ const { TextArea } = Input;
function Detail() {
const navigate = useNavigate();
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 [form] = Form.useForm();
const [dataLoading, setDataLoading] = useState(false);
@ -33,9 +38,9 @@ function Detail() {
function defaultShow() {
setDataLoading(true);
invoiceStore
.fetchInvoiceDetail(GMDSN, GSN)
.fetchInvoiceDetail(travelAgencyId, GMDSN, GSN)
.then(json => {
let ZDDetail = json.ZDDetail;
let ZDDetail = json.ZDDetail;
if (isNotEmpty(ZDDetail)) {
let arrLen = ZDDetail.length;
const formData = ZDDetail.map((data, index) => {
@ -109,7 +114,7 @@ function Detail() {
console.log("Success:", 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);
runInAction(() => {
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!",
placement: "top",
duration: 4,
});
});
}
});
}
@ -163,7 +168,7 @@ function Detail() {
function addInvoice() {
invoiceStore
.postAddInvoice(GSN, "", 0, "", "[]", "")
.postAddInvoice(userId, travelAgencyId, GSN, "", 0, "", "[]", "")
.then(data => {})
.finally(() => {
defaultShow();
@ -272,13 +277,13 @@ function Detail() {
},
]}
>
<DatePicker picker="month" />
</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>
<DatePicker picker="month" />
</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>
<Form.Item name="info_gmdsn" hidden={true}>
<input />
</Form.Item>
<br/>
<br/>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
@ -290,7 +295,7 @@ function Detail() {
<Upload
name="ghhfile"
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}
listType="picture-card"
onChange={handleChange}
@ -333,9 +338,7 @@ function Detail() {
<Title level={4}>Reference Number: {invoiceGroupInfo.VGroupInfo}</Title>
</Col>
<Col span={4}>
<Button type="link" onClick={() => navigate("/invoice")}>
Back
</Button>
<BackBtn />
</Col>
</Row>
<Title level={5}></Title>

@ -7,13 +7,19 @@ import { useStore } from "@/stores/StoreContext.js";
import * as config from "@/config";
import { formatDate, isNotEmpty } from "@/utils/commons";
import { AuditOutlined, SmileOutlined, SolutionOutlined, EditOutlined } from "@ant-design/icons";
import useAuthStore from '@/stores/Auth';
import usePresets from '@/hooks/usePresets';
import SearchForm from '@/components/SearchForm';
import dayjs from 'dayjs';
const { Title } = Typography;
function Index() {
const { authStore, invoiceStore } = useStore();
const { invoiceList, search_date_start, search_date_end } = invoiceStore;
const [travelAgencyId ] = useAuthStore((state) => [state.loginUser.travelAgencyId]);
const [groupNo, onGroupNoChange] = useState("");
const [OrderType, onOrderTypeChange] = useState(0); //
const navigate = useNavigate();
@ -76,76 +82,36 @@ function Index() {
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Title level={3}></Title>
<Row gutter={16}>
<Col md={24} lg={4} xxl={4}>
<Input
placeholder="Reference Number"
onChange={e => {
onGroupNoChange(e.target.value);
}}
/>
</Col>
<Col md={24} lg={4} xxl={3}>
<Row gutter={16}>
<Col flex="auto">
<SearchForm
initialValue={{
dates: [dayjs().subtract(2, 'M').startOf('M'), dayjs().endOf('M')],
}}
defaultValue={{
shows: ['referenceNo', 'invoiceStatus', 'dates'],
fieldProps: {
referenceNo: { col: 5},
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>
<Col md={24} lg={24} xxl={24}>
<Table bordered pagination={{ defaultPageSize: 20, showTotal: showTotal }} columns={invoiceListColumns} dataSource={toJS(invoiceList)} />

@ -1,121 +1,104 @@
import { NavLink, useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
import { observer } from "mobx-react";
import { toJS } from "mobx";
import { Row, Col, Space, Button, Table, Input, DatePicker, Typography, App, Image } from "antd";
import { useStore } from "@/stores/StoreContext.js";
import * as config from "@/config";
import { formatDate, isNotEmpty } from "@/utils/commons";
import { NavLink, useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { toJS } from 'mobx';
import { Row, Col, Space, Button, Table, Input, DatePicker, Typography, App, Image } from 'antd';
import { useStore } from '@/stores/StoreContext.js';
import * as config from '@/config';
import { formatDate, isNotEmpty } from '@/utils/commons';
import usePresets from '@/hooks/usePresets';
const { Title } = Typography;
function Paid(){
const { authStore, invoiceStore } = useStore();
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,"","","");
import SearchForm from '@/components/SearchForm';
import useAuthStore from '@/stores/Auth';
import dayjs from 'dayjs';
import BackBtn from '@/components/BackBtn';
},[]);
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 (
const { Title } = Typography;
JSON.parse(strPic).map((item,index) => {
return <Image key={index} width={90} src={item.url} />;
})
function Paid() {
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`;
);
}else{
return "";
}
useEffect(() => {
invoiceStore.fetchInvoicePaid(travelAgencyId, '', '', '');
}, []);
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%" }}>
<Row gutter={16}>
<Col span={20}>
</Col>
<Col span={4}>
<Button type="link" onClick={() => navigate("/invoice")}>
Back
</Button>
</Col>
</Row>
<Title level={3}></Title>
<Row gutter={16}>
<Col md={24} lg={6} xxl={4}>
<Input
placeholder="Reference Number"
onChange={e => {
onGroupNoChange(e.target.value);
}}
/>
</Col>
<Col md={24} lg={8} xxl={6}>
<Space direction="horizontal">
Date
<DatePicker.RangePicker format={config.DATE_FORMAT} allowClear={false} style={{ width: "100%" }} value={[search_date_start, search_date_end]} presets={usePresets()} onChange={invoiceStore.onDateRangeChange} />
</Space>
</Col>
<Col md={24} lg={4} xxl={4}>
<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>
);
return (
<Space direction='vertical' style={{ width: '100%' }}>
<Row gutter={16}>
<Col span={20}>
<SearchForm
initialValue={{
dates: [dayjs().subtract(2, 'M').startOf('M'), dayjs().endOf('M')],
}}
defaultValue={{
shows: ['referenceNo', 'dates'],
fieldProps: {
referenceNo: { col: 5 },
dates: { col: 10, label: 'Date' },
},
}}
onSubmit={(err, formVal, filedsVal) => {
invoiceStore.fetchInvoicePaid(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.enddate);
}}
/>
</Col>
<Col span={4}>
<BackBtn to={'/invoice'} />
</Col>
</Row>
<Row>
<Col md={24} lg={24} xxl={24}>
<Table bordered columns={invoicePaidColumns} dataSource={toJS(invoicePaid)} />
</Col>
</Row>
</Space>
);
}
export default observer(Paid);

@ -6,16 +6,20 @@ import { Row, Col, Space, Button, Table, Typography } from "antd";
import { useStore } from "@/stores/StoreContext.js";
import * as config from "@/config";
import { formatDate, isNotEmpty } from "@/utils/commons";
import useAuthStore from '@/stores/Auth';
import BackBtn from '@/components/BackBtn';
const { Title } = Typography;
function PaidDetail(){
const navigate = useNavigate();
const { authStore, invoiceStore } = useStore();
const { invoiceStore } = useStore();
const { invoicePaidDetail } = invoiceStore;
const [travelAgencyId] = useAuthStore((state) => [state.loginUser.travelAgencyId]);
const { flid } = useParams();
useEffect(() => {
invoiceStore.fetchInvoicePaidDetail(authStore.login.travelAgencyId,flid);
invoiceStore.fetchInvoicePaidDetail(travelAgencyId,flid);
},[flid]);
@ -49,9 +53,7 @@ function PaidDetail(){
<Col span={20}>
</Col>
<Col span={4}>
<Button type="link" onClick={() => navigate("/invoice/paid")}>
Back
</Button>
<BackBtn />
</Col>
</Row>
<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 { useEffect, useState } from 'react';
import { Row, Col, Space, Typography, Divider } from 'antd';
import { useStore } from '@/stores/StoreContext.js';
import * as comm from '@/utils/commons';
import { useTranslation } from 'react-i18next';
import { fetchNoticeDetail } from '@/stores/Notice';
import useAuthStore from '@/stores/Auth';
import BackBtn from '@/components/BackBtn';
const { Title, Paragraph } = Typography;
function Detail() {
const { t } = useTranslation();
const { authStore } = useStore();
const { CCP_BLID } = useParams();
const [userId, ] = useAuthStore((state) => [state.loginUser.userId]);
const [noticeInfo, setNoticeInfo] = useState({});
useEffect(() => {
// console.info("notice detail .useEffect " + CCP_BLID);
fetchNoticeDetail(authStore.login.userId, CCP_BLID).then((res) => {
fetchNoticeDetail(userId, CCP_BLID).then((res) => {
setNoticeInfo(res);
});
}, []);
@ -33,7 +34,7 @@ function Detail() {
</Paragraph>
</Col>
<Col span={4}>
<NavLink to='/notice'>{t('Back')}</NavLink>
<BackBtn />
</Col>
</Row>
</Space>

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

@ -1,505 +1,404 @@
import { useEffect } from "react";
import { observer } from "mobx-react";
import { Row, Col, Space, Button, Table, Divider, Typography, DatePicker } from "antd";
import { useStore } from "@/stores/StoreContext.js";
import * as config from "@/config";
import * as comm from "@/utils/commons";
import { usePDF } from "react-to-pdf";
import { Row, Col, Space, Button, Table, Divider, Typography, } from 'antd';
import * as comm from '@/utils/commons';
import { usePDF } from 'react-to-pdf';
import dayjs from 'dayjs';
import SearchForm from '@/components/SearchForm';
import useAuthStore from '@/stores/Auth';
import useReportStore from '@/stores/Report';
import { useTranslation } from 'react-i18next'
function Index() {
const { reportStore, authStore } = useStore();
const { search_date_start, search_date_end, vendorScoresData, productScoresData, commendScoresData } = reportStore;
const evaluationScores = vendorScoresData.EvaluationScores ? vendorScoresData.EvaluationScores[0] : [];
useEffect(() => {}, []);
const { t } = useTranslation();
const columns_month = [
{
title: "Date",
dataIndex: "VMonth",
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 [travelAgencyId, ] = useAuthStore((state) => [state.loginUser.travelAgencyId]);
const [loading, vendorScoresData, getHWVendorScores] = useReportStore((state) => [state.loading, state.vendorScoresData, state.getHWVendorScores]);
const [productScoresData, getHWProductScores] = useReportStore((state) => [state.productScoresData, state.getHWProductScores]);
const [commendScoresData, getHWCommendScores] = useReportStore((state) => [state.commendScoresData, state.getHWCommendScores]);
const columns_guide = [
{
title: "Name",
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 evaluationScores = vendorScoresData.EvaluationScores ? vendorScoresData.EvaluationScores[0] : [];
const primaryData = vendorScoresData.EvaluationScores
? [comm.pick(vendorScoresData.EvaluationScores[0], ['Groups', 'PersonNum', 'AmountUSD', 'EvaluationScore', 'TPReviews', 'TPReviewRate', 'Complaints', 'ComplaintRate'])]
: [];
const columns_commend = [
{
title: "Reference Number",
dataIndex: "GRI_No",
key: "GRI_No",
},
{
title: "Tour Guides",
dataIndex: "ObjectName",
key: "ObjectName",
},
{
title: "Essential Comments",
dataIndex: "ECI_Content",
key: "ECI_Content",
},
];
const evaluationScoresData = [
{ category: 'DMC Services', item: 'Guide', value: evaluationScores.FRTGuide, note: evaluationScores.FRTText },
{ category: 'DMC Services', item: 'Driver & Vehicle', value: evaluationScores.FRTGriver },
{ category: 'DMC Services', item: 'Food Arrangement', value: evaluationScores.FRTMeal },
{ 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 },
];
const columns_evaluation = [
{ 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: 'Note', dataIndex: 'note', key: 'note',align: 'center', onCell: (_, index) => ({ rowSpan: index === 0 ? 7 : 0 })},
];
const { toPDF, targetRef } = usePDF({
method: "save",
filename: "GHH-Report.pdf",
page: { margin: "3", format: "letter" }, //margin: Margin.SMALL,
canvas: { mimeType: "image/jpeg", qualityRatio: 1 },
overrides: { pdf: { compress: true }, canvas: { useCORS: true } },
});
const columns_DMC_customer_satisfaction = [
{ title: 'Customer Satisfaction', dataIndex: 'item', key: 'item',align: 'center', },
{ title: '3 scores', dataIndex: 'stand_3', key: 'stand_3', align: 'center', onCell: (_, index) => ({ colSpan: (index === 2 || index===4) ? 3 : 1 }) },
{ title: '4 scores', dataIndex: 'stand_4', key: 'stand_4',align: 'center',onCell: (_, index) => ({ colSpan: (index === 2 || index===4) ? 0 : 1 }) },
{ title: '5 scores', dataIndex: 'stand_5', key: 'stand_5',align: 'center',onCell: (_, index) => ({ colSpan: (index === 2 || index===4) ? 0 : 1 }) },
{ 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 (
<Space direction="vertical" style={{ width: "100%" }}>
<Typography.Title level={3}></Typography.Title>
<Row gutter={16}>
<Col md={24} lg={8} xxl={6}>
<Space direction="horizontal">
Select Date
<DatePicker.RangePicker
format={config.DATE_FORMAT_MONTH}
allowClear={true}
style={{ width: "100%" }}
defaultValue={[search_date_start, search_date_end]}
onChange={reportStore.onDateRangeChange}
allowEmpty={true}
picker="month"
/>
</Space>
</Col>
<Col md={24} lg={4} xxl={4}>
<Button
type="primary"
loading={reportStore.loading}
onClick={() => {
reportStore.getHWVendorScores(
authStore.login.travelAgencyId,
search_date_start == null ? null : search_date_start.format(config.DATE_FORMAT),
search_date_end == null ? null : search_date_end.format(config.DATE_FORMAT)
);
reportStore.getHWProductScores(
authStore.login.travelAgencyId,
search_date_start == null ? null : search_date_start.format(config.DATE_FORMAT),
search_date_end == null ? null : search_date_end.format(config.DATE_FORMAT)
);
reportStore.getHWCommendScores(
authStore.login.travelAgencyId,
search_date_start == null ? null : search_date_start.format(config.DATE_FORMAT),
search_date_end == null ? null : search_date_end.format(config.DATE_FORMAT)
);
}}>
Get Report
</Button>
</Col>
<Col md={24} lg={10} xxl={11}>
<Button onClick={() => toPDF()}>Download PDF</Button>
</Col>
</Row>
<Typography.Title level={3}></Typography.Title>
const columns_DMC_sopport_local = [
{ title: 'Operator Support & Local resources', 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 ? 6 : 0 })},
{ title: 'Note', dataIndex: 'note', key: 'note',align: 'center', },
];
const DMCData_sopport_local = [
{
item: 'Response efficiency',
stand_3: '1d',
stand_4: '6hrs',
stand_5: '3hrs',
value: evaluationScores.ResponseEfficiency,
final_score: evaluationScores.AvgLocalResources,
note: evaluationScores.ResponseEfficiencyText,
},
{
item: 'Provide suggestions and alternatives',
stand_3: '\\',
stand_4: '√',
stand_5: '√',
value: evaluationScores.ProvideSuggestions,
// final_score: evaluationScores.AvgCusSatisfaction,
note: evaluationScores.ProvideSuggestionsText,
},
{
item: 'Provide local tourism information',
stand_3: '\\',
stand_4: '√',
stand_5: '√',
value: evaluationScores.ProvideLocalInfo,
note: evaluationScores.ProvideLocalInfoText,
},
{ item: 'Assist in developing exclusive products',
stand_3: '\\',
stand_4: '\\',
stand_5: '√', value: evaluationScores.ExclusiveProducts, note: evaluationScores.ExclusiveProductsText },
{ item: 'Dedicated tour guide team for AH',
stand_3: '\\',
stand_4: '√',
stand_5: '√', value: evaluationScores.DedicatedTourGuide, note: evaluationScores.DedicatedTourGuideText },
{ item: 'Partner hotels with contracted rate',
stand_3: '\\',
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}>
<Col md={24} lg={24} xxl={16}>
<Divider orientation="center">
<Typography.Title level={3} type="success">Primary Data</Typography.Title>
</Divider>
<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">
<div className="ant-table-content">
<table style={{ textAlign: "center" }}>
<thead className="ant-table-thead">
<tr>
<th scope="col">Groups</th>
<th scope="col">Number of People</th>
<th scope="col">Transaction Amount (USD)</th>
<th scope="col">Evaluation Score</th>
<th scope="col">TP Reviews</th>
<th scope="col">TP Reviews Rate</th>
<th scope="col">Complaints</th>
<th scope="col">Complaint Rate</th>
</tr>
</thead>
<tbody className="ant-table-tbody">
<tr className="ant-table-row ant-table-row-level-0">
<td>{evaluationScores.Groups}</td>
<td>{evaluationScores.PersonNum}</td>
<td>{comm.formatPrice(evaluationScores.AmountUSD)}</td>
<td>{evaluationScores.EvaluationScore}</td>
<td>{evaluationScores.TPReviews}</td>
<td>{comm.formatPercent(evaluationScores.TPReviewRate)}</td>
<td>{evaluationScores.Complaints}</td>
<td>{comm.formatPercent(evaluationScores.ComplaintRate)}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</Col>
const columns_primary = [
{
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),
},
];
<Col md={24} lg={24} xxl={16}>
<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>
const columns_month = [{ title: 'Date', dataIndex: 'VMonth', key: 'VMonth' }, ...columns_primary];
<Col md={24} lg={24} xxl={16}>
<Divider orientation="center">
<Typography.Title level={3} type="success">DMC Assessment Criteria</Typography.Title>
</Divider>
<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">
<div className="ant-table-content">
<table style={{ textAlign: "center" }}>
<thead className="ant-table-thead">
<tr>
<th scope="col">Customer Satisfaction</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>TP review rating</td>
<td>\</td>
<td>30%</td>
<td>60%</td>
<td>{evaluationScores.TPReviewRating}</td>
<td rowSpan="5">{evaluationScores.AvgCusSatisfaction}</td>
<td>{evaluationScores.TPReviewRatingText}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Post tour complaints</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>{evaluationScores.PostTourComplaints}</td>
<td>{evaluationScores.PostTourComplaintsText}</td>
</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>
const columns_guide = [
{
title: 'Name',
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),
},
];
<br />
<Divider orientation="center">
<Typography.Title level={3} type="success">Evaluation Scores</Typography.Title>
</Divider>
<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">
<div className="ant-table-content">
<table style={{ textAlign: "center" }}>
<thead className="ant-table-thead">
<tr>
<th scope="col">Category</th>
<th scope="col">Item</th>
<th scope="col">Your 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 rowSpan="4">DMC Services</td>
<td>Guide</td>
<td>{evaluationScores.FRTGuide}</td>
<td rowSpan="7">{evaluationScores.FRTText}</td>
</tr>
const columns_commend = [
{
title: 'Reference Number',
dataIndex: 'GRI_No',
key: 'GRI_No',
},
{
title: 'Tour Guides',
dataIndex: 'ObjectName',
key: 'ObjectName',
},
{
title: 'Essential Comments',
dataIndex: 'ECI_Content',
key: 'ECI_Content',
},
];
<tr className="ant-table-row ant-table-row-level-0">
<td>Driver & Vehicle</td>
<td>{evaluationScores.FRTGriver}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Food Arrangement</td>
<td>{evaluationScores.FRTMeal}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Activity</td>
<td>{evaluationScores.FRTProduct}</td>
</tr>
const { toPDF, targetRef } = usePDF({
method: 'save',
filename: 'GHH-Report.pdf',
page: { margin: '3', format: 'letter' }, //margin: Margin.SMALL,
canvas: { mimeType: 'image/jpeg', qualityRatio: 1 },
overrides: { pdf: { compress: true }, canvas: { useCORS: true } },
});
<tr className="ant-table-row ant-table-row-level-0">
<td rowSpan="2">Itinerary Arrangements</td>
<td>Hotel</td>
<td>{evaluationScores.FRTHotel}</td>
</tr>
<tr className="ant-table-row ant-table-row-level-0">
<td>Travel Advisor&rsquo;s Planning</td>
<td>{evaluationScores.FRTAdvisor}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</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 />
return (
<Space direction='vertical' style={{ width: '100%' }}>
{/* <Typography.Title level={3}></Typography.Title> */}
<Row gutter={16}>
<Col md={24} lg={18} xxl={12}>
<SearchForm
initialValue={{
dates: [dayjs().startOf('year'), dayjs().endOf('year')],
}}
defaultValue={{
shows: ['dates'],
fieldProps: {
dates: { label: 'Select Date', col: 12 },
},
fieldComProps: { dates: { picker: 'month', presets: false } },
}}
confirmText={t('vendor:report.GetReport')}
onSubmit={(err, formVal, filedsVal) => {
getHWVendorScores(travelAgencyId, formVal.startdate, formVal.enddate);
getHWProductScores(travelAgencyId, formVal.startdate, formVal.enddate);
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">
<Typography.Title level={3} type="success">TP Reviews</Typography.Title>
</Divider>
<Table dataSource={commendScoresData.CommendScores} columns={columns_commend} pagination={false} bordered />
<Col md={24} lg={24} xxl={16}>
<Divider orientation='center'>
<Typography.Title level={3} type='success'>
Monthly Data
</Typography.Title>
</Divider>
<Table loading={loading} dataSource={vendorScoresData.MonthlyData} columns={columns_month} pagination={false} bordered />
</Col>
<Divider orientation="center">
<Typography.Title level={3} type="success">Complaints</Typography.Title>
</Divider>
<Table dataSource={commendScoresData.ComplaintScores} columns={columns_commend} pagination={false} bordered />
<Col md={24} lg={24} xxl={16}>
<Divider orientation='center'>
<Typography.Title level={3} type='success'>
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">
<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>
);
<br />
<Divider orientation='center'>
<Typography.Title level={3} type='success'>
Evaluation Scores
</Typography.Title>
</Divider>
<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';
import useAuthStore from '@/stores/Auth'
import { useTranslation } from 'react-i18next';
import useReservationStore from '@/stores/Reservation'
import BackBtn from '@/components/BackBtn';
const { Title, Paragraph } = Typography;
const { TextArea } = Input;
@ -162,7 +162,7 @@ function Detail() {
<Title level={4}>{t('group:RefNo')}: {reservationDetail.referenceNumber}; {t('group:ArrivalDate')}: {reservationDetail.arrivalDate};</Title>
</Col>
<Col span={4}>
<Button type="link" onClick={() => navigate('/reservation/newest?back')}>{t('Back')}</Button>
<BackBtn to={'/reservation/newest?back'} />
</Col>
</Row>
<Row gutter={{ md: 24 }}>

Loading…
Cancel
Save