feat: 多语言, 切换语言

dev/i18n
Lei OT 1 year ago
parent 588cdab3c5
commit 7e0773fcb2

@ -11,10 +11,13 @@
"dependencies": { "dependencies": {
"@react-pdf/renderer": "^3.4.0", "@react-pdf/renderer": "^3.4.0",
"antd": "^5.4.2", "antd": "^5.4.2",
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.0.0",
"mobx": "^6.9.0", "mobx": "^6.9.0",
"mobx-react": "^7.6.0", "mobx-react": "^7.6.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^14.1.2",
"react-router-dom": "^6.10.0", "react-router-dom": "^6.10.0",
"react-to-pdf": "^1.0.1" "react-to-pdf": "^1.0.1"
}, },

@ -0,0 +1,42 @@
import { useEffect, useState } from 'react';
import dayjs from "dayjs";
import { useTranslation } from 'react-i18next';
const usePresets = () => {
const [presets, setPresets] = useState([]);
const { t, i18n } = useTranslation();
useEffect(() => {
const newPresets = [
{
label: t("datetime.thisWeek"),
value: [dayjs().startOf("w"), dayjs().endOf("w")],
},
{
label: t("datetime.lastWeek"),
value: [dayjs().startOf("w").subtract(7, "days"), dayjs().endOf("w").subtract(7, "days")],
},
{
label: t("datetime.thisMonth"),
value: [dayjs().startOf("M"), dayjs().endOf("M")],
},
{
label: t("datetime.lastMonth"),
value: [dayjs().subtract(1, "M").startOf("M"), dayjs().subtract(1, "M").endOf("M")],
},
{
label: t("datetime.lastThreeMonth"),
value: [dayjs().subtract(2, "M").startOf("M"), dayjs().endOf("M")],
},
{
label: t("datetime.thisYear"),
value: [dayjs().startOf("y"), dayjs().endOf("y")],
},
];
setPresets(newPresets);
}, [i18n.language]);
return presets;
}
export default usePresets;

@ -0,0 +1,45 @@
import i18n from 'i18next';
//
// https://github.com/i18next/i18next-browser-languageDetector
// localStorage.getItem('i18nextLng')
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import en from './locales/en.json';
import zh from './locales/zh.json';
i18n
.use(initReactI18next)
.use(LanguageDetector)
// https://www.i18next.com/overview/configuration-options
.init({
// detection: {
// convertDetectedLanguage: 'Iso15897',
// convertDetectedLanguage: (lng) => lng.replace('-', '_')
// },
supportedLngs: ['en', 'zh'],
resources: {
en: { translation: en },
zh: { translation: zh },
},
fallbackLng: 'en',
// fallbackLng: (code) => {
// if (!code || code === 'en') return ['en'];
// const fallbacks = []; // [code];
// // add pure lang
// const langPart = code.split('-')[0];
// if (langPart !== code) fallbacks.push(langPart);
// fallbacks.push('en');
// console.log('fallbacks', fallbacks);
// return fallbacks;
// },
preload: ['en', 'zh'],
interpolation: {
escapeValue: false,
},
// keySeparator: false,
debug: false,
});
export default i18n;

@ -0,0 +1,71 @@
{
"lang": {
"en": "English",
"zh": "中文"
},
"menu": {
"Reservation": "Reservation",
"Invoice": "Invoice",
"Feedback": "Feedback",
"Notice": "Notice"
},
"loginAction": {
"ChangePassword": "Change password",
"Profile": "Profile",
"Logout": "Logout",
"ChangeVendor": "Change Vendor",
"LoginTimeout": "Login timeout",
"LoginTimeoutTip": "Please input your password"
},
"common": {
"Search": "Search",
"Reset": "Reset",
"Cancel": "Cancel",
"Submit": "Submit",
"Confirm": "Confirm",
"Close": "Close",
"Save": "Save",
"Edit": "Edit",
"Delete": "Delete",
"Add": "Add",
"View": "View",
"Back": "Back",
"Download": "Download",
"Login": "Login"
},
"form": {
"Username": "Username",
"Password": "Password"
},
"datetime": {
"thisWeek": "This Week",
"lastWeek": "Last Week",
"thisMonth": "This Month",
"lastMonth": "Last Month",
"lastThreeMonth": "Last Three Month",
"thisYear": "This Year"
},
"group": {
"ArrivalDate": "Arrival Date",
"RefNo": "Reference number",
"Pax": "Pax",
"Status": "Status",
"City": "City",
"Guide": "Guide",
"ResSendingDate": "Res. sending date",
"3DGuideTip": "Reservations without the tour guide information will be highlighted in red if the arrival date is within 3 days.",
"Attachments": "Attachments",
"ConfirmationDate": "Confirmation Date",
"ConfirmationDetails": "Confirmation Details",
"Rate Code": "Rate Code",
"Rate Plan": "Rate Plan",
"Rate Type": "Rate Type",
"Rate Category": "Rate Category",
"Rate Class": "Rate Class",
"Rate Family": "Rate Family",
"Rate Group": "Rate Group",
"Rate Sub Group": "Rate Sub Group",
"Rate Sub Group Code": "Rate Sub Group Code"
}
}

