diff --git a/BV_DHYD_HCM/Untitled-6.html b/BV_DHYD_HCM/Untitled-6.html index 5f1bb093..f7c5f3c7 100644 --- a/BV_DHYD_HCM/Untitled-6.html +++ b/BV_DHYD_HCM/Untitled-6.html @@ -3,8 +3,8 @@ - Danh sách khóa học / chứng chỉ - + Danh sách Bác sĩ + - + - -
+ +
- -
+ +
- -
- Khóa học 1 + +
+ + + + + +
+ + +
+ +
+ BS CKII. Nguyễn Viết Hậu +
+ + +
+ BS CKII. +

Nguyễn Viết Hậu

+

Trưởng Khoa Cấp cứu

+
- -

- CHỨNG CHỈ ĐÀO Tạo KỸ THUẬT CHUYÊN MÔN: ĐIỆ... -

- -

- Phẫu thuật thần kinh luôn đi kèm với những biến chứng nặng nề làm thay đổi cả phần đời còn lại của người... -

-
- -
-
- - 10/08/2026 -
- SAT -
- - + +
- -
+ +
- -
- Khóa học 2 +
+ + + +
+ +
+
+ BS CKI. Tăng Tuấn Phong +
+
+ BS CKI. +

Tăng Tuấn Phong

+

Phó Trưởng Khoa Cấp cứu

+
- -

- CHỨNG CHỈ ĐÀO TẠO KỸ THUẬT CHUYÊN MÔN: KỸ... -

- -

- Thăm dò chức năng hô hấp là một phần quan trọng trong chẩn đoán, điều trị và theo dõi các bệnh lý hô... -

-
- -
-
- - 17/08/2026 -
- SAT -
- - +
- -
+ +
- -
- Khóa học 3 +
+ + + +
+ +
+
+ TS BS. Nguyễn Tuấn Anh +
+
+ TS BS. +

Nguyễn Tuấn Anh

+

Khoa Cấp cứu

+
- -

- CHƯƠNG TRÌNH CHỨNG CHỈ ĐÀO TẠO KỸ THUẬT... -

- -

- Đột quỵ là bệnh phổ biến với khoảng 200.000 ca đột quỵ mới mỗi năm, theo thống kê của Bộ Y tế. Hơn thế... -

-
- -
-
- - 07/09/2026 -
- SAT -
- - +
- + + + + diff --git a/BV_DHYD_HCM/temp.html b/BV_DHYD_HCM/temp.html index 939b7ad8..107d8ce1 100644 --- a/BV_DHYD_HCM/temp.html +++ b/BV_DHYD_HCM/temp.html @@ -1,166 +1,382 @@ - - - - - - Chia sẻ lời tri ân - - - - - - +
+
+

Đội ngũ nhân sự

- -

- Chia sẻ lời tri ân -

