diff --git a/src/api/02_organizational/api.organization.ts b/src/api/02_organizational/api.organization.ts index f2f8f34bb..d8aad98b3 100644 --- a/src/api/02_organizational/api.organization.ts +++ b/src/api/02_organizational/api.organization.ts @@ -195,6 +195,7 @@ export default { orgAssistance: (id: string) => `${orgProfile}/assistance/${id}`, + orgIssues: `${organization}/issues`, // active รักษาการในตำแหน่งตามหน่วยงาน activeActPosition: (id: string) => `${orgPosAct}/${id}`, }; diff --git a/src/components/Dialogs/DialogDebug.vue b/src/components/Dialogs/DialogDebug.vue new file mode 100644 index 000000000..917151c0e --- /dev/null +++ b/src/components/Dialogs/DialogDebug.vue @@ -0,0 +1,410 @@ + + + + + diff --git a/src/modules/15_development/views/MainPage.vue b/src/modules/15_development/views/MainPage.vue index 8572b38d0..7db45984f 100644 --- a/src/modules/15_development/views/MainPage.vue +++ b/src/modules/15_development/views/MainPage.vue @@ -531,11 +531,7 @@ onMounted(() => { แก้ไขข้อมูล +import { computed, reactive, ref, watch } from "vue"; +import { useQuasar } from "quasar"; +import { storeToRefs } from "pinia"; + +import { useCounterMixin } from "@/stores/mixin"; +import { useIssueStore } from "@/modules/22_issues/store"; + +import http from "@/plugins/http"; +import config from "@/app.config"; + +import type { + IssueData, + IssueAttachment, + IssueAttachmentWithDownloadUrl, +} from "@/modules/22_issues/interface/Main"; + +import DialogHeader from "@/components/DialogHeader.vue"; + +const $q = useQuasar(); +const store = useIssueStore(); +const { statusOptions } = storeToRefs(store); +const { convertStatus, convertSystem } = store; + +const { + dialogConfirm, + showLoader, + hideLoader, + messageError, + success, + date2Thai, +} = useCounterMixin(); + +const modal = defineModel("modal", { + default: false, +}); +const type = defineModel("type", { + default: "edit", +}); +const data = defineModel("data", { + default: null, +}); + +const props = defineProps<{ + fetchData: () => Promise; +}>(); + +const isEdit = computed(() => type.value === "edit"); +const title = computed(() => (isEdit.value ? "แก้ไขสถานะ" : "รายละเอียดปัญหา")); +const optionsStatus = computed(() => + statusOptions.value.filter((item) => item.value !== "") +); +const splitterModel = computed({ + get: () => (isEdit.value ? 70 : 100), + set: (val: number) => {}, +}); + +const form = reactive({ + status: "", + remark: "", +}); +const fileList = ref([]); +const images = ref([]); + +const imageModal = ref(false); +const selectedImg = ref(""); + +// ฟังก์ชันสำหรับเปิดดูรูป +const openPreview = (url: string) => { + selectedImg.value = url; + imageModal.value = true; +}; + +/** ฟังก์ชันบันทึกข้อมูล */ +function onSubmit() { + dialogConfirm($q, async () => { + showLoader(); + try { + await http.put(config.API.orgIssues + "/" + data?.value?.id, { + status: form.status, + remark: form.remark, + }); + await props.fetchData(); + success($q, "บันทึกข้อมูลเรียบร้อย"); + onClose(); + } catch (error) { + messageError($q, error); + } finally { + hideLoader(); + } + }); +} + +/** ฟังก์ชันปิด dialog และรีเซ็ตข้อมูล */ +function onClose() { + modal.value = false; + form.status = ""; + form.remark = ""; + fileList.value = []; + images.value = []; +} + +async function fetchDocument(codeIssue: string, system: string) { + try { + const res = await http.get( + config.API.file("issueAttachments", system, codeIssue) + ); + const allFiles = res.data; + + // 1. แยกไฟล์ที่ไม่ใช่รูปเก็บเข้า list + fileList.value = allFiles.filter( + (f: IssueAttachment) => !/\.(jpg|jpeg|png|gif|webp)$/i.test(f.fileName) + ); + + // 2. แยกเฉพาะรูปภาพแล้วโหลดข้อมูล + const images = allFiles.filter((f: IssueAttachment) => + /\.(jpg|jpeg|png|gif|webp)$/i.test(f.fileName) + ); + for (const img of images) { + await getImg(img.path, img.fileName); + } + } catch (error) { + messageError($q, error); + } +} + +/** + * ฟังก์ชันเรียกข้อมูลรายการรูป + * @param dataList ข้อมูล + */ +async function getImg(path: string, fileName: string) { + await http + .get(config.API.fileByPath(`${path}/${fileName}`)) + .then((res) => { + const data = res.data; + const newData: IssueAttachmentWithDownloadUrl = { + ...data, + }; + + images.value.push(newData); + }) + .catch((e) => { + messageError($q, e); + }); +} + +/** + * ดาวน์โหลดลิงก์ไฟล์ + * @param fileName file name + */ +function downloadFile(fileName: string) { + showLoader(); + http + .get( + config.API.fileByFile( + "issueAttachments", + data?.value?.system || "", + data?.value?.codeIssue || "", + fileName + ) + ) + .then((res) => { + const data = res.data.downloadUrl; + window.open(data, "_blank"); + }) + .catch((e) => { + messageError($q, e); + }) + .finally(async () => { + hideLoader(); + }); +} + +const downloadImage = async (url: string, fileName: string = "image.png") => { + try { + const response = await fetch(url); + const blob = await response.blob(); // แปลงข้อมูลเป็น Blob + const blobUrl = window.URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = blobUrl; + link.download = fileName; // กำหนดชื่อไฟล์ที่ต้องการให้บันทึก + document.body.appendChild(link); + link.click(); + + // ล้างหน่วยความจำ + document.body.removeChild(link); + window.URL.revokeObjectURL(blobUrl); + } catch (error) { + messageError($q, error); + } +}; + +watch( + () => modal.value, + (val: boolean) => { + if (val && data.value) { + form.status = data.value.status || ""; + form.remark = data.value.remark || ""; + fetchDocument(data.value.codeIssue, data.value.system); + } + } +); + + + + + diff --git a/src/modules/22_issues/interface/Main.ts b/src/modules/22_issues/interface/Main.ts new file mode 100644 index 000000000..cdb6c7cd1 --- /dev/null +++ b/src/modules/22_issues/interface/Main.ts @@ -0,0 +1,42 @@ +import type { D } from "@fullcalendar/core/internal-common"; + +interface Options { + label: string; + value: string; +} + +interface IssueData { + codeIssue: string; + createdAt: Date; + createdFullName: string; + createdUserId: string; + description: string; + id: string; + lastUpdateFullName: string; + lastUpdateUserId: string; + lastUpdatedAt: Date; + menu: string; + org: string; + remark: string; + status: "IN_PROGRESS" | "RESOLVED" | "CLOSED" | "NEW"; + system: "mgt" | "user" | "checkin"; + title: string; +} + +interface IssueAttachment { + fileName: string; + path: string; + pathname: string; + title: string; +} + +interface IssueAttachmentWithDownloadUrl extends IssueAttachment { + downloadUrl: string; +} + +export type { + Options, + IssueData, + IssueAttachment, + IssueAttachmentWithDownloadUrl, +}; diff --git a/src/modules/22_issues/router.ts b/src/modules/22_issues/router.ts new file mode 100644 index 000000000..df3ef253d --- /dev/null +++ b/src/modules/22_issues/router.ts @@ -0,0 +1,14 @@ +const Main = () => import("@/modules/22_issues/views/Main.vue"); + +export default [ + { + path: "/issues", + name: "issuesMain", + component: Main, + meta: { + Auth: true, + Key: "REPORT_ORG", + Role: "STAFF", + }, + }, +]; diff --git a/src/modules/22_issues/store.ts b/src/modules/22_issues/store.ts new file mode 100644 index 000000000..f7d19d0a6 --- /dev/null +++ b/src/modules/22_issues/store.ts @@ -0,0 +1,52 @@ +import { ref } from "vue"; +import { defineStore } from "pinia"; +import type { Options } from "@/modules/22_issues/interface/Main"; + +export const useIssueStore = defineStore("issue", () => { + const systemOptions = ref([ + { label: "ทั้งหมด", value: "" }, + { label: "ระบบบริหารจัดการ", value: "MGT" }, + { label: "ระบบผู้ใช้งาน", value: "USER" }, + { label: "ระบบลงเวลา", value: "CHECKIN" }, + ]); + + const statusOptions = ref([ + { label: "ทั้งหมด", value: "" }, + { label: "ใหม่", value: "NEW" }, + { label: "กำลังดำเนินการ", value: "IN_PROGRESS" }, + { label: "แก้ไขแล้ว", value: "RESOLVED" }, + { label: "ปิดแล้ว", value: "CLOSED" }, + ]); + + function convertStatus(status: string) { + let val = status.toUpperCase(); + switch (val) { + case "NEW": + return "ใหม่"; + case "IN_PROGRESS": + return "กำลังดำเนินการ"; + case "RESOLVED": + return "แก้ไขแล้ว"; + case "CLOSED": + return "ปิดแล้ว"; + default: + return "-"; + } + } + + function convertSystem(system: string) { + let val = system.toUpperCase(); + switch (val) { + case "MGT": + return "ระบบบริหารจัดการ"; + case "USER": + return "ระบบผู้ใช้งาน"; + case "CHECKIN": + return "ระบบลงเวลา"; + default: + return "-"; + } + } + + return { systemOptions, statusOptions, convertStatus, convertSystem }; +}); diff --git a/src/modules/22_issues/views/Main.vue b/src/modules/22_issues/views/Main.vue new file mode 100644 index 000000000..31b0209bb --- /dev/null +++ b/src/modules/22_issues/views/Main.vue @@ -0,0 +1,333 @@ + + + + diff --git a/src/router/index.ts b/src/router/index.ts index 3fc48e205..f570ed6ce 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -28,6 +28,7 @@ import ModuleCommand from "@/modules/18_command/router"; import ModulePositionCondition from "@/modules/19_condition/router"; import ModulePositionTemp from "@/modules/20_positionTemp/router"; import ModuleReport from "@/modules/21_report/router"; +import ModuleIssues from "@/modules/22_issues/router"; // TODO: ใช้หรือไม่? import { authenticated, logout } from "@/plugins/auth"; @@ -79,6 +80,7 @@ const router = createRouter({ ...ModulePositionCondition, ...ModulePositionTemp, ...ModuleReport, + ...ModuleIssues, ], }, /** diff --git a/src/stores/positionKeycloak.ts b/src/stores/positionKeycloak.ts new file mode 100644 index 000000000..a285c459c --- /dev/null +++ b/src/stores/positionKeycloak.ts @@ -0,0 +1,60 @@ +import { ref } from "vue"; +import { defineStore } from "pinia"; + +export const usePositionKeycloakStore = defineStore("positionKeycloak", () => { + const dataPositionKeycloak = ref(null); + + function setPositionKeycloak(data: any) { + dataPositionKeycloak.value = data; + } + + function findOrgName(obj: any) { + if (obj) { + let name = + obj.child4 != null && + obj.child4 !== "" && + obj.child3 != null && + obj.child3 !== "" + ? obj.child4 + (obj.child3 ? "/" : "") + : obj.child4 != null && obj.child4 !== "" + ? obj.child4 + : ""; + + name += + obj.child3 != null && + obj.child3 !== "" && + obj.child2 != null && + obj.child2 !== "" + ? obj.child3 + (obj.child2 ? "/" : "") + : obj.child3 != null && obj.child3 !== "" + ? obj.child3 + : ""; + + name += + obj.child2 != null && + obj.child2 !== "" && + obj.child1 != null && + obj.child1 !== "" + ? obj.child2 + (obj.child1 ? "/" : "") + : obj.child2 != null && obj.child2 !== "" + ? obj.child2 + : ""; + + name += + obj.child1 != null && + obj.child1 !== "" && + obj.root != null && + obj.root !== "" + ? obj.child1 + (obj.root ? "/" : "") + : obj.child1 != null && obj.child1 !== "" + ? obj.child1 + : ""; + name += obj.root != null && obj.root !== "" ? obj.root : ""; + return name == "" ? "-" : name; + } else { + return ""; + } + } + + return { dataPositionKeycloak, setPositionKeycloak, findOrgName }; +}); diff --git a/src/views/MainLayout.vue b/src/views/MainLayout.vue index 8180cbd86..452e437a4 100644 --- a/src/views/MainLayout.vue +++ b/src/views/MainLayout.vue @@ -6,6 +6,7 @@ import { storeToRefs } from "pinia"; import { scroll, useQuasar } from "quasar"; import { useCounterMixin } from "@/stores/mixin"; import { useMenuDataStore } from "@/stores/menuList"; +import { usePositionKeycloakStore } from "@/stores/positionKeycloak"; import { tokenParsed, logout, @@ -26,6 +27,7 @@ import type { import { tabList, tabListPlacement } from "../interface/request/main/main"; import LoginLinkage from "@/components/LoginLinkage.vue"; +import DialogDebug from "@/components/Dialogs/DialogDebug.vue"; // landing page config url const configParam = { @@ -63,6 +65,7 @@ const modalLoginLinkage = ref(false); //เข้าสู่ระบ // landing page redirect const landingPageUrl = ref(configParam.landingPageUrl); +const modalDebug = ref(false); async function fetchmsgNoread() { await http @@ -524,6 +527,7 @@ async function fetchKeycloakPosition() { .get(config.API.keycloakPosition()) .then(async (res) => { const data = await res.data.result; + usePositionKeycloakStore().setPositionKeycloak(data); if (data.avatarName) { await getImg(data.profileId, data.avatarName); } else { @@ -783,6 +787,21 @@ function onViewDetailNoti(url: string) { + + + + + + แจ้งปัญหาการใช้งานระบบ + + + +