From 5388c8783adfcef2fe31db85fb144b6a559aabba Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 27 Jun 2024 17:33:19 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20report=20index=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC;=20=E4=BF=AE=E6=94=B9=E6=89=B9=E8=AF=84?= =?UTF-8?q?=E8=A1=A8;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/commons.js | 498 +++++++++++++++++++++++++++++++++++-- src/views/report/Index.jsx | 423 +++++++++++++------------------ 2 files changed, 647 insertions(+), 274 deletions(-) diff --git a/src/utils/commons.js b/src/utils/commons.js index 4b158f4..9b178ee 100644 --- a/src/utils/commons.js +++ b/src/utils/commons.js @@ -82,24 +82,24 @@ export function isNotEmpty(val) { return val !== undefined && val !== null && val !== ""; } -export function isEmpty(val) { - return val === undefined || val === null || val === ""; -} +// export function isEmpty(val) { +// return val === undefined || val === null || val === ""; +// } export function prepareUrl(url) { return new UrlBuilder(url); } -export function debounce(fn, delay = 500) { - let timer; - return e => { - e.persist(); - clearTimeout(timer); - timer = setTimeout(() => { - fn(e); - }, delay); - }; -} +// export function debounce(fn, delay = 500) { +// let timer; +// return e => { +// e.persist(); +// clearTimeout(timer); +// timer = setTimeout(() => { +// fn(e); +// }, delay); +// }; +// } export function throttle(fn, delay, atleast) { let timeout = null, @@ -131,7 +131,92 @@ export function escape2Html(str) { return output; } -export function groupBy(array, callback) { +export function formatPrice(price) { + return Math.ceil(price).toLocaleString(); +} + +export function formatPercent(number) { + return Math.round(number * 100) + "%"; +} + + +/** + * ! 不支持计算 Set 或 Map + * @param {*} val + * @example + * true if: 0, [], {}, null, '', undefined + * false if: 'false', 'undefined' + */ +export function isEmpty(val) { + // return val === undefined || val === null || val === ""; + return [Object, Array].includes((val || {}).constructor) && !Object.entries(val || {}).length; +} +/** + * 数组排序 + */ +export const sortBy = (key) => { + return (a, b) => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0); +}; + +/** + * Object排序keys + */ +export const sortKeys = (obj) => + Object.keys(obj) + .sort() + .reduce((a, k2) => ({ ...a, [k2]: obj[k2] }), {}); + +/** + * 数组排序, 给定排序数组 + * @param {array} items 需要排序的数组 + * @param {array} keyName 排序的key + * @param {array} keyOrder 给定排序 + * @returns + */ +export const sortArrayByOrder = (items, keyName, keyOrder) => { + return items.sort((a, b) => { + return keyOrder.indexOf(a[keyName]) - keyOrder.indexOf(b[keyName]); + }); +}; +/** + * 合并Object, 递归地 + */ +export function merge(...objects) { + const isDeep = objects.some((obj) => obj !== null && typeof obj === 'object'); + + const result = objects[0] || (isDeep ? {} : objects[0]); + + for (let i = 1; i < objects.length; i++) { + const obj = objects[i]; + + if (!obj) continue; + + Object.keys(obj).forEach((key) => { + const val = obj[key]; + + if (isDeep) { + if (Array.isArray(val)) { + result[key] = [].concat(Array.isArray(result[key]) ? result[key] : [result[key]], val); + } else if (typeof val === 'object') { + result[key] = merge(result[key], val); + } else { + result[key] = val; + } + } else { + result[key] = typeof val === 'boolean' ? val : result[key]; + } + }); + } + + return result; +} + +/** + * 数组分组 + * - 相当于 lodash 的 _.groupBy + * @see https://www.lodashjs.com/docs/lodash.groupBy#_groupbycollection-iteratee_identity + */ +export function groupBy(array = [], callback) { return array.reduce((groups, item) => { const key = typeof callback === 'function' ? callback(item) : item[callback]; @@ -144,11 +229,386 @@ export function groupBy(array, callback) { }, {}); } +/** + * 创建一个从 object 中选中的属性的对象。 + * @param {*} object + * @param {array} keys + */ +export function pick(object, keys) { + return keys.reduce((obj, key) => { + if (object && Object.prototype.hasOwnProperty.call(object, key)) { + obj[key] = object[key]; + } + return obj; + }, {}); +} -export function formatPrice(price) { - return Math.ceil(price).toLocaleString(); +/** + * 返回对象的副本,经过筛选以省略指定的键。 + * @param {*} object + * @param {string[]} keysToOmit + * @returns + */ +export function omit(object, keysToOmit) { + return Object.fromEntries(Object.entries(object).filter(([key]) => !keysToOmit.includes(key))); } -export function formatPercent(number) { - return Math.round(number * 100) + "%"; -} \ No newline at end of file +/** + * 深拷贝 + */ +export function cloneDeep(value) { + // return structuredClone(value); + if (typeof value !== 'object' || value === null) { + return value; + } + + const result = Array.isArray(value) ? [] : {}; + + for (const key in value) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + result[key] = cloneDeep(value[key]); + } + } + + return result; +} + +/** + * 向零四舍五入, 固定精度设置 + */ +function curriedFix(precision = 0) { + return function (number) { + // Shift number by precision places + const shift = Math.pow(10, precision); + const shiftedNumber = number * shift; + + // Round to nearest integer + const roundedNumber = Math.round(shiftedNumber); + + // Shift back decimal place + return roundedNumber / shift; + }; +} +/** + * 向零四舍五入, 保留2位小数 + */ +export const fixTo2Decimals = curriedFix(2); +/** + * 向零四舍五入, 保留4位小数 + */ +export const fixTo4Decimals = curriedFix(4); + +export const fixTo1Decimals = curriedFix(1); +export const fixToInt = curriedFix(0); + +/** + * 映射 + * @example + * const keyMap = { + a: [{key: 'a1'}, {key: 'a2', transform: v => v * 2}], + b: {key: 'b1'} + }; + const result = objectMapper({a: 1, b: 3}, keyMap); + // result = {a1: 1, a2: 2, b1: 3} + * + */ +export function objectMapper(input, keyMap) { + // Loop through array mapping + if (Array.isArray(input)) { + return input.map((obj) => objectMapper(obj, keyMap)); + } + + if (typeof input === 'object') { + const mappedObj = {}; + + Object.keys(input).forEach((key) => { + // Keep original keys not in keyMap + if (!keyMap[key]) { + mappedObj[key] = input[key]; + } + // Handle array of maps + if (Array.isArray(keyMap[key])) { + keyMap[key].forEach((map) => { + let value = input[key]; + if (map.transform) value = map.transform(value); + mappedObj[map.key] = value; + }); + + // Handle single map + } else { + const map = keyMap[key]; + if (map) { + let value = input[key]; + if (map.transform) value = map.transform(value); + mappedObj[map.key || key] = value; + } + } + }); + + return mappedObj; + } + + return input; +} + +/** + * 创建一个对应于对象路径的值数组 + */ +export function at(obj, path) { + let result; + if (Array.isArray(obj)) { + // array case + const indexes = path.split('.').map((i) => parseInt(i)); + result = []; + for (let i = 0; i < indexes.length; i++) { + result.push(obj[indexes[i]]); + } + } else { + // object case + const indexes = path.split('.').map((i) => i); + result = [obj]; + for (let i = 0; i < indexes.length; i++) { + result = [result[0][indexes[i]]]; + } + } + return result; +} +/** + * 删除 null/undefined + */ +export function flush(collection) { + let result, len, i; + if (!collection) { + return undefined; + } + if (Array.isArray(collection)) { + result = []; + len = collection.length; + for (i = 0; i < len; i++) { + const elem = collection[i]; + if (elem != null) { + result.push(elem); + } + } + return result; + } + if (typeof collection === 'object') { + result = {}; + const keys = Object.keys(collection); + len = keys.length; + for (i = 0; i < len; i++) { + const key = keys[i]; + const value = collection[key]; + if (value != null) { + result[key] = value; + } + } + return result; + } + return undefined; +} + +/** + * 千分位 格式化数字 + */ +export const numberFormatter = (number) => { + return new Intl.NumberFormat().format(number); +}; + +/** + * @example + * const obj = { a: { b: 'c' } }; + * const keyArr = ['a', 'b']; + * getNestedValue(obj, keyArr); // Returns: 'c' + */ +export const getNestedValue = (obj, keyArr) => { + return keyArr.reduce((acc, curr) => { + return acc && Object.prototype.hasOwnProperty.call(acc, curr) ? acc[curr] : undefined; + // return acc && acc[curr]; + }, obj); +}; + +/** + * 计算笛卡尔积 + */ +export const cartesianProductArray = (arr, sep = '_', index = 0, prefix = '') => { + let result = []; + if (index === arr.length) { + return [prefix]; + } + arr[index].forEach((item) => { + result = result.concat(cartesianProductArray(arr, sep, index + 1, prefix ? `${prefix}${sep}${item}` : `${item}`)); + }); + return result; +}; + +export const stringToColour = (str) => { + var hash = 0 + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash) + } + var colour = '#' + for (let i = 0; i < 3; i++) { + var value = (hash >> (i * 8)) & 0xff + value = (value % 150) + 50 + colour += ('00' + value.toString(16)).substr(-2) + } + return colour +} + +export const debounce = (func, wait, immediate) => { + var timeout; + return function () { + var context = this, + args = arguments; + clearTimeout(timeout); + if (immediate && !timeout) func.apply(context, args); + timeout = setTimeout(function () { + timeout = null; + if (!immediate) func.apply(context, args); + }, wait); + }; +} + +export const removeFormattingChars = (str) => { + const regex = /[\r\n\t\v\f]/g; + str = str.replace(regex, ' '); + // Replace more than four consecutive spaces with a single space + str = str.replace(/\s{4,}/g, ' '); + return str; +} + +export const olog = (text, ...args) => { + console.log( + `%c ${text} `, + 'background:#fb923c ; padding: 1px; border-radius: 3px; color: #fff',...args + ); +}; + +export const sanitizeFilename = (str) => { + // Remove whitespace and replace with hyphens + str = str.replace(/\s+/g, '-'); + // Remove invalid characters and replace with hyphens + str = str.replace(/[^a-zA-Z0-9.-]/g, '-'); + // Replace consecutive hyphens with a single hyphen + str = str.replace(/-+/g, '-'); + // Trim leading and trailing hyphens + str = str.replace(/^-+|-+$/g, ''); + return str; +} + +export const formatBytes = (bytes, decimals = 2) => { + if (bytes === 0) return ''; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +}; +export const calcCacheSizes = async () => { + try { + let swCacheSize = 0; + let diskCacheSize = 0; + let indexedDBSize = 0; + + // 1. Get the service worker cache size + if ('caches' in window) { + const cacheNames = await caches.keys(); + for (const name of cacheNames) { + const cache = await caches.open(name); + const requests = await cache.keys(); + for (const request of requests) { + const response = await cache.match(request); + swCacheSize += Number(response.headers.get('Content-Length')) || 0; + } + } + } + + // 2. Get the disk cache size + // const diskCacheName = 'disk-cache'; + // const diskCache = await caches.open(diskCacheName); + // const diskCacheKeys = await diskCache.keys(); + // for (const request of diskCacheKeys) { + // const response = await diskCache.match(request); + // diskCacheSize += Number(response.headers.get('Content-Length')) || 0; + // } + + // 3. Get the IndexedDB cache size + // const indexedDBNames = await window.indexedDB.databases(); + // for (const dbName of indexedDBNames) { + // const db = await window.indexedDB.open(dbName.name); + // const objectStoreNames = db.objectStoreNames; + + // if (objectStoreNames !== undefined) { + // const objectStores = Array.from(objectStoreNames).map((storeName) => db.transaction([storeName], 'readonly').objectStore(storeName)); + + // for (const objectStore of objectStores) { + // const request = objectStore.count(); + // request.onsuccess = () => { + // indexedDBSize += request.result; + // }; + // } + // } + // } + + return { swCacheSize, diskCacheSize, indexedDBSize, totalSize: Number(swCacheSize) + Number(diskCacheSize) + indexedDBSize }; + } catch (error) { + console.error('Error getting cache sizes:', error); + } +}; + +export const clearAllCaches = async (cb) => { + try { + // 1. Clear the service worker cache + if ('caches' in window) { + // if (navigator.serviceWorker) { + const cacheNames = await caches.keys(); + await Promise.all(cacheNames.map((name) => caches.delete(name))); + } + + // 2. Clear the disk cache (HTTP cache) + // const diskCacheName = 'disk-cache'; + // await window.caches.delete(diskCacheName); + // const diskCache = await window.caches.open(diskCacheName); + // const diskCacheKeys = await diskCache.keys(); + // await Promise.all(diskCacheKeys.map((request) => diskCache.delete(request))); + + // 3. Clear the IndexedDB cache + const indexedDBNames = await window.indexedDB.databases(); + await Promise.all(indexedDBNames.map((dbName) => window.indexedDB.deleteDatabase(dbName.name))); + + // Unregister the service worker + const registration = await navigator.serviceWorker.getRegistration(); + if (registration) { + await registration.unregister(); + console.log('Service worker unregistered'); + } else { + console.log('No service worker registered'); + } + if (typeof cb === 'function' ) { + cb(); + } + + } catch (error) { + console.error('Error clearing caches or unregistering service worker:', error); + } +}; + +export const loadScript = (src) => { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.onload = resolve; + script.onerror = reject; + script.crossOrigin = 'anonymous'; + script.src = src; + if (document.head.append) { + document.head.append(script); + } else { + document.getElementsByTagName('head')[0].appendChild(script); + } + }); +}; + diff --git a/src/views/report/Index.jsx b/src/views/report/Index.jsx index 2349627..1205bfc 100644 --- a/src/views/report/Index.jsx +++ b/src/views/report/Index.jsx @@ -12,12 +12,151 @@ function Index() { const evaluationScores = vendorScoresData.EvaluationScores ? vendorScoresData.EvaluationScores[0] : []; useEffect(() => {}, []); - const columns_month = [ - { - title: "Date", - dataIndex: "VMonth", - key: "VMonth", - }, + const primaryData = vendorScoresData.EvaluationScores + ? [comm.pick(vendorScoresData.EvaluationScores[0], ['Groups', 'PersonNum', 'AmountUSD', 'EvaluationScore', 'TPReviews', 'TPReviewRate', 'Complaints', 'ComplaintRate'])] + : []; + + 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’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 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.TPReviewRatingText, + }, + { + 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 }, + ]; + + 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, + },]; + + const columns_primary = [ { title: "Groups", dataIndex: "Groups", @@ -63,6 +202,8 @@ function Index() { }, ]; + const columns_month = [{ title: 'Date', dataIndex: 'VMonth', key: 'VMonth' }, ...columns_primary]; + const columns_guide = [ { title: "Name", @@ -120,6 +261,18 @@ function Index() { key: "ECI_Content", }, ]; + const columns_commend_criticizescores = [ + { + title: "Reference Number", + dataIndex: "GRI_No", + key: "GRI_No", + }, + { + title: "Essential Comments", + dataIndex: "ECI_Content", + key: "ECI_Content", + }, + ]; const { toPDF, targetRef } = usePDF({ method: "save", @@ -182,40 +335,7 @@ function Index() { Primary Data -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
GroupsNumber of PeopleTransaction Amount(USD)Evaluation ScoreTP ReviewsTP Reviews RateComplaintsComplaint Rate
{evaluationScores.Groups}{evaluationScores.PersonNum}{comm.formatPrice(evaluationScores.AmountUSD)}{evaluationScores.EvaluationScore}{evaluationScores.TPReviews}{comm.formatPercent(evaluationScores.TPReviewRate)}{evaluationScores.Complaints}{comm.formatPercent(evaluationScores.ComplaintRate)}
-
-
-
-
+ @@ -229,172 +349,12 @@ function Index() { DMC Assessment Criteria -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Customer Satisfaction3 scores4 scores5 scoresYour ScoresFinal ScoresNote
TP review rating\30%60%{evaluationScores.TPReviewRating}{evaluationScores.AvgCusSatisfaction}{evaluationScores.TPReviewRatingText}
Post tour complaints100{evaluationScores.PostTourComplaints}{evaluationScores.PostTourComplaintsText}
Complaints resolved during the tour3{evaluationScores.ComplaintsDuringTour}{evaluationScores.ComplaintsDuringTourText}
Customer photos\30%50%{evaluationScores.CustomerPhotoRate}{evaluationScores.CustomerPhotoRateText}
Evaluation scores4.5{evaluationScores.EvaluationFormScore}{evaluationScores.EvaluationFormScoreText}
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Operator Support & Local resources3 scores4 scores5 scoresYour ScoresFinal ScoresNote
Response efficiency1d6hrs3hrs{evaluationScores.ResponseEfficiency}{evaluationScores.AvgLocalResources}{evaluationScores.ResponseEfficiencyText}
Provide suggestions and alternatives\{evaluationScores.ProvideSuggestions}{evaluationScores.ProvideSuggestionsText}
Provide local tourism information\{evaluationScores.ProvideLocalInfo}{evaluationScores.ProvideLocalInfoText}
Assist in developing exclusive products\\{evaluationScores.ExclusiveProducts}{evaluationScores.ExclusiveProductsText}
Dedicated tour guide team for AH\{evaluationScores.DedicatedTourGuide}{evaluationScores.DedicatedTourGuideText}
Partner hotels with contracted rate\{evaluationScores.PartnerHotels}{evaluationScores.PartnerHotelsText}
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pricing & Settlement (20%)3 scores4 scores5 scoresYour ScoresFinal ScoresNote
QuotationPackageDay toursIndividual services{evaluationScores.Quotation}{evaluationScores.AvgPricingAndSettlement}{evaluationScores.QuotationText}
SettlementPrepaymentMonthly PrepaymentMonthly settlement after the tours{evaluationScores.Settlement}{evaluationScores.SettlementText}
Cancellation policy30 days21 days1 day{evaluationScores.CancellationPolicy}{evaluationScores.CancellationPolicyText}
- - - - + +
+
+
+
+ Final Scores: {evaluationScores.FinalScores} @@ -426,55 +386,8 @@ function Index() { Evaluation Scores -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - +
CategoryItemYour ScoresNote
DMC ServicesGuide{evaluationScores.FRTGuide}{evaluationScores.FRTText}
Driver & Vehicle{evaluationScores.FRTGriver}
Food Arrangement{evaluationScores.FRTMeal}
Activity{evaluationScores.FRTProduct}
- - - - - - - - - - -
Itinerary ArrangementsHotel{evaluationScores.FRTHotel}
Travel Advisor's Planning{evaluationScores.FRTAdvisor}
- - - - @@ -495,7 +408,7 @@ function Index() { Suggestions from Customers - +