- - -
-
- - -
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
- - -
- - -
- -
- - - +
+
+
+
- Gửi thông tin - + A02-036 +
+
+ GS TS BS. +
+ Nguyễn Sào Trung +
+ Ban chuyên gia +
+
+
+ library-15 +
+
- + +
+
+
+
+ A02-037 +
+
+ TS BS. +
+ Đoàn Thị Phương Thảo +
+ Ban chuyên gia +
+
+
+ library-15 +
+
+
+ +
- - +
+ +
+
+ diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageMenuController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageMenuController.java index c3e8e42e..79547fb6 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageMenuController.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageMenuController.java @@ -51,13 +51,14 @@ public class ManageMenuController { } @PostMapping("/{id}/items/add") - public String addMenuItem(@PathVariable Long id, @RequestParam String label, @RequestParam String url, @RequestParam(required = false) Long parentId, @RequestParam(required = false) String title, @RequestParam(required = false) String imageUrl, RedirectAttributes redirectAttributes) { + public String addMenuItem(@PathVariable Long id, @RequestParam String label, @RequestParam String url, @RequestParam(required = false) Long parentId, @RequestParam(required = false) String title, @RequestParam(required = false) String imageUrl, @RequestParam(required = false) String description, RedirectAttributes redirectAttributes) { Menu menu = menuService.findById(id).orElseThrow(() -> new IllegalArgumentException("Invalid menu Id")); MenuItem item = new MenuItem(); item.setLabel(label); item.setUrl(url); item.setTitle(title); item.setImageUrl(imageUrl); + item.setDescription(description); item.setMenu(menu); item.setDisplayOrder(menu.getItems().size()); @@ -94,12 +95,13 @@ public class ManageMenuController { } @PostMapping("/{id}/items/{itemId}/update") - public String updateMenuItem(@PathVariable Long id, @PathVariable Long itemId, @RequestParam String label, @RequestParam String url, @RequestParam(required = false) Long parentId, @RequestParam(required = false) Integer displayOrder, @RequestParam(required = false) String title, @RequestParam(required = false) String imageUrl, RedirectAttributes redirectAttributes) { + public String updateMenuItem(@PathVariable Long id, @PathVariable Long itemId, @RequestParam String label, @RequestParam String url, @RequestParam(required = false) Long parentId, @RequestParam(required = false) Integer displayOrder, @RequestParam(required = false) String title, @RequestParam(required = false) String imageUrl, @RequestParam(required = false) String description, RedirectAttributes redirectAttributes) { MenuItem item = menuItemRepository.findById(itemId).orElseThrow(() -> new IllegalArgumentException("Invalid item Id")); item.setLabel(label); item.setUrl(url); item.setTitle(title); item.setImageUrl(imageUrl); + item.setDescription(description); if (displayOrder != null) { item.setDisplayOrder(displayOrder); } diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/MenuItem.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/MenuItem.java index 144b867c..bf55a2e1 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/MenuItem.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/MenuItem.java @@ -51,6 +51,10 @@ public class MenuItem extends AbstractAuditingEntity { @Column(name = "image_url", length = 500) private String imageUrl; + @Size(max = 1000) + @Column(name = "description", length = 1000) + private String description; + @Column(name = "display_order") private Integer displayOrder = 0; @@ -127,6 +131,14 @@ public class MenuItem extends AbstractAuditingEntity { this.imageUrl = imageUrl; } + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260811031000_add_description_to_menu_item.xml b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260811031000_add_description_to_menu_item.xml new file mode 100644 index 00000000..8d28b6b7 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260811031000_add_description_to_menu_item.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml b/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml index 8ae17e11..ed82f7ee 100644 --- a/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml +++ b/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml @@ -59,4 +59,5 @@ + diff --git a/sisvietnamvn_main/src/main/resources/static/css/custom.css b/sisvietnamvn_main/src/main/resources/static/css/custom.css index 2b9c1326..35ea8e37 100644 --- a/sisvietnamvn_main/src/main/resources/static/css/custom.css +++ b/sisvietnamvn_main/src/main/resources/static/css/custom.css @@ -660,6 +660,114 @@ figure.table table tr:hover { height: stretch; } +.card-icon-shadow { + display: none; +} + +/* ========================================================================== + Image Alignment Rules (Rich Text / Editor / CMS Content) + Supports display: inline-block !important in element.style + ========================================================================== */ + +/* 1. Force parent elements containing centered images to text-align: center !important */ +p[style*="text-align: center"], +p[style*="text-align:center"], +div[style*="text-align: center"], +div[style*="text-align:center"], +figure[style*="text-align: center"], +figure[style*="text-align:center"], +section[style*="text-align: center"], +section[style*="text-align:center"], +.text-center, +.ce-block--align-center { + text-align: center !important; +} + +/* 2. Parent containers auto-center if child img has auto-margin or center styles in element.style */ +p:has(> img[style*="margin-left: auto"]), +p:has(> img[style*="margin-left:auto"]), +div:has(> img[style*="margin-left: auto"]), +div:has(> img[style*="margin-left:auto"]), +figure:has(> img[style*="margin-left: auto"]), +figure:has(> img[style*="margin-left:auto"]), +p:has(> img[style*="text-align: center"]), +p:has(> img[style*="text-align:center"]), +div:has(> img[style*="text-align: center"]), +div:has(> img[style*="text-align:center"]), +p:has(> img.aligncenter), +div:has(> img.aligncenter), +figure:has(> img.aligncenter) { + text-align: center !important; +} + +/* 3. Center Aligned Images (Works for both block and inline-block) */ +p[style*="text-align: center"] img, +p[style*="text-align:center"] img, +div[style*="text-align: center"] img, +div[style*="text-align:center"] img, +figure[style*="text-align: center"] img, +figure[style*="text-align:center"] img, +section[style*="text-align: center"] img, +section[style*="text-align:center"] img, +[style*="text-align: center"] > img, +[style*="text-align:center"] > img, +img[style*="text-align: center"], +img[style*="text-align:center"], +img[style*="margin-left: auto"][style*="margin-right: auto"], +img[style*="margin-left:auto"][style*="margin-right:auto"], +img.aligncenter, +figure.aligncenter, +figure.aligncenter img, +figure.image-style-align-center, +figure.image-style-align-center img, +.text-center img, +.text-center > img, +p.text-center img, +div.text-center img { + margin-left: auto !important; + margin-right: auto !important; +} + +/* Left Aligned Images */ +p[style*="text-align: left"] img, +p[style*="text-align:left"] img, +div[style*="text-align: left"] img, +div[style*="text-align:left"] img, +figure[style*="text-align: left"] img, +figure[style*="text-align:left"] img, +[style*="text-align: left"] > img, +[style*="text-align:left"] > img, +img[style*="text-align: left"], +img[style*="text-align:left"], +img.alignleft, +figure.alignleft, +figure.alignleft img, +.text-left img, +.text-left > img { + margin-left: 0 !important; + margin-right: auto !important; +} + +/* Right Aligned Images */ +p[style*="text-align: right"] img, +p[style*="text-align:right"] img, +div[style*="text-align: right"] img, +div[style*="text-align:right"] img, +figure[style*="text-align: right"] img, +figure[style*="text-align:right"] img, +[style*="text-align: right"] > img, +[style*="text-align:right"] > img, +img[style*="text-align: right"], +img[style*="text-align:right"], +img.alignright, +figure.alignright, +figure.alignright img, +.text-right img, +.text-right > img { + margin-left: auto !important; + margin-right: 0 !important; +} + /* ========================================================================== 5. Responsive Breakpoints — 3 Groups Only ========================================================================== */ diff --git a/sisvietnamvn_main/src/main/resources/static/js/manage/editor-config.js b/sisvietnamvn_main/src/main/resources/static/js/manage/editor-config.js index d772b70e..565c6fbf 100644 --- a/sisvietnamvn_main/src/main/resources/static/js/manage/editor-config.js +++ b/sisvietnamvn_main/src/main/resources/static/js/manage/editor-config.js @@ -325,13 +325,8 @@ class SISAccordionTool { } - - - // SISHeroBannerTool extracted to /js/manage/editor-plugins/hero-banner.js - - /** * Initialize the SIS Block Editor on a given holder element. * @@ -457,7 +452,22 @@ function initSISEditor(holderId, hiddenInputId, initialData, skipSubmitHandler = if (Object.prototype.hasOwnProperty.call(sourceTools, key)) { var toolDef = sourceTools[key]; if (toolDef) { - allTools[key] = toolDef; + var toolClass = toolDef.class || toolDef; + if (typeof toolClass === 'function') { + if (registeredClasses.has(toolClass)) { + // Mark alias keys with toolbox: false so Editor.js parses the block type without adding duplicate menu items + if (typeof toolDef === 'function') { + allTools[key] = { class: toolDef, toolbox: false }; + } else { + allTools[key] = Object.assign({}, toolDef, { toolbox: false }); + } + } else { + registeredClasses.add(toolClass); + allTools[key] = toolDef; + } + } else { + allTools[key] = toolDef; + } } } } diff --git a/sisvietnamvn_main/src/main/resources/static/js/manage/editor-plugins/human-resource.js b/sisvietnamvn_main/src/main/resources/static/js/manage/editor-plugins/human-resource.js new file mode 100644 index 00000000..e2c143b4 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/static/js/manage/editor-plugins/human-resource.js @@ -0,0 +1,418 @@ +/** + * SISHumanResourceTool — Dedicated Human Resources / Doctor List Block Tool for Editor.js + * Allows content managers to easily manage doctor cards, degrees, names, positions, avatars, specialty icons, and booking/profile action links. + */ +(function () { + 'use strict'; + + class SISHumanResourceTool { + static get toolbox() { + return { + title: 'Đội ngũ nhân sự / Bác sĩ', + icon: '' + }; + } + + constructor({ data, api, readOnly }) { + this.api = api; + this.readOnly = readOnly; + this.instanceId = 'hr_' + Math.random().toString(36).substring(7); + + this.data = { + title: (data && data.title !== undefined) ? data.title : 'Đội ngũ nhân sự', + cols: parseInt(data && data.cols) || 3, + globalClass: (data && data.globalClass) || '', + globalId: (data && data.globalId) || '', + globalAttributes: (data && data.globalAttributes) || (data && data.attributes) || '', + showMoreBtn: (data && (data.showMoreBtn === true || data.showMoreBtn === 'true')), + showMoreText: (data && data.showMoreText) || 'Xem thêm', + showMoreUrl: (data && data.showMoreUrl) || '#', + items: (data && data.items && Array.isArray(data.items)) ? data.items : [] + }; + + // Populate default items if empty + if (this.data.items.length === 0) { + this.data.items.push({ + degree: 'BS CKII.', + name: 'Nguyễn Viết Hậu', + position: 'Trưởng Khoa Cấp cứu', + avatarUrl: 'https://images.unsplash.com/photo-1622253692010-333f2da6031d?auto=format&fit=crop&w=300&q=80', + specialtyIconUrl: '', + profileUrl: '#', + profileBtnText: 'Xem hồ sơ', + bookingUrl: '#', + bookingBtnText: 'Đặt lịch khám', + active: true + }); + } + this.wrapper = undefined; + } + + render() { + this.wrapper = document.createElement('div'); + this.wrapper.className = 'p-3 bg-light border rounded mb-3 ce-hr-tool-wrapper'; + this.wrapper.style.fontFamily = 'inherit'; + + const titleLabel = document.createElement('div'); + titleLabel.className = 'font-weight-bold text-primary small mb-3 d-flex align-items-center justify-content-between'; + titleLabel.innerHTML = ' Quản lý Đội ngũ Nhân sự / Danh sách Bác sĩ'; + this.wrapper.appendChild(titleLabel); + + // Global Block Settings + const settingsRow = document.createElement('div'); + settingsRow.className = 'form-row mb-3 pb-2 border-bottom'; + + // 1. Block Title + const titleDiv = document.createElement('div'); + titleDiv.className = 'col-md-4 mb-2'; + titleDiv.innerHTML = ''; + this.titleInput = document.createElement('input'); + this.titleInput.type = 'text'; + this.titleInput.className = 'form-control form-control-sm'; + this.titleInput.placeholder = 'Đội ngũ nhân sự...'; + this.titleInput.value = this.data.title; + titleDiv.appendChild(this.titleInput); + + // 2. Columns Select + const colDiv = document.createElement('div'); + colDiv.className = 'col-md-2 mb-2'; + colDiv.innerHTML = ''; + this.colsSelect = document.createElement('select'); + this.colsSelect.className = 'form-control form-control-sm'; + [1, 2, 3, 4].forEach(num => { + const opt = document.createElement('option'); + opt.value = num; + opt.textContent = num + ' cột'; + if (num === this.data.cols) opt.selected = true; + this.colsSelect.appendChild(opt); + }); + colDiv.appendChild(this.colsSelect); + + // 3. CSS Class & Global ID + const cssDiv = document.createElement('div'); + cssDiv.className = 'col-md-3 mb-2'; + cssDiv.innerHTML = ''; + this.globalClassInput = document.createElement('input'); + this.globalClassInput.type = 'text'; + this.globalClassInput.className = 'form-control form-control-sm'; + this.globalClassInput.placeholder = 'sis-doctor-section'; + this.globalClassInput.value = this.data.globalClass; + cssDiv.appendChild(this.globalClassInput); + + const idDiv = document.createElement('div'); + idDiv.className = 'col-md-3 mb-2'; + idDiv.innerHTML = ''; + this.globalIdInput = document.createElement('input'); + this.globalIdInput.type = 'text'; + this.globalIdInput.className = 'form-control form-control-sm'; + this.globalIdInput.placeholder = 'doctor-list-section'; + this.globalIdInput.value = this.data.globalId; + idDiv.appendChild(this.globalIdInput); + + settingsRow.appendChild(titleDiv); + settingsRow.appendChild(colDiv); + settingsRow.appendChild(cssDiv); + settingsRow.appendChild(idDiv); + this.wrapper.appendChild(settingsRow); + + // Show More Button Settings Row + const moreRow = document.createElement('div'); + moreRow.className = 'form-row mb-3 pb-2 border-bottom align-items-center'; + + const showMoreToggleDiv = document.createElement('div'); + showMoreToggleDiv.className = 'col-md-3 mb-2'; + const showMoreCheckbox = document.createElement('div'); + showMoreCheckbox.className = 'custom-control custom-switch'; + const checkId = 'hr_sm_' + Math.random().toString(36).substring(7); + this.showMoreCheck = document.createElement('input'); + this.showMoreCheck.type = 'checkbox'; + this.showMoreCheck.className = 'custom-control-input'; + this.showMoreCheck.id = checkId; + this.showMoreCheck.checked = !!this.data.showMoreBtn; + + const checkLabel = document.createElement('label'); + checkLabel.className = 'custom-control-label small font-weight-bold text-secondary'; + checkLabel.htmlFor = checkId; + checkLabel.textContent = 'Hiển thị nút "Xem thêm"'; + + showMoreCheckbox.appendChild(this.showMoreCheck); + showMoreCheckbox.appendChild(checkLabel); + showMoreToggleDiv.appendChild(showMoreCheckbox); + + const showMoreTextDiv = document.createElement('div'); + showMoreTextDiv.className = 'col-md-4 mb-2'; + showMoreTextDiv.innerHTML = ''; + this.showMoreTextInput = document.createElement('input'); + this.showMoreTextInput.type = 'text'; + this.showMoreTextInput.className = 'form-control form-control-sm'; + this.showMoreTextInput.value = this.data.showMoreText; + showMoreTextDiv.appendChild(this.showMoreTextInput); + + const showMoreUrlDiv = document.createElement('div'); + showMoreUrlDiv.className = 'col-md-5 mb-2'; + showMoreUrlDiv.innerHTML = ''; + this.showMoreUrlInput = document.createElement('input'); + this.showMoreUrlInput.type = 'text'; + this.showMoreUrlInput.className = 'form-control form-control-sm'; + this.showMoreUrlInput.value = this.data.showMoreUrl; + showMoreUrlDiv.appendChild(this.showMoreUrlInput); + + moreRow.appendChild(showMoreToggleDiv); + moreRow.appendChild(showMoreTextDiv); + moreRow.appendChild(showMoreUrlDiv); + this.wrapper.appendChild(moreRow); + + // Items Container + this.itemsContainer = document.createElement('div'); + this.itemsContainer.className = 'hr-items-list mb-3'; + this.wrapper.appendChild(this.itemsContainer); + + this.data.items.forEach((item, index) => { + this._renderItemRow(item, index); + }); + + // Add New Item Button + const addBtn = document.createElement('button'); + addBtn.type = 'button'; + addBtn.className = 'btn btn-sm btn-outline-primary font-weight-bold mt-2'; + addBtn.innerHTML = ' Thêm Bác sĩ / Nhân sự'; + addBtn.addEventListener('click', (e) => { + e.preventDefault(); + const newItem = { + degree: 'BS.', + name: 'Họ và Tên Bác sĩ', + position: 'Chức danh / Chuyên khoa', + avatarUrl: '', + specialtyIconUrl: '', + profileUrl: '#', + profileBtnText: 'Xem hồ sơ', + bookingUrl: '#', + bookingBtnText: 'Đặt lịch khám', + active: true + }; + this.data.items.push(newItem); + this._renderItemRow(newItem, this.data.items.length - 1); + }); + + this.wrapper.appendChild(addBtn); + + return this.wrapper; + } + + _openPicker(currentUrl, onSelect) { + if (window.SISMediaPicker && typeof window.SISMediaPicker.open === 'function') { + window.SISMediaPicker.open(function (url) { + if (url) onSelect(url); + }, currentUrl); + } else { + const url = prompt('Nhập URL hình ảnh:', currentUrl || ''); + if (url !== null) onSelect(url.trim()); + } + } + + _renderItemRow(item, index) { + const self = this; + const card = document.createElement('div'); + card.className = 'card mb-3 shadow-sm border hr-item-card'; + + const cardHeader = document.createElement('div'); + cardHeader.className = 'card-header bg-white py-2 px-3 d-flex align-items-center justify-content-between'; + cardHeader.innerHTML = ` + #${index + 1}: ${item.degree ? item.degree + ' ' : ''}${item.name || 'Bác sĩ'} +
+ + + +
+ `; + + const cardBody = document.createElement('div'); + cardBody.className = 'card-body p-3'; + + // Row 1: Degree, Name, Position + const row1 = document.createElement('div'); + row1.className = 'form-row mb-2'; + + const degreeCol = document.createElement('div'); degreeCol.className = 'col-md-2 mb-2'; + degreeCol.innerHTML = ''; + const degreeInput = document.createElement('input'); degreeInput.type = 'text'; degreeInput.className = 'form-control form-control-sm'; + degreeInput.value = item.degree || ''; + degreeInput.placeholder = 'BS CKII., TS BS...'; + degreeInput.addEventListener('input', () => { item.degree = degreeInput.value; }); + degreeCol.appendChild(degreeInput); + + const nameCol = document.createElement('div'); nameCol.className = 'col-md-5 mb-2'; + nameCol.innerHTML = ''; + const nameInput = document.createElement('input'); nameInput.type = 'text'; nameInput.className = 'form-control form-control-sm'; + nameInput.value = item.name || ''; + nameInput.placeholder = 'Nguyễn Viết Hậu...'; + nameInput.addEventListener('input', () => { + item.name = nameInput.value; + const headerText = cardHeader.querySelector('.font-weight-bold'); + if (headerText) headerText.textContent = `#${index + 1}: ${item.degree ? item.degree + ' ' : ''}${item.name}`; + }); + nameCol.appendChild(nameInput); + + const posCol = document.createElement('div'); posCol.className = 'col-md-5 mb-2'; + posCol.innerHTML = ''; + const posInput = document.createElement('input'); posInput.type = 'text'; posInput.className = 'form-control form-control-sm'; + posInput.value = item.position || ''; + posInput.placeholder = 'Trưởng Khoa Cấp cứu...'; + posInput.addEventListener('input', () => { item.position = posInput.value; }); + posCol.appendChild(posInput); + + row1.appendChild(degreeCol); row1.appendChild(nameCol); row1.appendChild(posCol); + cardBody.appendChild(row1); + + // Row 2: Avatar Image (with SISMediaPicker), Specialty Icon (with SISMediaPicker) + const row2 = document.createElement('div'); + row2.className = 'form-row mb-2'; + + // Avatar + const avatarCol = document.createElement('div'); avatarCol.className = 'col-md-6 mb-2'; + avatarCol.innerHTML = ''; + const avatarGroup = document.createElement('div'); avatarGroup.className = 'input-group input-group-sm'; + const avatarInput = document.createElement('input'); avatarInput.type = 'text'; avatarInput.className = 'form-control'; + avatarInput.value = item.avatarUrl || ''; + avatarInput.placeholder = '/uploads/... hoặc URL ảnh'; + avatarInput.addEventListener('input', () => { item.avatarUrl = avatarInput.value.trim(); }); + + const avatarAppend = document.createElement('div'); avatarAppend.className = 'input-group-append'; + const avatarPickBtn = document.createElement('button'); avatarPickBtn.type = 'button'; avatarPickBtn.className = 'btn btn-outline-secondary'; + avatarPickBtn.innerHTML = ' Thư viện'; + avatarPickBtn.addEventListener('click', (e) => { + e.preventDefault(); + self._openPicker(item.avatarUrl, (selectedUrl) => { + item.avatarUrl = selectedUrl; + avatarInput.value = selectedUrl; + }); + }); + avatarAppend.appendChild(avatarPickBtn); + avatarGroup.appendChild(avatarInput); avatarGroup.appendChild(avatarAppend); + avatarCol.appendChild(avatarGroup); + + // Specialty Icon + const iconCol = document.createElement('div'); iconCol.className = 'col-md-6 mb-2'; + iconCol.innerHTML = ''; + const iconGroup = document.createElement('div'); iconGroup.className = 'input-group input-group-sm'; + const iconInput = document.createElement('input'); iconInput.type = 'text'; iconInput.className = 'form-control'; + iconInput.value = item.specialtyIconUrl || ''; + iconInput.placeholder = '/uploads/icon.png hoặc URL'; + iconInput.addEventListener('input', () => { item.specialtyIconUrl = iconInput.value.trim(); }); + + const iconAppend = document.createElement('div'); iconAppend.className = 'input-group-append'; + const iconPickBtn = document.createElement('button'); iconPickBtn.type = 'button'; iconPickBtn.className = 'btn btn-outline-secondary'; + iconPickBtn.innerHTML = ' Thư viện'; + iconPickBtn.addEventListener('click', (e) => { + e.preventDefault(); + self._openPicker(item.specialtyIconUrl, (selectedUrl) => { + item.specialtyIconUrl = selectedUrl; + iconInput.value = selectedUrl; + }); + }); + iconAppend.appendChild(iconPickBtn); + iconGroup.appendChild(iconInput); iconGroup.appendChild(iconAppend); + iconCol.appendChild(iconGroup); + + row2.appendChild(avatarCol); row2.appendChild(iconCol); + cardBody.appendChild(row2); + + // Row 3: Action Buttons (Profile URL, Profile Btn Text, Booking URL, Booking Btn Text) + const row3 = document.createElement('div'); + row3.className = 'form-row mb-2'; + + const profUrlCol = document.createElement('div'); profUrlCol.className = 'col-md-3 mb-2'; + profUrlCol.innerHTML = ''; + const profUrlInput = document.createElement('input'); profUrlInput.type = 'text'; profUrlInput.className = 'form-control form-control-sm'; + profUrlInput.value = item.profileUrl || '#'; + profUrlInput.addEventListener('input', () => { item.profileUrl = profUrlInput.value.trim(); }); + profUrlCol.appendChild(profUrlInput); + + const profTextCol = document.createElement('div'); profTextCol.className = 'col-md-3 mb-2'; + profTextCol.innerHTML = ''; + const profTextInput = document.createElement('input'); profTextInput.type = 'text'; profTextInput.className = 'form-control form-control-sm'; + profTextInput.value = item.profileBtnText || 'Xem hồ sơ'; + profTextInput.addEventListener('input', () => { item.profileBtnText = profTextInput.value; }); + profTextCol.appendChild(profTextInput); + + const bookUrlCol = document.createElement('div'); bookUrlCol.className = 'col-md-3 mb-2'; + bookUrlCol.innerHTML = ''; + const bookUrlInput = document.createElement('input'); bookUrlInput.type = 'text'; bookUrlInput.className = 'form-control form-control-sm'; + bookUrlInput.value = item.bookingUrl || '#'; + bookUrlInput.addEventListener('input', () => { item.bookingUrl = bookUrlInput.value.trim(); }); + bookUrlCol.appendChild(bookUrlInput); + + const bookTextCol = document.createElement('div'); bookTextCol.className = 'col-md-3 mb-2'; + bookTextCol.innerHTML = ''; + const bookTextInput = document.createElement('input'); bookTextInput.type = 'text'; bookTextInput.className = 'form-control form-control-sm'; + bookTextInput.value = item.bookingBtnText || 'Đặt lịch khám'; + bookTextInput.addEventListener('input', () => { item.bookingBtnText = bookTextInput.value; }); + bookTextCol.appendChild(bookTextInput); + + row3.appendChild(profUrlCol); row3.appendChild(profTextCol); + row3.appendChild(bookUrlCol); row3.appendChild(bookTextCol); + cardBody.appendChild(row3); + + card.appendChild(cardHeader); + card.appendChild(cardBody); + + // Event handlers for move/delete + cardHeader.querySelector('.move-up-btn').addEventListener('click', (e) => { + e.preventDefault(); + if (index > 0) { + const temp = this.data.items[index]; + this.data.items[index] = this.data.items[index - 1]; + this.data.items[index - 1] = temp; + this._refreshItemsUI(); + } + }); + + cardHeader.querySelector('.move-down-btn').addEventListener('click', (e) => { + e.preventDefault(); + if (index < this.data.items.length - 1) { + const temp = this.data.items[index]; + this.data.items[index] = this.data.items[index + 1]; + this.data.items[index + 1] = temp; + this._refreshItemsUI(); + } + }); + + cardHeader.querySelector('.delete-btn').addEventListener('click', (e) => { + e.preventDefault(); + if (confirm('Bạn có chắc muốn xóa bác sĩ này?')) { + this.data.items.splice(index, 1); + this._refreshItemsUI(); + } + }); + + this.itemsContainer.appendChild(card); + } + + _refreshItemsUI() { + this.itemsContainer.innerHTML = ''; + this.data.items.forEach((item, idx) => { + this._renderItemRow(item, idx); + }); + } + + save() { + return { + title: this.titleInput ? this.titleInput.value.trim() : (this.data.title || 'Đội ngũ nhân sự'), + cols: this.colsSelect ? parseInt(this.colsSelect.value) : (this.data.cols || 3), + globalClass: this.globalClassInput ? this.globalClassInput.value.trim() : (this.data.globalClass || ''), + globalId: this.globalIdInput ? this.globalIdInput.value.trim() : (this.data.globalId || ''), + globalAttributes: this.data.globalAttributes || '', + showMoreBtn: this.showMoreCheck ? this.showMoreCheck.checked : !!this.data.showMoreBtn, + showMoreText: this.showMoreTextInput ? this.showMoreTextInput.value.trim() : (this.data.showMoreText || 'Xem thêm'), + showMoreUrl: this.showMoreUrlInput ? this.showMoreUrlInput.value.trim() : (this.data.showMoreUrl || '#'), + items: this.data.items || [] + }; + } + } + + // Register the plugin globally + window.SISEditorPlugins = window.SISEditorPlugins || {}; + window.SISEditorPlugins['human_resource'] = { class: SISHumanResourceTool }; + window.SISEditorPlugins['humanResource'] = { class: SISHumanResourceTool }; + window.SISEditorPlugins['human-resource'] = { class: SISHumanResourceTool }; +})(); diff --git a/sisvietnamvn_main/src/main/resources/static/js/manage/editor-plugins/tiny-mce.js b/sisvietnamvn_main/src/main/resources/static/js/manage/editor-plugins/tiny-mce.js index 47be383e..06f2e823 100644 --- a/sisvietnamvn_main/src/main/resources/static/js/manage/editor-plugins/tiny-mce.js +++ b/sisvietnamvn_main/src/main/resources/static/js/manage/editor-plugins/tiny-mce.js @@ -194,7 +194,7 @@ }, image_dimensions: true, object_resizing: 'img', - content_style: 'body { font-family: Roboto, ui-sans-serif, sans-serif; font-size: 14px; color: #333; } img { max-width: 100%; height: auto; cursor: pointer; display: inline-block; }', + content_style: 'body { font-family: Roboto, ui-sans-serif, sans-serif; font-size: 14px; color: #333; } img { max-width: 100%; height: auto; cursor: pointer; display: inline-block; } p[style*="text-align: center"] img, p[style*="text-align:center"] img, div[style*="text-align: center"] img, div[style*="text-align:center"] img, figure[style*="text-align: center"] img, figure[style*="text-align:center"] img, img[style*="text-align: center"], img[style*="text-align:center"], img.aligncenter, figure.aligncenter img { display: block !important; margin-left: auto !important; margin-right: auto !important; }', paste_postprocess: function(plugin, args) { if (args && args.node) { args.node.querySelectorAll('img').forEach(function(img) { diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/menus/edit.html b/sisvietnamvn_main/src/main/resources/templates/manage/menus/edit.html index 85fa0c26..23e0a351 100644 --- a/sisvietnamvn_main/src/main/resources/templates/manage/menus/edit.html +++ b/sisvietnamvn_main/src/main/resources/templates/manage/menus/edit.html @@ -66,7 +66,11 @@
- + + +
+
+
@@ -79,6 +83,9 @@ + + + @@ -114,6 +121,7 @@ th:data-label="${topItem.label}" th:data-url="${topItem.url}" th:data-title="${topItem.title}" + th:data-description="${topItem.description}" th:data-image-url="${topItem.imageUrl}" th:data-parent-id="${topItem.parent != null ? topItem.parent.id : ''}" th:data-display-order="${topItem.displayOrder}">Edit @@ -139,6 +147,7 @@ th:data-label="${child.label}" th:data-url="${child.url}" th:data-title="${child.title}" + th:data-description="${child.description}" th:data-image-url="${child.imageUrl}" th:data-parent-id="${child.parent != null ? child.parent.id : ''}" th:data-display-order="${child.displayOrder}">Edit @@ -163,6 +172,7 @@ th:data-label="${grandchild.label}" th:data-url="${grandchild.url}" th:data-title="${grandchild.title}" + th:data-description="${grandchild.description}" th:data-image-url="${grandchild.imageUrl}" th:data-parent-id="${grandchild.parent != null ? grandchild.parent.id : ''}" th:data-display-order="${grandchild.displayOrder}">Edit @@ -170,6 +180,32 @@ th:data-action="@{/manage/menus/{menuId}/items/{itemId}/delete(menuId=${menu.id}, itemId=${grandchild.id})}">Delete
+ + +
  • +
    + Link Text + Tier 4 +
    + URL +
    +
    + 0 + + +
    +
  • +
    @@ -208,7 +244,11 @@
    - + + +
    +
    +
    @@ -221,6 +261,9 @@ + + + @@ -277,6 +320,7 @@ var label = $btn.data('label') || ''; var url = $btn.data('url') || ''; var title = $btn.data('title') || ''; + var description = $btn.data('description') || ''; var imageUrl = $btn.data('image-url') || ''; var parentId = $btn.data('parent-id') || ''; var displayOrder = $btn.data('display-order') || '0'; @@ -285,6 +329,7 @@ $('#modalLabel').val(label); $('#modalUrl').val(url); $('#modalTitle').val(title); + $('#modalDescription').val(description); $('#modalImageUrl').val(imageUrl); $('#modalParentId').val(parentId); $('#modalDisplayOrder').val(displayOrder); diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/pages/form.html b/sisvietnamvn_main/src/main/resources/templates/manage/pages/form.html index e12b740e..1cc3cbe1 100644 --- a/sisvietnamvn_main/src/main/resources/templates/manage/pages/form.html +++ b/sisvietnamvn_main/src/main/resources/templates/manage/pages/form.html @@ -472,6 +472,7 @@ + diff --git a/sisvietnamvn_main/src/main/resources/templates/page.html b/sisvietnamvn_main/src/main/resources/templates/page.html index b7bd1fdb..7b6cda0c 100644 --- a/sisvietnamvn_main/src/main/resources/templates/page.html +++ b/sisvietnamvn_main/src/main/resources/templates/page.html @@ -570,6 +570,83 @@
    + + +
    +
    +

    Đội ngũ nhân sự

    + +
    + +
    +
    +
    + +
    + Specialty Icon +
    + + +
    +
    + +
    +
    + +

    Doctor Name

    +

    +
    +
    +
    + + + +
    +
    +
    +
    + + +
    + Xem thêm +
    +
    +
    +
    +
    - - -
    -
    - - - -

    -

    -

    -

    -
    -
    -

    -
    -
    - - -

    -
    - - -
      -
    • -
    -
      -
    1. -
    -
    - - - -

    -
    - - -
    -

    -
    -
    -
    - - -
    -
    - - -
    + + +
    +
    + + + +

    +

    +

    +

    +
    +
    +

    +
    + + +

    +
    + + +
      +
    • +
    +
      +
    1. +
    +
    + + + +

    +
    + + +
    +

    +
    +
    +
    + + +
    +
    + + +
    +
    - + -

    Đội ngũ nhân sự

    A09-056 (1)
    TS BS.
    Trần Hương Giang
    Trưởng khoa Giải phẫu bệnh
    library-15
    A02-036
    GS TS BS.
    Nguyễn Sào Trung
    Ban chuyên gia
    library-15
    +
    +
    +

    Đội ngũ nhân sự

    +
    +
    +
    +
    + A09-056 (1) +
    +
    TS BS.
    + Trần Hương Giang 2 +
    Trưởng khoa Giải phẫu bệnh
    +
    +
    + library-15 +
    +
    +
    + +
    +
    +
    +
    + A02-036 +
    +
    GS TS BS.
    + Nguyễn Sào Trung +
    Ban chuyên gia
    +
    +
    + library-15 +
    +
    +
    + +
    + +
    +
    +
    +
    -

    Thành tích và thành tựu

    Bằng khen Bộ trưởng Bộ Y tế
    2022
    Bằng khen Bộ trưởng Bộ Y tế
    2023
    Tập thể lao động xuất sắc
    2020 - 2023
    Bằng khen Bộ trưởng Bộ Y tế
    2022
    Bằng khen Bộ trưởng Bộ Y tế
    2023
    Tập thể lao động xuất sắc
    2020 - 2023
    +
    +
    +

    Thành tích và thành tựu

    +
    + +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    + Bằng khen Bộ trưởng Bộ Y tế +
    +
    +
    2022
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    + Bằng khen Bộ trưởng Bộ Y tế +
    +
    +
    2023
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    + Tập thể lao động xuất sắc +
    +
    +
    2020 - 2023
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    + Bằng khen Bộ trưởng Bộ Y tế +
    +
    +
    2022
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    + Bằng khen Bộ trưởng Bộ Y tế +
    +
    +
    2023
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    +
    + Tập thể lao động xuất sắc +
    +
    +
    2020 - 2023
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    diff --git a/sisvietnamvn_main/src/main/resources/templates/themes/umass/header.html b/sisvietnamvn_main/src/main/resources/templates/themes/umass/header.html index c92ae9d0..9931047d 100644 --- a/sisvietnamvn_main/src/main/resources/templates/themes/umass/header.html +++ b/sisvietnamvn_main/src/main/resources/templates/themes/umass/header.html @@ -223,14 +223,14 @@ padding-left: 8px; /* Layout indent animation */ } - /* Right Column: Image */ + /* Column 4: Image */ .mega-col-image { flex: 1; display: flex; align-items: center; justify-content: center; border-left: 1px solid #d1d5db; - padding-left: 30px; + padding-left: 20px; } .mega-col-image img { width: 100%; @@ -240,11 +240,11 @@ object-fit: cover; } - /* Middle Column for Tabbed Level 2 */ + /* Column 2: Level 2 */ .mega-col-level2 { - flex: 1.2; + flex: 1; border-left: 1px solid #d1d5db; - padding-left: 30px; + padding-left: 20px; max-height: 245px; overflow-y: auto; } @@ -281,11 +281,11 @@ color: #111827 !important; } - /* Right Column for Level 3 Links */ + /* Column 3: Level 3 */ .mega-col-level3-container { flex: 1; border-left: 1px solid #d1d5db; - padding-left: 30px; + padding-left: 20px; max-height: 245px; overflow-y: auto; } @@ -378,9 +378,12 @@

    BỆNH VIỆN ĐA KHOA QUỐC TẾ S.I.S CẦN THƠ

    -

    Bệnh viện Đa Khoa Quốc Tế S.I.S Cần Thơ tự hào là đơn vị tiên phong trong việc cung cấp dịch vụ khám chữa bệnh chất lượng cao, mang lại sự tin cậy và an tâm cho bệnh nhân.

    -

    Khám phá các chuyên khoa hàng đầu với đội ngũ bác sĩ chuyên môn cao, trang thiết bị y tế hiện đại đạt tiêu chuẩn quốc tế.

    -

    Khám phá thêm thông tin chi tiết về các dịch vụ, chuyên gia và hoạt động của chúng tôi để bảo vệ sức khỏe của bạn.

    +

    + +

    Bệnh viện Đa Khoa Quốc Tế S.I.S Cần Thơ tự hào là đơn vị tiên phong trong việc cung cấp dịch vụ khám chữa bệnh chất lượng cao, mang lại sự tin cậy và an tâm cho bệnh nhân.

    +

    Khám phá các chuyên khoa hàng đầu với đội ngũ bác sĩ chuyên môn cao, trang thiết bị y tế hiện đại đạt tiêu chuẩn quốc tế.

    +

    Khám phá thêm thông tin chi tiết về các dịch vụ, chuyên gia và hoạt động của chúng tôi để bảo vệ sức khỏe của bạn.

    +
    @@ -392,25 +395,30 @@
    - -
    + + + + +
    + + +
    diff --git a/sisvietnamvn_main/temp3.html b/sisvietnamvn_main/temp3.html deleted file mode 100644 index b9609315..00000000 --- a/sisvietnamvn_main/temp3.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - - - Cơ Hội Nghề Nghiệp - S.I.S - - - -
    - -
    -
    - - -
    - -

    Cơ hội nghề nghiệp

    - - -
    - -
    - Bằng khen S.I.S -
    - - -
    - -
    -
    - Cúp danh hiệu - TOP 10 -
    -

    NƠI LÀM VIỆC TỐT NHẤT NGÀNH Y DƯỢC - CHĂM SÓC SỨC KHOẺ

    -
    - - -
    -
    - Cúp danh hiệu - TOP 50 -
    -

    NƠI LÀM VIỆC TỐT NHẤT VIỆT NAM

    -
    -
    -
    - - -
    - Đội ngũ Y Bác sĩ S.I.S -
    -
    -
    - - diff --git a/sisvietnamvn_main/temp3.json b/sisvietnamvn_main/temp3.json new file mode 100644 index 00000000..ae25aacc --- /dev/null +++ b/sisvietnamvn_main/temp3.json @@ -0,0 +1,144 @@ + + + + + + Danh sách Bác sĩ + + + + + + + +
    + + +
    +
    + +
    + + + + + +
    + + +
    + +
    + BS CKII. Nguyễn Viết Hậu +
    + + +
    + BS CKII. +

    Nguyễn Viết Hậu

    +

    Trưởng Khoa Cấp cứu

    +
    +
    +
    + + + +
    + + +
    +
    +
    + + + +
    + +
    +
    + BS CKI. Tăng Tuấn Phong +
    +
    + BS CKI. +

    Tăng Tuấn Phong

    +

    Phó Trưởng Khoa Cấp cứu

    +
    +
    +
    + + +
    + + +
    +
    +
    + + + +
    + +
    +
    + TS BS. Nguyễn Tuấn Anh +
    +
    + TS BS. +

    Nguyễn Tuấn Anh

    +

    Khoa Cấp cứu

    +
    +
    +
    + + +
    + +
    + + + + + + + + \ No newline at end of file diff --git a/sisvietnamvn_main/uploads/2026/08/00973.jpg b/sisvietnamvn_main/uploads/2026/08/00973.jpg new file mode 100644 index 00000000..ec21b4cc Binary files /dev/null and b/sisvietnamvn_main/uploads/2026/08/00973.jpg differ diff --git a/sisvietnamvn_main/uploads/2026/08/01049.jpg b/sisvietnamvn_main/uploads/2026/08/01049.jpg new file mode 100644 index 00000000..27ab1f5c Binary files /dev/null and b/sisvietnamvn_main/uploads/2026/08/01049.jpg differ diff --git a/sisvietnamvn_main/uploads/2026/08/01358.jpg b/sisvietnamvn_main/uploads/2026/08/01358.jpg new file mode 100644 index 00000000..8283ceba Binary files /dev/null and b/sisvietnamvn_main/uploads/2026/08/01358.jpg differ