พื้นที่ทำงานแบบแท็บ MDI และสิทธิ์จอ (TabWorkspace)
TabWorkspace คือ "หน้าต่างโปรแกรมบัญชี" ที่เปิดหลายจอเป็นแท็บพร้อมกัน เลียนแบบ MDI ของ SML เดิม มีแถบเมนูบน, ผังเมนู, เมนูที่ใช้บ่อย, ค้นหาเมนู และแท็บของจอที่เปิดอยู่
จอทุกจอได้รับสิทธิ์ เพิ่ม/แก้ไข/ลบ ผ่าน TabsContext (กันฝั่ง UI เพื่อ UX เท่านั้น backend ตรวจซ้ำ)
ทำงานอย่างไร
ตัวห่อ: PersistentWorkspaceHost โหลด TabWorkspace แบบ dynamic (PersistentWorkspaceHost.tsx:37) และส่ง session, autoOpen (จาก ?open=) เข้ามา ดู โครงหน้าเว็บและการนำทาง (App shell & routing).
สถานะหลัก (TabWorkspace.tsx:200-255): tabs ({key,title,code}), activeKey, mountedTabKeys, popupScreen, freqStats, expandedNodes, quickSearch, screenRightsMap, rightsLoaded, permissionDeniedNotice.
คีย์แท็บ: เปิดเมนูเดียวกันซ้ำ = แท็บใหม่อิสระ คีย์เป็น menu_x, menu_x#2, menu_x#3 ... และชื่อแท็บต่อท้าย #N; getBaseMenuKey (บรรทัด 58) ตัดส่วนหลัง # เพื่อหาคีย์ใน SCREENS (ทะเบียนหน้าจอและโครงเมนู (Screen registry & smlMenu)).
Keep-alive: จอที่เคยเปิดจะอยู่ใน mountedTabKeys และไม่ถูก unmount เมื่อสลับแท็บ (ซ่อนไว้ ข้อมูลที่พิมพ์ค้างยังอยู่) - เรนเดอร์ที่ TabWorkspace.tsx:1539-1550, แต่ละแผงมี data-workspace-tab-panel / data-workspace-tab-active.
ลำดับตรวจใน openScreen(menuName, {reuseExisting}) (บรรทัด 532-600)
- ไม่มีใน
SCREENS-> คืนfalse. - ผู้ใช้ไม่ใช่ admin และ
isAdminOnlyMenu-> modal "จำกัดสิทธิ์เฉพาะผู้ดูแลระบบ". - ผู้ใช้ไม่ใช่ admin และสิทธิ์ยังโหลดไม่เสร็จ -> modal "กำลังตรวจสอบสิทธิ์".
- ผู้ใช้ไม่ใช่ admin และไม่มี
right.readของจอนั้น -> modal "ไม่มีสิทธิ์เข้าใช้งานหน้าจอนี้". recordMenuUsageนับสถิติเมนูที่ใช้บ่อย; ถ้าscreen.mode === "popup"เปิดเป็น modal (popupScreen); ถ้าreuseExistingและมีแท็บอยู่แล้ว -> สลับไปแท็บนั้น; ไม่งั้นเปิดแท็บใหม่.
สิทธิ์จอ (screen rights)
- ผู้ใช้ที่
session.isDemo || role === "ADMIN" || userLevel >= 9ถือเป็น admin ไม่ต้องโหลดสิทธิ์ (บรรทัด 197, 254-260). คนอื่นเรียกGET /api/workspace/my-screen-rights(บรรทัด 261-280) ได้{is_admin, role, user_code, menus[]}แล้วทำ map ตามcode. getScreenRights(key)(บรรทัด 622-632) ->ScreenRights {canAdd, canEdit, canDelete, readOnly}(TabsContext.tsx:41-47): admin =FULL_SCREEN_RIGHTS; เมนู admin-only = อ่านอย่างเดียว; ระหว่างโหลด =PENDING_SCREEN_RIGHTS(ทุกอย่างปิด, บรรทัด 128); อื่นๆ ตามright.add/edit/delete.- จอใช้ผ่าน hook ใน
TabsContext.tsx:useScreenRights()(64) และuseTabs()(69);canSaveWithRights(rights, isNew)(57) = รายการใหม่ต้องcanAdd, รายการเดิมต้องcanEdit(ใช้ใน จอข้อมูลหลักมาตรฐานและจอ master-detail (StandardMasterScreen / MasterDetailScreen) และ จอเอกสารธุรกรรมแบบเดียวกันทุกจอ (UniversalTransScreen)). - ชั้นกันเพิ่มเติมที่ตัวห่อแผง:
onClickCapture(บรรทัด 1590+) ดักปุ่มที่ข้อความมี "เพิ่มใหม่"/"เพิ่ม" หรือ "แก้ไข" เมื่อไม่มีสิทธิ์ แล้วแสดง modal "ไม่มีสิทธิ์เพิ่มข้อมูลใหม่/แก้ไขข้อมูล" (หมายเหตุ: อ้างอิงข้อความปุ่ม จึงผูกกับคำบนปุ่มโดยตรง).
ค่าที่ TabsApi ให้จอ (TabsContext.tsx:15-38): openScreen, session, activeKey, isActive(menuKey), screenKey (คีย์เมนูของจอนี้), screenRights, closeScreen() (ปิดแบบตรวจข้อมูลค้าง), discardAndCloseScreen(menuKey) (ปิดทันที ใช้หลังจอถามยืนยันเองแล้ว). ScreenTabsProvider (บรรทัด 79) ครอบแต่ละแผงและป๊อปอัปให้ screenKey/rights ถูกต่อจอ.
ปิดแท็บเมื่อมีข้อมูลค้าง
checkTabDirty(tabKey)(643-665) ตรวจ DOM ของแผงนั้น: หาข้อความหัวจออย่าง "มีการแก้ไขข้อมูล", "เพิ่มรายการใหม่", "แก้ไขแล้ว ...", "โหมดแก้ไข" (ยกเว้นจอmenu_mis_change_password) นอกจาก dialog/datalist; หรือ flagdirtyInputTabsRefที่onInputCaptureตั้งเมื่อพิมพ์ในช่องที่ไม่ใช่ช่องค้นหา/readOnly/รายงาน.Ctrl+Sล้าง flag (บรรทัด 1586-1588).closeTab(686) ถ้า dirty เปิดcloseWarnState(modal เตือน, โหมดsingleหรือall, บรรทัด 1711+) ก่อนเรียกforceCloseTab(666). ปิดแล้วเลือกแท็บข้างเคียงเป็น active.
บันทึกสภาพ: แท็บที่เปิดอยู่ + แท็บ active + สถานะผังเมนู เก็บ localStorage["sml_workspace_tabs_v1_<db>_<USER>"] (บรรทัด 207-212, 360-378) และคืนค่าเมื่อเปิดใหม่ (290-). เก็บ sessionStorage["sml_last_active_menu"] และยิง event sml:workspace-tab-change (รายละเอียด activeKey/code/title/tabCount) ให้ส่วนอื่นฟัง (บรรทัด 380).
เมนูที่ใช้บ่อย / ค้นหาเมนู
freqStatsเก็บ{count,lastAt}ต่อเมนู ในlocalStorage["sml_user_freq_menus_v1_<db>_<USER>"](บรรทัด 201-205, 395-441); จัดอันดับตาม count แล้ว lastAt ตัดที่ 16 รายการ (บรรทัด 464-465) และ dropdown แสดง 10 รายการแรก (บรรทัด 1476). รายการที่แสดงต้องมีจอในSCREENSและอยู่ในรุ่นที่อนุญาตเท่านั้น (บรรทัด 448-455; ไม่ได้กรองตามสิทธิ์ แต่openScreenตรวจสิทธิ์ตอนคลิก).- ค้นหาเมนู (
quickSearch, บรรทัด 745-795): ใช้matchSegmentedAndLikeกับcode, legacyCode, text, name, owner, csharpModuleก่อน (ตรงทุกคำ) ถ้าไม่เจอจึงจับคู่บางส่วนแล้วจัดคะแนน; แสดงสูงสุด 14 รายการ; กด Enter เปิดรายการแรก (บรรทัด 1186). - หมวดเมนูบนแถบ:
getMenuCategory(master / daily / cancel / report / general) จากMenuBar.tsxใช้จัดกลุ่มสีและชื่อในผังเมนู (โครงหน้าเว็บและการนำทาง (App shell & routing)).
กรองตามรุ่นผลิตภัณฑ์: แท็บ/ป๊อปอัปที่ไม่อยู่ใน editionAllowedMenuSet จะถูกซ่อนและสลับ active ไปแท็บอื่น (บรรทัด 466-490) ดู ทะเบียนหน้าจอและโครงเมนู (Screen registry & smlMenu).
ข้อควรระวัง
- ตรวจ dirty อ่านข้อความภาษาไทยจาก DOM - ถ้าเปลี่ยนข้อความหัวจอ (เช่น "มีการแก้ไขข้อมูล") ตัวเตือนปิดแท็บจะไม่ทำงาน.
- คีย์แท็บมี
#Nเสมอเมื่อซ้ำ จอที่ใช้screenKeyต้องใช้ฐานคีย์ ไม่ใช่คีย์แท็บ. - ผู้ใช้สิทธิ์ไม่พอไม่ถูกบล็อกที่ backend โดยโค้ดชั้นนี้ - เป็นการกันฝั่งหน้าจอ (คอมเมนต์ใน
TabsContext.tsx:38-40).
ที่มาในโค้ด
frontend/account/tabs/TabWorkspace.tsx:58-62,79,128- คีย์แท็บ, provider, สิทธิ์ระหว่างรอfrontend/account/tabs/TabWorkspace.tsx:184-290- รุ่น, admin, โหลดสิทธิ์frontend/account/tabs/TabWorkspace.tsx:360-465- บันทึกแท็บ, เมนูใช้บ่อยfrontend/account/tabs/TabWorkspace.tsx:532-632-openScreen,getScreenRightsfrontend/account/tabs/TabWorkspace.tsx:643-740,1711-1830- ปิดแท็บ + modal เตือนfrontend/account/tabs/TabWorkspace.tsx:745-795,1186- ค้นหาเมนูfrontend/account/tabs/TabWorkspace.tsx:1304-1330,1539-1660,1850-1875- ลากสลับแท็บ (moveTab492), แผง keep-alive, ป๊อปอัปfrontend/account/tabs/TabsContext.tsx:15-72-TabsApi,ScreenRights, hooks
ดูเพิ่ม
ภาพรวม Frontend ของ SML Ai Account (Frontend overview) · ทะเบียนหน้าจอและโครงเมนู (Screen registry & smlMenu) · โครงหน้าเว็บและการนำทาง (App shell & routing) · เซสชันและการยืนยันตัวตนฝั่ง frontend (Auth session & tokens) · จอข้อมูลหลักมาตรฐานและจอ master-detail (StandardMasterScreen / MasterDetailScreen) · จอเอกสารธุรกรรมแบบเดียวกันทุกจอ (UniversalTransScreen)