573 lines
16 KiB
TypeScript
573 lines
16 KiB
TypeScript
import {
|
|
Dialog,
|
|
QSelect,
|
|
Notify,
|
|
QNotifyCreateOptions,
|
|
useQuasar,
|
|
} from 'quasar';
|
|
import { Ref, ref } from 'vue';
|
|
import axios, { AxiosInstance, AxiosProgressEvent } from 'axios';
|
|
import { ComposerTranslation } from 'vue-i18n';
|
|
|
|
import arr from 'src/utils/arr';
|
|
import { getRole } from 'src/services/keycloak';
|
|
|
|
import GlobalDialog from 'components/GlobalDialog.vue';
|
|
import DialogDuplicateData from 'components/DialogDuplicateData.vue';
|
|
import useOptionStore from '../options';
|
|
import { CustomerBranch } from '../customer/types';
|
|
import { CustomerBranchRelation } from '../quotations';
|
|
|
|
export const baseUrl = import.meta.env.VITE_API_BASE_URL;
|
|
|
|
export function dialog(opts: {
|
|
title: string;
|
|
message: string;
|
|
color?: string;
|
|
icon?: string;
|
|
persistent?: boolean;
|
|
actionText?: string;
|
|
cancelText?: string;
|
|
enablei18n?: boolean;
|
|
action?: (...args: unknown[]) => unknown;
|
|
cancel?: (...args: unknown[]) => unknown;
|
|
}) {
|
|
Dialog.create({
|
|
component: GlobalDialog,
|
|
componentProps: opts,
|
|
});
|
|
}
|
|
|
|
export function dialogCheckData(opts: {
|
|
checkData: (...args: unknown[]) => {
|
|
oldData: { nameField: string; value: string }[];
|
|
newData: { nameField: string; value: string }[];
|
|
};
|
|
action?: (...args: unknown[]) => unknown;
|
|
cancel?: (...args: unknown[]) => unknown;
|
|
}) {
|
|
Dialog.create({
|
|
component: DialogDuplicateData,
|
|
componentProps: opts,
|
|
});
|
|
}
|
|
|
|
export function dialogWarningClose(
|
|
t: ComposerTranslation,
|
|
opts: {
|
|
message?: string;
|
|
actionText?: string;
|
|
action?: (...args: unknown[]) => unknown;
|
|
cancel?: (...args: unknown[]) => unknown;
|
|
},
|
|
) {
|
|
dialog({
|
|
color: 'warning',
|
|
icon: 'mdi-alert',
|
|
title: t('form.warning.title'),
|
|
actionText: opts.actionText || t('dialog.action.close'),
|
|
message: opts.message || t('dialog.message.warningClose'),
|
|
action: async () => {
|
|
if (opts.action) opts.action();
|
|
},
|
|
cancel: () => {
|
|
if (opts.cancel) opts.cancel();
|
|
},
|
|
});
|
|
}
|
|
|
|
export function notify(
|
|
type: 'create',
|
|
message?: string,
|
|
opts?: QNotifyCreateOptions | string,
|
|
) {
|
|
if (type === 'create') {
|
|
Notify.create({
|
|
icon: 'mdi-check-circle',
|
|
iconColor: 'white',
|
|
iconSize: '42px',
|
|
color: 'positive',
|
|
message: message,
|
|
position: 'bottom',
|
|
timeout: 1000,
|
|
group: false,
|
|
actions: [
|
|
{
|
|
icon: 'mdi-close',
|
|
color: 'white',
|
|
round: true,
|
|
handler: () => {},
|
|
},
|
|
],
|
|
});
|
|
}
|
|
if (opts) {
|
|
Notify.create(opts);
|
|
}
|
|
}
|
|
|
|
export function moveItemUp<T extends unknown[]>(items: T, index: number) {
|
|
arr.move(items, index, -1);
|
|
}
|
|
|
|
export function moveItemDown<T extends unknown[]>(items: T, index: number) {
|
|
arr.move(items, index, 1);
|
|
}
|
|
|
|
export function deleteItem(items: unknown[], index: number) {
|
|
if (!items) return;
|
|
if (index >= 0 && index < items.length) {
|
|
items.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
export function formatNumberDecimal(num: number, point: number = 2): string {
|
|
return num.toLocaleString('eng', {
|
|
minimumFractionDigits: point,
|
|
maximumFractionDigits: point,
|
|
});
|
|
}
|
|
|
|
export function selectFilterOptionRefMod(
|
|
source: Ref<Record<string, unknown>[]>,
|
|
destination: Ref<Record<string, unknown>[]>,
|
|
filterField?: string,
|
|
) {
|
|
destination.value = source.value;
|
|
|
|
const filter = ((value, update) => {
|
|
if (value === '') update(() => (destination.value = source.value));
|
|
else
|
|
update(
|
|
() => {
|
|
destination.value = source.value.filter((v) => {
|
|
const label = v[filterField || 'label'];
|
|
|
|
if (!label) return;
|
|
if (typeof label !== 'string') {
|
|
throw new Error('Label must be of type string.');
|
|
}
|
|
|
|
return (
|
|
label.toLocaleLowerCase().indexOf(value.toLocaleLowerCase()) > -1
|
|
);
|
|
});
|
|
},
|
|
(ref) => {
|
|
if (value !== '' && destination.value.length > 0) {
|
|
ref.setOptionIndex(-1);
|
|
ref.moveOptionSelection(1, true);
|
|
}
|
|
},
|
|
);
|
|
}) satisfies QSelect['onFilter'];
|
|
|
|
return filter;
|
|
}
|
|
|
|
export function selectOptionFilter(
|
|
list: Record<string, unknown>[],
|
|
filterField?: string,
|
|
prepare?: (
|
|
...args: unknown[]
|
|
) => Record<string, unknown>[] | Promise<Record<string, unknown>[]>,
|
|
) {
|
|
const options = ref<typeof list>([]);
|
|
|
|
(async () => {
|
|
const data = await prepare?.();
|
|
if (data) options.value = list = data;
|
|
})();
|
|
|
|
const filter = ((value, update) => {
|
|
if (value === '') update(() => (options.value = list));
|
|
else
|
|
update(() => {
|
|
options.value = list.filter((v) => {
|
|
const label = v[filterField || 'label'];
|
|
|
|
if (typeof label !== 'string') {
|
|
throw new Error('Label must be of type string.');
|
|
}
|
|
|
|
return (
|
|
label.toLocaleLowerCase().indexOf(value.toLocaleLowerCase()) > -1
|
|
);
|
|
});
|
|
});
|
|
}) satisfies QSelect['onFilter'];
|
|
|
|
return { options, filter };
|
|
}
|
|
|
|
export function checkTabBeforeAdd(data: unknown[], except?: string[]) {
|
|
if (!data) return;
|
|
const lastData = data[data.length - 1];
|
|
|
|
if (typeof lastData !== 'object') return false;
|
|
|
|
let canAdd = false;
|
|
for (const [key, val] of Object.entries(
|
|
lastData as Record<string, unknown>,
|
|
)) {
|
|
if (except?.includes(key)) continue;
|
|
canAdd = val !== '' && val !== null;
|
|
if (canAdd) break;
|
|
}
|
|
return canAdd;
|
|
}
|
|
|
|
export function isRoleInclude(role2check: string[]): boolean {
|
|
const roles = getRole() ?? [];
|
|
const isIncluded = role2check.some((r) => roles.includes(r));
|
|
|
|
return isIncluded;
|
|
}
|
|
|
|
export function resetScrollBar(elementId: string) {
|
|
const element = document.getElementById(elementId);
|
|
|
|
if (element) {
|
|
element.scrollTop = 0;
|
|
}
|
|
}
|
|
|
|
export function manageAttachment(
|
|
api: AxiosInstance,
|
|
base: string,
|
|
option?: { onUploadProgress?: (e: AxiosProgressEvent) => void },
|
|
) {
|
|
return {
|
|
listAttachment: async (opts: { parentId: string }) => {
|
|
const res = await api.get<string[]>(
|
|
`/${base}/${opts.parentId}/attachment`,
|
|
);
|
|
if (res.status >= 400) return false;
|
|
return res.data;
|
|
},
|
|
headAttachment: async (opts: { parentId: string; fileId: string }) => {
|
|
const url = `/${base}/${opts.parentId}/attachment/${opts.fileId}`;
|
|
const res = await api.head(url);
|
|
if (res.status < 400) return res.headers;
|
|
return false;
|
|
},
|
|
getAttachment: async (opts: {
|
|
parentId: string;
|
|
name: string;
|
|
download?: boolean;
|
|
}) => {
|
|
const url = `/${base}/${opts.parentId}/attachment/${opts.name}`;
|
|
const res = await api.get<string>(url);
|
|
if (opts.download) {
|
|
fetch(res.data)
|
|
.then(async (res) => await res.blob())
|
|
.then((blob) => {
|
|
const a = document.createElement('a');
|
|
a.download = opts.name;
|
|
a.href = window.URL.createObjectURL(blob);
|
|
a.click();
|
|
a.remove();
|
|
});
|
|
}
|
|
return res.data;
|
|
},
|
|
putAttachment: async (opts: {
|
|
parentId: string;
|
|
name: string;
|
|
file: File;
|
|
onUploadProgress?: (e: AxiosProgressEvent) => void;
|
|
abortController?: AbortController;
|
|
}) => {
|
|
const urlRes = await api.put(
|
|
`/${base}/${opts.parentId}/attachment/${opts.name}`,
|
|
);
|
|
|
|
if (urlRes.status >= 400) return false;
|
|
|
|
// NOTE: Must use axios instance or else CORS error.
|
|
const uploadRes = await axios.put(urlRes.data, opts.file, {
|
|
headers: { 'Content-Type': opts.file.type },
|
|
onUploadProgress: opts.onUploadProgress
|
|
? opts.onUploadProgress
|
|
: option?.onUploadProgress
|
|
? option.onUploadProgress
|
|
: (e) => console.log(e),
|
|
signal: opts.abortController?.signal,
|
|
});
|
|
|
|
if (uploadRes.status >= 400) return false;
|
|
|
|
return true;
|
|
},
|
|
delAttachment: async (opts: { parentId: string; name: string }) => {
|
|
const res = await api.delete(
|
|
`/${base}/${opts.parentId}/attachment/${opts.name}`,
|
|
);
|
|
if (res.status < 400) return true;
|
|
return false;
|
|
},
|
|
};
|
|
}
|
|
|
|
export function manageFile<T extends string>(
|
|
api: AxiosInstance,
|
|
base: string,
|
|
option?: { onUploadProgress?: (e: AxiosProgressEvent) => void },
|
|
) {
|
|
return {
|
|
listFile: async (opts: { group: T; parentId: string }) => {
|
|
const res = await api.get<string[]>(
|
|
`/${base}/${opts.parentId}/file-${opts.group}`,
|
|
);
|
|
if (res.status >= 400) return false;
|
|
return res.data;
|
|
},
|
|
headFile: async (opts: { group: T; parentId: string; fileId: string }) => {
|
|
const url = `/${base}/${opts.parentId}/file-${opts.group}/${opts.fileId}`;
|
|
const res = await api.head(url);
|
|
if (res.status < 400) return res.headers;
|
|
return false;
|
|
},
|
|
getFile: async (opts: {
|
|
group: T;
|
|
parentId: string;
|
|
fileId: string;
|
|
download?: boolean;
|
|
}) => {
|
|
const url = `/${base}/${opts.parentId}/file-${opts.group}/${opts.fileId}`;
|
|
const res = await api.get<string>(url);
|
|
if (opts.download) {
|
|
fetch(res.data)
|
|
.then(async (res) => await res.blob())
|
|
.then((blob) => {
|
|
const a = document.createElement('a');
|
|
a.download = opts.fileId;
|
|
a.href = window.URL.createObjectURL(blob);
|
|
a.click();
|
|
a.remove();
|
|
});
|
|
}
|
|
return res.data;
|
|
},
|
|
putFile: async (opts: {
|
|
group: T;
|
|
parentId: string;
|
|
fileId: string;
|
|
file: File;
|
|
onUploadProgress?: (e: AxiosProgressEvent) => void;
|
|
abortController?: AbortController;
|
|
}) => {
|
|
const res = await api.put(
|
|
`/${base}/${opts.parentId}/file-${opts.group}/${opts.fileId}`,
|
|
opts.file,
|
|
{
|
|
headers: { 'Content-Type': opts.file.type },
|
|
onUploadProgress: opts.onUploadProgress
|
|
? opts.onUploadProgress
|
|
: option?.onUploadProgress
|
|
? option.onUploadProgress
|
|
: (e) => console.log(e),
|
|
signal: opts.abortController?.signal,
|
|
},
|
|
);
|
|
if (res.status < 400) return true;
|
|
return false;
|
|
},
|
|
delFile: async (opts: { group: T; parentId: string; fileId: string }) => {
|
|
const res = await api.delete(
|
|
`/${base}/${opts.parentId}/file-${opts.group}/${opts.fileId}`,
|
|
);
|
|
if (res.status < 400) return true;
|
|
return false;
|
|
},
|
|
};
|
|
}
|
|
|
|
export function manageMeta<T extends string>(api: AxiosInstance, base: string) {
|
|
return {
|
|
postMeta: async (opts: {
|
|
group: T;
|
|
parentId: string;
|
|
meta: any;
|
|
file?: File;
|
|
}) => {
|
|
const url = `${base}/${opts.parentId}/${opts.group}`;
|
|
const res = await api.post<typeof opts.meta>(url, opts.meta);
|
|
|
|
if (res.status < 400) {
|
|
if (opts.file !== undefined) {
|
|
await manageFile(api, base).putFile({
|
|
parentId: opts.parentId,
|
|
group: opts.group,
|
|
fileId: res.data.id,
|
|
file: opts.file,
|
|
});
|
|
}
|
|
return res.data;
|
|
}
|
|
},
|
|
getMetaList: async (opts: { group: T; parentId: string }) => {
|
|
const url = `${base}/${opts.parentId}/${opts.group}`;
|
|
const res = await api.get(url);
|
|
return res.data;
|
|
},
|
|
getMeta: async (opts: { group: T; parentId: string; metaId: string }) => {
|
|
const url = `${base}/${opts.parentId}/${opts.group}/${opts.metaId}`;
|
|
const res = await api.get<string>(url);
|
|
return res.data;
|
|
},
|
|
putMeta: async (opts: {
|
|
group: T;
|
|
parentId: string;
|
|
metaId: string;
|
|
meta: any;
|
|
file?: File;
|
|
}) => {
|
|
const res = await api.put<typeof opts.meta>(
|
|
`${base}/${opts.parentId}/${opts.group}/${opts.metaId}`,
|
|
opts.meta,
|
|
);
|
|
if (res.status < 400) {
|
|
if (opts.file !== undefined)
|
|
await manageFile(api, base).putFile({
|
|
parentId: opts.parentId,
|
|
group: opts.group,
|
|
fileId: res.data.id,
|
|
file: opts.file,
|
|
});
|
|
return res.data;
|
|
}
|
|
return false;
|
|
},
|
|
delMeta: async (opts: { group: T; parentId: string; metaId: string }) => {
|
|
const res = await api.delete(
|
|
`${base}/${opts.parentId}/${opts.group}/${opts.metaId}`,
|
|
);
|
|
if (res.status < 400) return true;
|
|
return false;
|
|
},
|
|
};
|
|
}
|
|
export async function waitAll<T extends Promise<any>[]>(arr: T) {
|
|
return await Promise.all(arr);
|
|
}
|
|
|
|
export function commaInput(
|
|
text: string,
|
|
type: 'string' | 'number' = 'number',
|
|
): string {
|
|
if (typeof text !== 'string') return '0';
|
|
if (!text) return '0';
|
|
|
|
const num = Number(text.replace(/,/gi, ''));
|
|
|
|
const parts = num.toString().split('.');
|
|
const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
const decimalPart = parts[1]?.slice(0, 2) || (type === 'number' && '00');
|
|
|
|
if (integerPart === 'NaN') return '0';
|
|
return integerPart + (decimalPart ? `.${decimalPart}` : '');
|
|
}
|
|
|
|
export function convertFileSize(size: number): string {
|
|
if (size === undefined) return 'Unknow size';
|
|
|
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
|
|
let i = 0;
|
|
let sizeNumber = typeof size === 'string' ? parseFloat(size) : size;
|
|
while (sizeNumber >= 1024 && i++ < units.length - 1) {
|
|
sizeNumber /= 1024;
|
|
}
|
|
return sizeNumber.toFixed(2) + ' ' + units[i];
|
|
}
|
|
|
|
export async function getAttachmentHead(api: AxiosInstance, url: string) {
|
|
const res = await api.head<string>(`${url}`);
|
|
if (res) return res.headers;
|
|
}
|
|
|
|
export function calculateDaysUntilExpire(expireDate: Date): number {
|
|
const today = new Date();
|
|
const expire = new Date(expireDate);
|
|
const diffInTime = expire.getTime() - today.getTime();
|
|
const diffInDays = Math.ceil(diffInTime / (1000 * 60 * 60 * 24));
|
|
|
|
return diffInDays;
|
|
}
|
|
|
|
export function formatNumberWithCommas(value: string) {
|
|
const [integer, decimal] = value.split('.');
|
|
const integerWithCommas = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
return decimal ? `${integerWithCommas}.${decimal}` : integerWithCommas;
|
|
}
|
|
|
|
export function createDataRefBase<T>(
|
|
defaultPage = 1,
|
|
defaultPageMax = 1,
|
|
defaultPageSize = 30,
|
|
) {
|
|
return {
|
|
data: ref<T[]>(),
|
|
page: ref<number>(defaultPage),
|
|
pageMax: ref<number>(defaultPageMax),
|
|
pageSize: ref<number>(defaultPageSize),
|
|
};
|
|
}
|
|
|
|
export function changeMode(mode: string) {
|
|
const $q = useQuasar();
|
|
if (mode === 'light') {
|
|
localStorage.setItem('currentTheme', 'light');
|
|
$q.dark.set(false);
|
|
return;
|
|
}
|
|
|
|
if (mode === 'dark') {
|
|
localStorage.setItem('currentTheme', 'dark');
|
|
$q.dark.set(true);
|
|
return;
|
|
}
|
|
|
|
if (mode === 'baseOnDevice') {
|
|
localStorage.setItem('currentTheme', 'baseOnDevice');
|
|
if (
|
|
window.matchMedia &&
|
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
) {
|
|
$q.dark.set(true);
|
|
} else {
|
|
$q.dark.set(false);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
export function capitalizeFirstLetter(val: string) {
|
|
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
|
|
}
|
|
|
|
export function getCustomerName(
|
|
record: CustomerBranch | CustomerBranchRelation,
|
|
opts?: {
|
|
locale?: string;
|
|
noCode?: boolean;
|
|
},
|
|
) {
|
|
const customer = record;
|
|
|
|
return (
|
|
{
|
|
['CORP']: {
|
|
['eng']: customer.registerNameEN,
|
|
['tha']: customer.registerName,
|
|
}[opts?.locale || 'eng'],
|
|
['PERS']:
|
|
{
|
|
['eng']: `${useOptionStore().mapOption(customer?.namePrefix || '')} ${customer?.firstNameEN} ${customer?.lastNameEN}`,
|
|
['tha']: `${useOptionStore().mapOption(customer?.namePrefix || '')} ${customer?.firstName} ${customer?.lastName}`,
|
|
}[opts?.locale || 'eng'] || '-',
|
|
}[customer.customer.customerType] +
|
|
(opts?.noCode ? '' : ' ' + `(${customer.code})`)
|
|
);
|
|
}
|