@ -0,0 +1,63 @@
{
"lang": {
"en": "English",
"zh": "中文"
},
"menu": {
"Reservation": "团预订",
"Invoice": "账单",
"Feedback": "反馈表",
"Notice": "通知"
},
"loginAction": {
"ChangePassword": "修改密码",
"Profile": "账户中心",
"Logout": "退出",
"ChangeVendor": "切换账户",
"LoginTimeout": "登录超时",
"LoginTimeoutTip": "请输入密码"
},
"common": {
"Search": "查询",
"Reset": "重置",
"Cancel": "取消",
"Submit": "提交",
"Confirm": "确认",
"Close": "关闭",
"Save": "保存",
"Edit": "编辑",
"Delete": "删除",
"Add": "添加",
"View": "查看",
"Back": "返回",
"Download": "下载",
"Login": "登录"
},
"form": {
"Username": "账户名",
"Password": "密码"
},
"datetime": {
"thisWeek": "本周",
"lastWeek": "上周",
"thisMonth": "本月",
"lastMonth": "上月",
"lastThreeMonth": "前三个月",
"thisYear": "今年"
},
"group": {
"ArrivalDate": "抵达日期",
"RefNo": "团号",
"Pax": "人数",
"Status": "状态",
"City": "城市",
"Guide": "导游",
"ResSendingDate": "发送时间",
"3DGuideTip": "红色突出显示:抵达日期在 3 天内,没有导游信息的预订。",
"Attachments": "附件",
"ConfirmationDate": "确认日期",
"ConfirmationDetails": "确认信息",
"Rate Code": "Rate Code"
}
}

