495 lines
13 KiB
TypeScript
495 lines
13 KiB
TypeScript
import { Dialog, QSelect, Notify, QNotifyCreateOptions } from 'quasar';
|
|
import { Ref, ref } from 'vue';
|
|
import axios, { AxiosInstance, AxiosProgressEvent } from 'axios';
|
|
import { ComposerTranslation } from 'vue-i18n';
|
|
import { defineStore } from 'pinia';
|
|
|
|
import { getRole } from 'src/services/keycloak';
|
|
|
|
import GlobalDialog from 'components/GlobalDialog.vue';
|
|
import DialogDuplicateData from 'components/DialogDuplicateData.vue';
|
|
|
|
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;
|
|
action?: (...args: unknown[]) => unknown;
|
|
cancel?: (...args: unknown[]) => unknown;
|
|
},
|
|
) {
|
|
dialog({
|
|
color: 'warning',
|
|
icon: 'mdi-alert',
|
|
title: t('form.warning.title'),
|
|
actionText: t('dialog.action.ok'),
|
|
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(items: unknown[], index: number) {
|
|
if (index > 0) {
|
|
const temp = items[index];
|
|
items[index] = items[index - 1];
|
|
items[index - 1] = temp;
|
|
}
|
|
}
|
|
|
|
export function moveItemDown(items: unknown[], index: number) {
|
|
if (index < items.length - 1) {
|
|
const temp = items[index];
|
|
items[index] = items[index + 1];
|
|
items[index + 1] = temp;
|
|
}
|
|
}
|
|
|
|
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): 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 (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;
|
|
}
|
|
|
|
const useUtilsStore = defineStore('utilsStore', () => {
|
|
const currentTitle = ref<{
|
|
title: string;
|
|
path: {
|
|
text: string;
|
|
i18n?: boolean;
|
|
argsi18n?: Record<string, string>;
|
|
handler?: () => unknown;
|
|
}[];
|
|
}>({
|
|
title: '',
|
|
path: [
|
|
{
|
|
text: '',
|
|
handler: () => {},
|
|
},
|
|
],
|
|
});
|
|
|
|
return {
|
|
currentTitle,
|
|
};
|
|
});
|
|
|
|
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;
|
|
},
|
|
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;
|
|
},
|
|
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): 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) || '00';
|
|
|
|
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 default useUtilsStore;
|