@ -29,6 +29,7 @@ import InvoicePaid from "@/views/invoice/Paid";
import InvoicePaidDetail from "@/views/invoice/PaidDetail"; import InvoicePaidDetail from "@/views/invoice/PaidDetail";
import ChangeVendor from "@/views/account/ChangeVendor"; import ChangeVendor from "@/views/account/ChangeVendor";
import './i18n';
configure({ configure({
useProxies: "ifavailable", useProxies: "ifavailable",

@ -9,58 +9,58 @@ import AppLogo from "@/assets/logo-gh.png";
import { isEmpty } from "@/utils/commons"; import { isEmpty } from "@/utils/commons";
import { useStore } from "@/stores/StoreContext.js"; import { useStore } from "@/stores/StoreContext.js";
import * as config from "@/config"; import * as config from "@/config";
import Language from "./Language";
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n/index';
import zhLocale from 'antd/locale/zh_CN';
import enLocale from 'antd/locale/en_US';
import 'dayjs/locale/zh-cn';
const { Header, Content, Footer } = Layout; const { Header, Content, Footer } = Layout;
const { Title } = Typography; const { Title } = Typography;
// const { t } = useTranslation();
let items = []; let items = [];
const items_default = [ const useItemDefault = () => {
{ const [data, setData] = useState([]);
label: <Link to="/account/change-password">Change password</Link>, const { t, i18n } = useTranslation();
key: "0",
}, useEffect(() => {
{ const newData = [
label: <Link to="/account/profile">Profile</Link>, { label: <Link to='/account/change-password'>{t('loginAction.ChangePassword')}</Link>, key: '0' },
key: "1", { label: <Link to='/account/profile'>{t('loginAction.Profile')}</Link>, key: '1' },
}, { type: 'divider' },
{ { label: <Link to='/login?out'>{t('loginAction.Logout')}</Link>, key: '3' },
type: "divider", ];
}, setData(newData);
{ }, [i18n.language]);
label: <Link to="/login?out">Logout</Link>,
key: "3", return data;
}, };
]; const useItemManager = () => {
const items_default = useItemDefault();
const item_manager = const [data, setData] = useState(items_default);
[ const { t, i18n } = useTranslation();
{
label: <Link to="/account/change-password">Change password</Link>, useEffect(() => {
key: "0", const newData = [
}, { label: <Link to='/account/change-password'>{t('loginAction.ChangePassword')}</Link>, key: '0' },
{ { label: <Link to='/account/profile'>{t('loginAction.Profile')}</Link>, key: '1' },
label: <Link to="/account/profile">Profile</Link>, { type: 'divider' },
key: "1", { label: <Link to='/login?out'>{t('loginAction.Logout')}</Link>, key: '3' },
}, { label: <Link to='/account/change-vendor'>{t('loginAction.ChangeVendor')}</Link>, key: '4' },
{ ];
type: "divider", setData(newData);
}, }, [i18n.language]);
{ return data;
label: <Link to="/login?out">Logout</Link>, };
key: "3",
},
{
label:<Link to="/account/change-vendor">Change Vendor</Link>,
key:"4",
},
];
function App() { function App() {
const { t } = useTranslation();
const items_default = useItemDefault();
const item_manager = useItemManager();
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const { authStore, noticeStore } = useStore(); const { authStore, noticeStore } = useStore();
const { notification } = AntApp.useApp(); const { notification } = AntApp.useApp();
@ -124,8 +124,12 @@ function App() {
token: { colorBgContainer }, token: { colorBgContainer },
} = theme.useToken(); } = theme.useToken();
const [antdLng, setAntdLng] = useState(enLocale);
useEffect(() => {
setAntdLng(i18n.language === 'en' ? enLocale : zhLocale);
}, [i18n.language]);
return ( return (
<ConfigProvider <ConfigProvider locale={antdLng}
theme={{ theme={{
token: { token: {
colorPrimary: "#00b96b", colorPrimary: "#00b96b",
@ -140,17 +144,17 @@ function App() {
footer={null} footer={null}
open={login.timeout} open={login.timeout}
> >
<Title level={3}>Login timeout</Title> <Title level={3}>{t('loginAction.LoginTimeout')}</Title>
<span>Please input your password</span> <div>{t('loginAction.LoginTimeoutTip')}</div>
<Space direction="horizontal"> <Space direction="horizontal">
<Input.Password value={password} <Input.Password value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
onPressEnter={() => onSubmit()} onPressEnter={() => onSubmit()}
addonBefore={login.username} /> addonBefore={login.username} />
<Button <Button
onClick={() => onSubmit()} onClick={() => onSubmit()}
>Submit</Button></Space> >{t('common.Submit')}</Button></Space>
</Modal> </Modal>
<Layout <Layout
style={{ style={{
@ -167,15 +171,15 @@ function App() {
mode="horizontal" mode="horizontal"
selectedKeys={[defaultPath]} selectedKeys={[defaultPath]}
items={[ items={[
{ key: "reservation", label: <Link to="/reservation/newest">Reservation</Link> }, { key: "reservation", label: <Link to="/reservation/newest">{t('menu.Reservation')}</Link> },
{ key: "invoice", label: <Link to="/invoice">Invoice</Link> }, { key: "invoice", label: <Link to="/invoice">{t('menu.Invoice')}</Link> },
{ key: "feedback", label: <Link to="/feedback">Feedback</Link> }, { key: "feedback", label: <Link to="/feedback">{t('menu.Feedback')}</Link> },
// { key: "report", label: <Link to="/report">Report</Link> }, // { key: "report", label: <Link to="/report">Report</Link> },
{ {
key: "notice", key: "notice",
label: ( label: (
<Link to="/notice"> <Link to="/notice">
Notice {t('menu.Notice')}
{noticeUnRead ? <Badge dot /> : ""} {noticeUnRead ? <Badge dot /> : ""}
</Link> </Link>
), ),
@ -188,7 +192,7 @@ function App() {
{authStore.login.travelAgencyName} {authStore.login.travelAgencyName}
</Title> </Title>
</Col> </Col>
<Col span={4}> <Col span={2}>
<Dropdown <Dropdown
menu={{ menu={{
items, items,
@ -203,6 +207,9 @@ function App() {
</a> </a>
</Dropdown> </Dropdown>
</Col> </Col>
<Col span={2}>
<Language />
</Col>
</Row> </Row>
</Header> </Header>
<Content <Content

@ -0,0 +1,27 @@
import React, { useState } from 'react';
import { Dropdown, Menu } from 'antd';
import { useTranslation } from 'react-i18next';
/**
* 语言选择组件
*/
const Language = () => {
const { t, i18n } = useTranslation();
const [selectedKeys, setSelectedKeys] = useState([i18n.language]);
//
const handleChangeLanguage = ({ key }) => {
setSelectedKeys([key]);
i18n.changeLanguage(key);
};
const langSupports = ['en', 'zh'].map((lang) => ({ label: t(`lang.${lang}`), key: lang }));
/* 🌏🌐 */
return (
<Dropdown menu={{ items: langSupports, onClick: handleChangeLanguage, style: { width: 100 }, selectedKeys: selectedKeys }}>
<div className='icon'>🌐<span style={{color: '#00b96b'}}>{t(`lang.${i18n.language}`)}</span></div>
</Dropdown>
);
};
export default Language;

@ -2,9 +2,10 @@ import { useNavigate, useLocation } from "react-router-dom";
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Button, Checkbox, Form, Input, Row, App } from 'antd'; import { Button, Checkbox, Form, Input, Row, App } from 'antd';
import { useStore } from '@/stores/StoreContext.js'; import { useStore } from '@/stores/StoreContext.js';
import { useTranslation } from 'react-i18next';
function Login() { function Login() {
const { t, i18n } = useTranslation();
const { authStore, noticeStore } = useStore(); const { authStore, noticeStore } = useStore();
const { notification } = App.useApp(); const { notification } = App.useApp();
@ -13,7 +14,7 @@ function Login() {
const [form] = Form.useForm(); const [form] = Form.useForm();
useEffect (() => { useEffect (() => {
if (location.search === '?out') { if (location.search === '?out') {
authStore.logout(); authStore.logout();
navigate('/login'); navigate('/login');
} }
@ -49,7 +50,7 @@ function Login() {
}); });
}); });
}; };
const onFinishFailed = (errorInfo) => { const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo); console.log('Failed:', errorInfo);
}; };
@ -78,7 +79,7 @@ function Login() {
autoComplete="off" autoComplete="off"
> >
<Form.Item <Form.Item
label="Username" label={t("form.Username")}
name="username" name="username"
rules={[ rules={[
{ {
@ -90,7 +91,7 @@ function Login() {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="Password" label={t("form.Password")}
name="password" name="password"
rules={[ rules={[
{ {
@ -108,7 +109,7 @@ function Login() {
}} }}
> >
<Button type="primary" htmlType="submit" style={{width: "100%"}}> <Button type="primary" htmlType="submit" style={{width: "100%"}}>
Login {t('common.Login')}
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>
@ -116,4 +117,4 @@ function Login() {
); );
} }
export default Login; export default Login;

@ -6,6 +6,7 @@ import { DownOutlined } from "@ant-design/icons";
import "antd/dist/reset.css"; import "antd/dist/reset.css";
import AppLogo from "@/assets/logo-gh.png"; import AppLogo from "@/assets/logo-gh.png";
import { useStore } from "@/stores/StoreContext.js"; import { useStore } from "@/stores/StoreContext.js";
import Language from "./Language";
const { Title } = Typography; const { Title } = Typography;
const { Header, Content, Footer } = Layout; const { Header, Content, Footer } = Layout;
@ -35,7 +36,10 @@ function Standlone() {
<Col span={4}> <Col span={4}>
<img src={AppLogo} className="logo" alt="App logo" /> <img src={AppLogo} className="logo" alt="App logo" />
</Col> </Col>
<Col span={20}><Title style={{ color: "white", marginTop: "3.5px" }}>Global Highlights Hub</Title></Col> <Col span={18}><Title style={{ color: "white", marginTop: "3.5px" }}>Global Highlights Hub</Title></Col>
<Col span={2}>
<Language />
</Col>
</Row> </Row>
</Header> </Header>
<Content <Content

@ -7,11 +7,13 @@ import {
FileOutlined FileOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useStore } from '@/stores/StoreContext.js'; import { useStore } from '@/stores/StoreContext.js';
import { useTranslation } from 'react-i18next';
const { Title, Paragraph } = Typography; const { Title, Paragraph } = Typography;
const { TextArea } = Input; const { TextArea } = Input;
function Detail() { function Detail() {
const { t } = useTranslation();
const confirmationListColumns = [ const confirmationListColumns = [
{ {
@ -19,19 +21,19 @@ function Detail() {
dataIndex: 'PCI_Changetext', dataIndex: 'PCI_Changetext',
}, },
{ {
title: 'Sending Date', title: t('group.ResSendingDate'),
dataIndex: 'PCI_SendDate', dataIndex: 'PCI_SendDate',
}, },
{ {
title: 'Confirmation Details', title: t('group.ConfirmationDetails'),
render: detailTextRender render: detailTextRender
}, },
{ {
title: 'Attachments', title: t('group.Attachments'),
render: attachmentRender render: attachmentRender
}, },
{ {
title: 'Confirmation Date', title: t('group.ConfirmationDate'),
dataIndex: 'PCI_ConfirmDate', dataIndex: 'PCI_ConfirmDate',
}, },
{ {
@ -48,7 +50,7 @@ function Detail() {
</div> </div>
); );
} }
function attachmentRender(text, confirm) { function attachmentRender(text, confirm) {
return ( return (
<> <>
@ -60,10 +62,10 @@ function Detail() {
</> </>
); );
} }
function confirmRender(text, confirm) { function confirmRender(text, confirm) {
return ( return (
<Button type="link" onClick={() => showConfirmModal(confirm)}>Confirm</Button> <Button type="link" onClick={() => showConfirmModal(confirm)}>{t('common.Confirm')}</Button>
); );
} }
@ -78,18 +80,18 @@ function Detail() {
const { authStore, reservationStore } = useStore(); const { authStore, reservationStore } = useStore();
const { reservationDetail, confirmationList } = reservationStore; const { reservationDetail, confirmationList } = reservationStore;
const { login } = authStore; const { login } = authStore;
const officeWebViewerUrl = const officeWebViewerUrl =
'https://view.officeapps.live.com/op/embed.aspx?wdPrint=1&wdHideGridlines=0&wdHideComments=1&wdEmbedCode=0&src='; 'https://view.officeapps.live.com/op/embed.aspx?wdPrint=1&wdHideGridlines=0&wdHideComments=1&wdEmbedCode=0&src=';
// https://www.chinahighlights.com/public/reservationW220420009.doc // https://www.chinahighlights.com/public/reservationW220420009.doc
const reservationUrl = const reservationUrl =
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${login.travelAgencyId}&token=${login.token}&FileType=1`; `https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${login.travelAgencyId}&token=${login.token}&FileType=1`;
const nameCardUrl = const nameCardUrl =
`https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${login.travelAgencyId}&token=${login.token}&FileType=2`; `https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${login.travelAgencyId}&token=${login.token}&FileType=2`;
const reservationPreviewUrl = officeWebViewerUrl + encodeURIComponent(reservationUrl); const reservationPreviewUrl = officeWebViewerUrl + encodeURIComponent(reservationUrl);
const nameCardPreviewUrl = officeWebViewerUrl + encodeURIComponent(nameCardUrl); const nameCardPreviewUrl = officeWebViewerUrl + encodeURIComponent(nameCardUrl);
const showConfirmModal = (confirm) => { const showConfirmModal = (confirm) => {
setIsModalOpen(true); setIsModalOpen(true);
const formattedText = confirm.PCI_ConfirmText;//.replace(/\;/g, "\n\n"); const formattedText = confirm.PCI_ConfirmText;//.replace(/\;/g, "\n\n");
setConfirmText(formattedText); setConfirmText(formattedText);
@ -153,31 +155,31 @@ function Detail() {
<Space direction="vertical" style={{ width: '100%' }}> <Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={{ md: 24 }}> <Row gutter={{ md: 24 }}>
<Col span={20}> <Col span={20}>
<Title level={4}>Reference Number: {reservationDetail.referenceNumber}; Arrival date: {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')}>Back</Button> <Button type="link" onClick={() => navigate('/reservation/newest?back')}>{t('common.Back')}</Button>
</Col> </Col>
</Row> </Row>
<Row gutter={{ md: 24 }}> <Row gutter={{ md: 24 }}>
<Col span={12} style={{height: '100%'}} > <Col span={12} style={{height: '100%'}} >
<iframe id="msdoc-iframe-reservation" title="msdoc-iframe-reservation" src={reservationPreviewUrl} frameBorder="0" style={{ width: '100%', height: '600px' }}></iframe> <iframe id="msdoc-iframe-reservation" title="msdoc-iframe-reservation" src={reservationPreviewUrl} frameBorder="0" style={{ width: '100%', height: '600px' }}></iframe>
<Button type='link' target='_blank' href={reservationUrl}>Download Itinerary</Button> <Button type='link' target='_blank' href={reservationUrl}>{t('common.Download')} Itinerary</Button>
</Col> </Col>
<Col span={12} style={{height: '100%'}} > <Col span={12} style={{height: '100%'}} >
<iframe id="msdoc-iframe-name-card" title="msdoc-iframe-name-card" src={nameCardPreviewUrl} frameBorder="0" style={{ width: '100%', height: '600px' }}></iframe> <iframe id="msdoc-iframe-name-card" title="msdoc-iframe-name-card" src={nameCardPreviewUrl} frameBorder="0" style={{ width: '100%', height: '600px' }}></iframe>
<Button type='link' target='_blank' href={nameCardUrl}>Download Name Card</Button> <Button type='link' target='_blank' href={nameCardUrl}>{t('common.Download')} Name Card</Button>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Col span={24}><Space direction="vertical" style={{ width: '100%' }}> <Col span={24}><Space direction="vertical" style={{ width: '100%' }}>
<Table <Table
bordered bordered
loading={dataLoading} loading={dataLoading}
pagination={false} pagination={false}
dataSource={confirmationList} columns={confirmationListColumns} dataSource={confirmationList} columns={confirmationListColumns}
/> />
</Space> </Space>
</Col> </Col>
</Row> </Row>
</Space> </Space>

@ -7,13 +7,17 @@ import dayjs from "dayjs";
import { useStore } from '@/stores/StoreContext.js'; import { useStore } from '@/stores/StoreContext.js';
import { DATE_PRESETS } from "@/config"; import { DATE_PRESETS } from "@/config";
import { formatDate, isEmpty } from "@/utils/commons"; import { formatDate, isEmpty } from "@/utils/commons";
import { useTranslation } from 'react-i18next';
import usePresets from '@/hooks/usePresets';
const { Title } = Typography; const { Title } = Typography;
function Newest() { function Newest() {
const { t } = useTranslation();
const presets = usePresets();
const reservationListColumns = [ const reservationListColumns = [
{ {
title: 'Reference number', title: t('group.RefNo'),
dataIndex: 'referenceNumber', dataIndex: 'referenceNumber',
key: 'Reference number', key: 'Reference number',
render: (text, record) => { render: (text, record) => {
@ -28,47 +32,47 @@ function Newest() {
}, },
}, },
{ {
title: 'Arrival date', title: t('group.ArrivalDate'),
dataIndex: 'arrivalDate', dataIndex: 'arrivalDate',
key: 'Arrival date', key: 'Arrival date',
render: (text, record) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')), render: (text, record) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')),
}, },
{ {
title: 'Pax', title: t('group.Pax'),
key: 'Pax', key: 'Pax',
dataIndex: 'pax' dataIndex: 'pax'
}, },
{ {
title: 'Status', title: t('group.Status'),
key: 'Status', key: 'Status',
dataIndex: 'status' dataIndex: 'status'
}, },
{ {
title: 'Res. sending date', title: t('group.ResSendingDate'),
key: 'Reservation date', key: 'Reservation date',
dataIndex: 'reservationDate', dataIndex: 'reservationDate',
render: (text, record) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')), render: (text, record) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')),
}, },
{ {
title: 'Guide', title: t('group.Guide'),
key: 'Guide', key: 'Guide',
dataIndex: 'guide', dataIndex: 'guide',
render: guideRender render: guideRender
}, },
]; ];
function guideRender(text, reservation) { function guideRender(text, reservation) {
if (reservation.guide === '') { if (reservation.guide === '') {
return ( return (
<Space size="middle"> <Space size="middle">
<Button type="link" onClick={() => showCityGuideModal(reservation)}>Add</Button> <Button type="link" onClick={() => showCityGuideModal(reservation)}>{t('common.Add')}</Button>
</Space> </Space>
); );
} else { } else {
return ( return (
<Space size="middle"> <Space size="middle">
<span>{reservation.guide}</span> <span>{reservation.guide}</span>
<Button type="link" onClick={() => showCityGuideModal(reservation)}>Edit</Button> <Button type="link" onClick={() => showCityGuideModal(reservation)}>{t('common.Edit')}</Button>
</Space> </Space>
); );
} }
@ -110,7 +114,7 @@ function Newest() {
useEffect (() => { useEffect (() => {
if (location.search !== '?back') { if (location.search !== '?back') {
// //
onSearchClick(1, 1); onSearchClick(1, 1);
} }
reservationStore.fetchAllGuideList() reservationStore.fetchAllGuideList()
@ -129,7 +133,7 @@ function Newest() {
}, []); }, []);
const showCityGuideModal = (reservation) => { const showCityGuideModal = (reservation) => {
setDataLoading(true); setDataLoading(true);
setIsModalOpen(true); setIsModalOpen(true);
reservationStore.editReservation(reservation); reservationStore.editReservation(reservation);
reservationStore.fetchCityList(reservation.reservationId) reservationStore.fetchCityList(reservation.reservationId)
@ -156,7 +160,7 @@ function Newest() {
setIsModalOpen(false); setIsModalOpen(false);
setDataLoading(false); setDataLoading(false);
}; };
// //
const onSearchClick = (current=1, status=null) => { const onSearchClick = (current=1, status=null) => {
setDataLoading(true); setDataLoading(true);
@ -189,12 +193,12 @@ function Newest() {
pagination={false} pagination={false}
columns={[ columns={[
{ {
title: 'City', title: t('group.City'),
dataIndex: 'cityName', dataIndex: 'cityName',
key: 'cityName' key: 'cityName'
}, },
{ {
title: 'Tour Guide', title: t('group.Guide'),
dataIndex: 'tourGuide', dataIndex: 'tourGuide',
key: 'tourGuide', key: 'tourGuide',
render: cityGuideRender, render: cityGuideRender,
@ -210,32 +214,32 @@ function Newest() {
<Title level={3}></Title> <Title level={3}></Title>
<Row gutter={16}> <Row gutter={16}>
<Col md={24} lg={6} xxl={4}> <Col md={24} lg={6} xxl={4}>
<Input placeholder="Reference Number" value={referenceNo} onChange={(e) => { reservationStore.updatePropertyValue('referenceNo', e.target.value)} } /> <Input placeholder={t("group.RefNo")} value={referenceNo} onChange={(e) => { reservationStore.updatePropertyValue('referenceNo', e.target.value)} } />
</Col> </Col>
<Col md={24} lg={8} xxl={6}> <Col md={24} lg={8} xxl={6}>
<Space direction="horizontal"> <Space direction="horizontal">
Arrival Date {t('group.ArrivalDate')}
<DatePicker.RangePicker <DatePicker.RangePicker
allowClear={true} allowClear={true}
inputReadOnly={true} inputReadOnly={true}
presets={DATE_PRESETS} presets={presets}
defaultValue={toJS(arrivalDateRange)} defaultValue={toJS(arrivalDateRange)}
placeholder={['From', 'Thru']} placeholder={['From', 'Thru']}
onChange={(dateRange) => { onChange={(dateRange) => {
reservationStore.updatePropertyValue('arrivalDateRange', dateRange == null ? [] : dateRange) reservationStore.updatePropertyValue('arrivalDateRange', dateRange == null ? [] : dateRange)
}} }}
/> />
</Space> </Space>
</Col> </Col>
<Col md={24} lg={4} xxl={4}> <Col md={24} lg={4} xxl={4}>
<Button type='primary' onClick={() => onSearchClick()} loading={dataLoading}>Search</Button> <Button type='primary' onClick={() => onSearchClick()} loading={dataLoading}>{t('common.Search')}</Button>
</Col> </Col>
</Row> </Row>
<Title level={3}></Title> <Title level={3}></Title>
<Row> <Row>
<Col span={24}> <Col span={24}>
<Table <Table
title={() => 'Reservations without the tour guide information will be highlighted in red if the arrival date is within 3 days.'} title={() => t('group.3DGuideTip')}
bordered bordered
loading={dataLoading} loading={dataLoading}
pagination={{ pagination={{
@ -255,4 +259,4 @@ function Newest() {
); );
} }
export default observer(Newest); export default observer(Newest);

Loading…
Cancel
Save