feat: implement doctor data migration and update booking link UI in doctor-detail template
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1 @@
|
||||
SELECT d.name, s.name as specialty FROM sis_doctor d LEFT JOIN sis_specialty s ON d.specialty_id = s.id WHERE d.name = 'Trương Lê Anh Kiệt';
|
||||
@@ -0,0 +1,42 @@
|
||||
import csv
|
||||
import xml.etree.ElementTree as ET
|
||||
import sys
|
||||
import os
|
||||
|
||||
csv_file = '/home/x79/sisvietnamvn_01/sisvietnamvn_Trang chính thức hiện tại/DS_BacSi/DANH SÁCH BÁC SĨ NGỒI PHÒNG KHÁM_THÔNG TIN CHI TIẾT_FULL.csv'
|
||||
xml_file = '/home/x79/sisvietnamvn_01/sisvietnamvn_Trang chính thức hiện tại/DS_BacSi/bnhvinakhoaquctsiscnth.WordPress.2026-07-28 (5).xml'
|
||||
|
||||
print("CSV Headers:")
|
||||
with open(csv_file, 'r', encoding='utf-8-sig') as f:
|
||||
reader = csv.reader(f)
|
||||
print(next(reader))
|
||||
|
||||
print("\nXML meta keys for first doctor:")
|
||||
tree = ET.parse(xml_file)
|
||||
root = tree.getroot()
|
||||
ns = {
|
||||
'wp': 'http://wordpress.org/export/1.2/',
|
||||
'content': 'http://purl.org/rss/1.0/modules/content/'
|
||||
}
|
||||
|
||||
for channel in root.findall('channel'):
|
||||
for item in channel.findall('item'):
|
||||
post_type = item.find('wp:post_type', ns)
|
||||
if post_type is not None and post_type.text == 'doctor':
|
||||
print('Title:', item.find('title').text)
|
||||
for meta in item.findall('wp:postmeta', ns):
|
||||
key = meta.find('wp:meta_key', ns)
|
||||
val = meta.find('wp:meta_value', ns)
|
||||
if key is not None and val is not None and not key.text.startswith('_'):
|
||||
print(f" {key.text}")
|
||||
break
|
||||
|
||||
# Count doctors in each
|
||||
print(f"\nTotal items in XML:")
|
||||
c = 0
|
||||
for channel in root.findall('channel'):
|
||||
for item in channel.findall('item'):
|
||||
post_type = item.find('wp:post_type', ns)
|
||||
if post_type is not None and post_type.text == 'doctor':
|
||||
c += 1
|
||||
print(c)
|
||||
@@ -0,0 +1 @@
|
||||
SELECT table_name, column_name, data_default FROM user_tab_columns WHERE table_name = 'SIS_SPECIALTY';
|
||||
@@ -0,0 +1,69 @@
|
||||
Name,Position,Gender,Birthday,Phone,Email,Address,Order,ImageID,ImageURL,Content
|
||||
Th.S HOÀNG KIM NGA,Phó giám đốc tài chính,nữ,20181003,123,bbb@gmail.com,fdfdfdfd,2,5394,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Thạc sĩ Tài chính - Ngân hàng năm 2010 tại CFVG TP.HCM (Liên kết giữa ĐH Kinh tế và Đại Học EFCG Pháp) Tốt nghiệp Cử nhân Anh văn năm 1997 tại Đại họ
|
||||
TS.BS Tăng Hà Nam Anh,Cố vấn chuyên môn,nam,20181002,012455221,bbb@gmail.com,adfsda,1,1713,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Tiến Sĩ Y Khoa chuyên ngành Chấn Thương Chỉnh Hình tại Trường Đại Học Y Dược TP. Hồ Chí Minh Kinh nghiệm làm việc: Trưởng Khoa Chấn Thương Chỉnh Hì
|
||||
TS.BS TRẦN CHÍ CƯỜNG,Giám đốc,nam,20181025,,,,0,12940,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Đại học Y khoa năm 2000 tại trường Đại học Y Dược Cần Thơ Tốt nghiệp Bác sĩ Chuyên khoa 1 chuyên ngành Phẫu thuật Thần kinh (Ngoại Thần kinh) năm 200
|
||||
BS.CKII PHAN TRỊNH MINH HIẾU,Phó giám đốc điều hành,nữ,20181011,0123456789,abc@gmail.com,abc,1,3542,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Đại học Y Khoa năm 2000 tại Đại học Y Dược Cần Thơ Tốt nghiệp Bác sĩ Chuyên khoa I chuyên ngành Sản Phụ Khoa năm 2004 tại Đại học Y Dược TP. Hồ Chí Mi
|
||||
DS. Lê Hoàng Mai,Trưởng khoa,nữ,19751016,,,,1,4589,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Dược Sĩ Đại Học năm 1972 tại Trường Đại Học Dược Khoa Sài Gòn Chứng chỉ đào tạo chuyên môn Dinh Dưỡng năm 1990 của Đại Học Mahidol - Thái Lan Chứng
|
||||
ĐD. Nguyễn Thị Thiên Trang,Điều dưỡng trưởng,nữ,19800207,,,,4,2249,,Giới tính: Nữ Quá trình đào tạo: Chứng chỉ đào tạo Điều dưỡng năm 1998 của TTĐT & BDCBYT - TP. Hồ Chí Minh Chứng chỉ đào tạo Năng lực quản lý cấp trung năm 2017 của FBT School of Business
|
||||
BS.CKII HUỲNH QUỐC SĨ,Trưởng khoa,nam,20180405,,,,0,4570,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Bác sĩ Đa khoa năm 2009 tại Trường Đại học Y Dược Cần Thơ Tốt nghiệp Thạc sĩ chuyên ngành Nội Tổng quát năm 2015 tại Trường Đại học Y Dược Cần Thơ
|
||||
BS.CKI Nguyễn Thị Hạnh,Phó khoa,nữ,19790413,,,,1,4552,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác sĩ Đa khoa năm 2005 tại Trường Đại học Y Dược Hải Phòng Tốt nghiệp Bác sĩ Chuyên khoa I năm 2014 tại Trường Đại học Y Dược TP.HCM Kinh nghiệm làm
|
||||
ĐD. Nguyễn Thị Thủy Tiên,Điều dưỡng trưởng,nữ,20181101,0123456789,abc@gmail.com,fdfdfdfd,1,8010,,Giới tính: Nữ Quá trình đào tạo: Chứng chỉ đào tạo Điều dưỡng đa khoa năm 2016 của Trường Cao Đẳng Y Tế Tiền Giang Cử nhân: Điều dưỡng – Trường Đại Học Cửu Long (năm 2021) Chứng chỉ: Quản lí đ
|
||||
Đinh Hoàng Khoa,Trưởng phòng,nam,20181205,,,,0,,,Trưởng phòng
|
||||
Nguyễn Văn Chiến,Nhân viên,nam,19991214,,,,1,,,Nhân viên
|
||||
Phan Minh Quang,Nhân viên,nam,20011211,,,,2,,,Nhân viên
|
||||
BS.CKI PHƯƠNG HỒNG THỌ,Phó khoa,nam,20181220,,,,1,4588,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Bác sĩ Đa khoa năm 1999 tại Trường Đại học Y Dược Cần Thơ Tốt nghiệp Bác sĩ Chuyên khoa I chuyên ngành Nội Thần kinh năm 2015 tại Trường Đại học Y Dư
|
||||
ThS.BS NGUYỄN ANH TRUNG,Phó khoa,nam,20181220,,,,0,4587,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Thạc sĩ chuyên ngành Chấn thương Chỉnh hình tại Trường Đại học Y Dược TP.HCM Chứng chỉ đào tạo Thay khớp của Trường Đại học Y Dược TP.HCM Chứng chỉ
|
||||
BS.CKI NGÔ THỊ NHƯ THÙY,Nhân viên,nữ,20181220,,,,2,1133,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác Sĩ Đa Khoa năm 2000 tại Trường Đại Học Y Dược Cần Thơ Tốt nghiệp Bác Sĩ Chuyên Khoa I chuyên ngành Nhi Khoa năm 2009 tại Trường Đại Học Y Dược TP.
|
||||
ĐD. Lê Quang Khải,Điều dưỡng trưởng,nam,20190301,,,,1,4586,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Trung cấp Điều dưỡng Đa khoa năm 2010 tại Trường Trung cấp Y tế Vĩnh Long Chứng chỉ đào tạo liên tục - Chứng chỉ CRRT năm 2014 của Bệnh viện 115 TP.
|
||||
ĐD. Đặng Thị Ngọc Trân,Điều dưỡng trưởng,nữ,20190301,,,,1,1854,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Trung cấp Điều dưỡng đa khoa năm 2006 tại Trường trung học y tế Bạc Liêu Tốt nghiệp Cao đẳng Điều dưỡng đa khoa năm 2018 tại Trường Cao đẳng Y tế Cần
|
||||
ĐD. Trương Thị Ngọc Linh,Điều dưỡng trưởng,nữ,20190301,,,,1,4585,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Trung cấp Điều dưỡng đa khoa năm 2009 tại Trường Cao đẳng Y tế Cần Thơ Chứng chỉ Điều dưỡng năm 2012 tại Bệnh Viện Đa Khoa Hoàn Mỹ Cửu Long Kinh nghi
|
||||
ĐD. Trần Việt Khanh,Điều dưỡng trưởng,nam,20190301,,,,1,4583,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Cử nhân Điều dưỡng đa khoa năm 2011 tại Trường Đại Học Y Dược Cần Thơ Tốt nghiệp Cử nhân Ngôn ngữ Anh năm 2016 tại Trường Đại Học Cần Thơ Chứng chỉ
|
||||
BS.CKII LÊ UYỂN DIỄM,Trưởng khoa,nữ,20190301,,,,0,1863,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác sĩ đa khoa năm 1996 tại Trường Đại Học Y Dược Huế Tốt nghiệp Bác sĩ Chuyên khoa I chuyên ngành Chẩn đoán hình ảnh năm 2005 tại Trường Đại Học Y Dư
|
||||
TS.BS NGUYỄN HUY THẮNG,Cố vấn chuyên môn,nam,20190301,0955555555,hh@gmail.com,Can tho,1,1865,,Giới tính: Nam Bằng cấp chuyên môn: Tốt nghiệp trường Đại học Y dược TP Hồ Chí Minh năm 1993 Bằng Tiến sĩ - Trường Đại học Y dược TP Hồ Chí Minh năm 2012 (Tiến sĩ chuyên ngành Thần kinh) Đào
|
||||
BS.CKI TRẦN ÁI THANH,Phó khoa,nữ,20190521,,,,1,4563,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác Sĩ Đa Khoa năm 2008 tại Trường Đại Học Y Dược Cần Thơ Tốt nghiệp Bác Sĩ Chuyên Khoa I chuyên ngành Nội - Tổng quát năm 2016 tại Trường Đại Học Y D
|
||||
BS.CKI TÔ VĂN TÂN,Phó khoa,nam,20190522,,,,1,4582,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Bác Sĩ Đa Khoa năm 2011 tại Trường Đại học Y Dược Cần Thơ Tốt nghiệp Bác Sĩ Chuyên Khoa Cấp I Nội Tổng quát năm 2018 tại Trường Đại học Y Dược Cần T
|
||||
BS.CKII BÙI THỊ BÍCH,Phó khoa,nữ,20190924,,,,1,4571,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác Sĩ Chuyên Khoa I chuyên ngành Chẩn đoán hình ảnh năm 2011 tại Trường Đại Học Y Dược TP. Hồ Chí Minh Tốt nghiệp Bác Sĩ Chuyên Khoa II chuyên ngành
|
||||
ThS.BS NGUYỄN MINH NGUYỆT,Nhân viên,nam,20200714,,,,0,4581,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác sĩ đa khoa năm 2007 tại Trường Đại Học Y Dược TP. Cần Thơ Tốt nghiệp Thạc sĩ Nội tổng quát năm 2014 tại Trường Đại Học Y Dược TP. Hồ Chí Minh Ch
|
||||
BS.CKII CHÂU THỊ THÚY LIỄU,Trưởng khoa,nữ,19800101,2,aa@gmail.com,,1,5453,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác sĩ đa khoa năm 1998 tại Trường Đại học Cần Thơ Tốt nghiệp Bác Sĩ Chuyên Khoa I chuyên ngành Nội thần kinh năm 2006 tại Trường Đại Học Y Dược TP. H
|
||||
BS NGUYỄN ĐÀO NHẬT HUY,Nhân viên,nam,20200724,,,,0,4561,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Y đa khoa năm 2012 tại trường Đại học Cần Thơ Chứng nhận Kiến tập Tim mạch Can thiệp năm 2013 tại Bệnh viện Chợ Rẫy Chứng nhận Siêu âm Tim năm 2019
|
||||
ThS.BS TRẦN MINH LUẬN,Nhân viên,nam,20200724,,,,0,3153,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Y đa khoa năm 2010 tại trường Đại học Cần Thơ Tốt nghiệp Thạc sĩ Thần kinh - Tâm thần năm 2016 tại trường Đại học Y Dược TP.HCM Chứng nhận Chup - C
|
||||
BS. TÔ THANH ỬNG,Nhân viên,nam,20200724,,,,0,4576,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Y đa khoa năm 2012 tại trường Đại học Y Dược Cần Thơ Chứng nhận Siêu âm tổng quát năm 2014 tại trường Đại học Y Dược Cần Thơ Chứng nhận Nội soi Phế
|
||||
ThS.BS NGUYỄN LƯU GIANG,Trưởng khoa,nam,20200724,,,,0,4580,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Y đa khoa năm 2011 tại trường Đại học Y Dược Cần Thơ Tốt nghiệp Thạc sĩ Y học năm 2016 tại trường Đại học Y Dược Cần Thơ Chứng chỉ Định hướng chẩn
|
||||
ThS.BS NGUYỄN MẠNH CƯỜNG,Trưởng khoa,nam,20200724,,,,0,4562,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Bác sĩ đa khoa năm 2011 trường Đại học Y dược Cần Thơ Tốt nghiệp Thạc Sĩ Y học chuyên ngành Nội khoa năm 2016 tại trường Đại học Y dược Cần Thơ Giấ
|
||||
ThS.BS TRƯƠNG PHẠM VĨNH LỄ,Trưởng phòng,nam,19800101,,,,3,4579,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Y đa khoa năm 2011 tại trường Đại học Cần Thơ Tốt nghiệp Thạc sĩ Thần kinh năm 2016 tại trường Đại học Y Dược Tp.HCM Chứng nhận Siêu âm tổng quát n
|
||||
ThS.BS TRẦM THỊ KIM SA,Nhân viên,nam,20210705,,,,0,4578,,Giới tính: Nữ Quá trình đào tạo: ● Tốt nghiệp Y đa khoa năm 2000 tại trường Đại học Cần Thơ ● Tốt nghiệp Thạc sĩ Nội khoa năm 2007 tại trường Đại học Y Dược Cần Thơ ● Chứng nhận Đào tạo Hồi sức cấp
|
||||
BS. TRẦN TIẾN THÀNH,Nhân viên,nam,20210705,,,,0,4623,,Giới tính: Nam Quá trình đào tạo: 2010 - 2016: Bác sĩ Đa Khoa Đại Học Y Dược Cần Thơ 2019 - 2021: Đào tạo BS.CKI Nội Thần Kinh - Đại Học Y Dược Tp HCM. Chứng nhận cập nhật chẩn đoán điều trị
|
||||
BS. DƯƠNG HOÀNG LINH,Nhân viên,nam,20210705,,,,0,4575,,"Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Đại học Y Dược Cần Thơ năm 2017 Kinh nghiệm làm việc: 7/2017-9/2020: BS Khoa hồi sức cấp cứu - chống độc Trung tâm y tế huyện Gò Quao, tỉnh Kiên Gi"
|
||||
BS.CKI PHAN THỊ THU,Nhân viên,nam,20210706,,,,0,,,Giới tính: Nữ Quá trình đào tạo: 1997 : Tốt nghiệp Đại Học Y Dược TP Hồ Chí Minh 2008 : Tốt nghiệp CKI Nội Thần Kinh Đại Học Y Dược TP. Hồ Chí Minh Kinh nghiệm làm việc: Bác sĩ Khoa Nội tổng
|
||||
ThS.BS NGUYỄN HẢI NGUYÊN,Nhân viên,nam,20210705,,,,0,4565,,Giới tính: Nam Quá trình đào tạo: 2012 Tốt nghiệp Bác sĩ Đa Khoa Đại Học Y Dược Cần Thơ Kinh nghiệm làm việc: 2012-2015 Bác sĩ nội khoa Bệnh viện Đa Học Y Dược Cần Thơ 2015-2018 Bác sĩ nội k
|
||||
ThS.BS LƯU TRÚC PHƯƠNG,Phó khoa,nam,20210706,0358032332,drphuongluu@dotquy.vn,,1,9459,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác sĩ đa khoa năm 2010 tại Trường Đại học Y dược Cần Thơ Tốt nghiệp Thạc sĩ Y học chuyên ngành Nội khoa năm 2018 tại Trường Đại học Y dược Thành phố
|
||||
BS.CKI DANH THỊ THOA,Nhân viên,nam,20210705,,,,0,4713,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác sĩ Đa khoa năm 2013 tại Trường Đại học Y Dược TP Hồ Chí Minh Tốt nghiệp Chuyên khoa cấp 1 chuyên ngành Nội khoa năm 2018 tại Trường Đại học Y Dược
|
||||
ThS.BS TRẦN PHI LONG,Nhân viên,nam,20210705,,,,0,4564,,Giới tính: Nam Quá trình đào tạo: 2006 - 2012: Bác Sĩ Đa Khoa - Đại Học Y Khoa Phạm Ngọc Thạch 2014 - 2017: Tốt Nghiệp Thạc Sỹ - Đại Học Y Dược Tp HCM. 2018 : Chứng chỉ siêu âm tim - Bệnh viện
|
||||
BS.CKI NGUYỄN QUANG HƯNG,Nhân viên,nam,20210705,,,,0,4862,,"Giới tính: Nam Quá trình đào tạo: 2006 - 2012: Đại Học Y Dược Cần Thơ 2016 - 2018: Đào tạo sau Đại học CK1 Ngoại Thần Kinh – Cột sống, Đại Học Y Dược Tp HCM. Kinh nghiệm làm việc: 2012 - 201"
|
||||
BS. HUỲNH QUỐC TOÀN,Nhân viên,nam,20210705,,,,0,4573,,Giới tính: Nam Quá trình đào tạo: 2012 - 2018: Đại Học Y Khoa - Trường Đại Học Võ Trường Toản Kinh nghiệm làm việc: 2018 - 2020: Khoa Cấp Cứu - Bệnh viện Đa Khoa Trung uơng Cần Thơ Chức vụ (
|
||||
BS. NGUYỄN THANH LIỆT,Nhân viên,nam,20210705,,,,0,,,Giới tính: Nam Quá trình đào tạo: Kinh nghiệm làm việc: Chức vụ (Công việc) tại Bệnh Viện Đa Khoa Quốc Tế S.I.S Cần Thơ: Bác sĩ điều trị
|
||||
BS. CKI NGUYỄN HỮU VỊNH,Phó khoa,nam,20210705,,,,0,4572,,Giới tính: Nam Quá trình đào tạo: 2008 - 2014: Đại Học Y Dược Cần Thơ 2018 - 2020: Tốt nghiệp Bác sĩ CKI - Đại Học Y Dược Cần Thơ Kinh nghiệm làm việc: 2014 - 2017: Khoa Cấp Cứu - Bệnh viện
|
||||
BS.CKII NGÔ MINH TUẤN,Trưởng khoa,nam,20221101,,,,0,5330,,Giới tính: Nam Quá trình đào tạo: Chứng chỉ DSA can thiệp mạch tổng quát tại Bệnh viện Chợ Rẫy năm 2008. Chứng chỉ Chụp và can thiệp mạch máu Thần Kinh tại Đại học Y khoa Tp. Hồ Chí Minh năm 200
|
||||
BS.CKI LƯU THỊ PHƯƠNG,Trưởng khoa,nam,20221101,,,,0,9214,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác sĩ đa khoa năm 2004 tại Trường Đại học Y dược Cần Thơ Tốt nghiệp Bác sĩ Chuyên khoa Cấp 1 chuyên nghành Nội tổng quát năm 2009 tại Trường Đại học
|
||||
TS. BS. TRẦN THỊ CẨM NHUNG,Trưởng khoa,nam,19840529,0902951984,,,0,9458,,"Giới tính: Nữ Quá trình đạo tạo: Tốt nghiệp Bs Đa Khoa, tại Trường Đại Học Y Dược Cần Thơ, năm 2008 Tốt nghiệp Thạc sĩ chuyên ngành Gây mê hồi sức, tại Đại Học Y Dược TP Hồ Chí Minh, năm 2"
|
||||
BS. CKII PHƯƠNG HỒNG THỌ,Phó khoa,nam,19900101,,,,3,9547,,Giới tính: Nam Quá trình đạo tạo: Tốt nghiệp Bác sĩ Đa khoa năm 1999 tại trường Đại Học Cần Thơ Tốt nghiệp Bác sĩ CKI chuyên ngành Thần kinh năm 2015 tại Trường Đại Học Y Dược TP.HCM Tốt nghiệ
|
||||
THS.BS.CKI NGUYỄN KIM PHỤNG,Phó khoa,nam,19900101,,,,1,9542,,Giới tính: Nữ Quá trình đạo tạo: Tốt nghiệp Bác sĩ Đa khoa năm 2015 tại trường Đại học Y Dược Cần Thơ Tốt nghiệp Bác sĩ nội trú năm 2018 tại trường Đại học Y Dược Cần Thơ Chứng chỉ thực hành h
|
||||
CNĐD. NGUYỄN THỊ THÚY QUYÊN,Điều dưỡng trưởng,nam,19900101,,,,4,9546,,Giới tính: Nữ Quá trình đào tạo: Bổ túc kiến thức về Điều dưỡng Hồi sức cấp cứu Bệnh viện Chợ Rẫy. Quản lý bệnh viện (Đại học Y dược Cần Thơ) Quản lý điều dưỡng (Đại học Y dược Cần Thơ) Phươn
|
||||
CN. NGUYỄN THANH HỒNG,Điều dưỡng trưởng,nữ,19800101,,,,4,9572,,Giới tính: Nữ Quá trình đào tạo: 2014: Tốt nghiệp Cao đẳng Điều dưỡng trường Cao đẳng Y tế Cần Thơ. 2024: Tốt nghiệp Cử nhân Điều dưỡng trường Đại Học Cửu Long. Chứng nhận khóa học “Quy
|
||||
BS. Lê Xuân Phượng,Trưởng khoa,nữ,19800101,,,,0,12300,,Quá trình đào tạo: Tốt nghiệp Bác sĩ Đa khoa tại trường Đại học Y dược Cần Thơ Chứng nhận chuyên khoa Vật lý trị liệu của Bệnh viện Chợ Rẫy Chứng chỉ Âm ngữ trị liệu của Bệnh viện Chợ Rẫy
|
||||
BS. Nguyễn Hữu Thơ,Nhân viên,nam,19800101,,,,0,,,Chứng chỉ định hướng Phục hồi chức năng của Trường Đại học Y dược Cần Thơ Chứng chỉ Âm ngữ trị liệu của Trường Đại học Y dược Cần Thơ
|
||||
BS.CKI Trương Đan Kha,Phó khoa,nam,19800101,,,,3,12299,,Tốt nghiệp Bác sĩ Chuyên khoa I chuyên ngành Phục hồi chức năng tại trường Đại học Y dược Cần Thơ. Chứng chỉ định hướng Phục hồi chức năng của Bệnh viện Phục hồi chức năng và điều trị bệnh nghề nghi
|
||||
Trần Công Minh,Trưởng khoa,nam,19800101,0908999793,,,0,,,Giới tính: Nam Quá trình đạo tạo: Chuyên khoa I xét nghiệm Hơn 10 năm kinh nghiệm làm việc trong Phòng xét nghiệm
|
||||
Trần Thị Diễm Trang,Điều dưỡng trưởng,nam,19800101,0383784374,,,0,,,Giới tính: Nam Quá trình đạo tạo: Cử nhân xét nghiệm Hơn 10 năm kinh nghiệm làm việc tại Phòng xét nghiệm
|
||||
THS.QLBV NGUYỄN VĂN TRUNG,Trưởng phòng,nam,19800101,,,,0,9666,,Giới tính: Nam Quá trình đạo tạo: Tốt nghiệp Thạc sĩ Quản lý bệnh viện tại Trường Đại học Y tế Công cộng Hà Nội năm 2017. Chứng chỉ Quản lý nhà nước tại Trường chính trị tỉnh Đồng Tháp năm 2010.
|
||||
BS.CK1 Nguyễn Nhựt Thái,Phó khoa,nam,19800101,,,,1,12894,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Bác sĩ đa khoa năm 2003 tại Trường Đại học Y dược Thành Phố Hồ Chí Minh. Tốt nghiệp Chuyên Khoa 1 chuyên ngành Ngoại Thần Kinh năm 2008 tại Trườn
|
||||
THS.BS Nguyễn Anh Trung,Phó khoa,nam,19800101,,,,3,10261,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Đại học Y dược Cần Thơ . Chứng chỉ đào tạo phẫu thuật thay khớp tại Trường Đại học Y dược Thành phố Hồ Chí Minh Chứng chỉ đào tạo phẫu thuật
|
||||
BS.CK1Nguyễn Quang Hưng,Nhân viên,nam,19800101,,,,4,4862,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Bác sĩ đa khoa năm 2012 tại Trường Đại học Y dược Cần Thơ. Tốt nghiệp Chuyên Khoa 1 chuyên ngành Ngoại Thần Kinh năm 2018 tại Trường Đại học Y dư
|
||||
KTV. Hồ Ngọc Diễm,Điều dưỡng trưởng,nữ,19900101,,,,4,12301,,Quá trình đào tạo: Chứng chỉ đào tạo liên tục: Quản lý điều dưỡng năm 2021 Chứng nhận Các tác nhân vật lý trong thực hành lâm sàng nâng cao của Bệnh viện Chợ Rẫy. Chứng chỉ ngoại ngữ tiếng Anh
|
||||
Ths MAI TRUNG KIÊN,Phó phòng,nam,19800101,,,,0,10284,,Giới tính: Nam Quá trình đạo tạo: Tốt nghiệp Kỹ sư Tin học năm 2011 Tốt nghiệp Thạc sĩ Quản lý Kinh tế năm 2022 Kinh nghiệm làm việc: 2011 – 2014: Phòng IT – Công ty Cổ phần Mía đường
|
||||
ĐD. Trần Việt Khanh,Điều dưỡng trưởng,nam,19800101,,,,0,,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Cử nhân Điều dưỡng đa khoa năm 2011 tại Trường Đại Học Y Dược Cần Thơ Tốt nghiệp Cử nhân Ngôn ngữ Anh năm 2016 tại Trường Đại Học Cần Thơ Chứ
|
||||
THS.BS NGUYỄN NGỌC ANH THƯ,Phó khoa,nữ,19800101,,,,0,11310,,Giới tính: Nữ Qúa trình đào tạo: Tốt nghiệp Bác sĩ Đa khoa Trường Đại Học Y Dược Cần Thơ- năm 2011 Đào tạo liên tục chuyên ngành Nội tim mạch- Bệnh viện Chợ Rẫy năm 2012 Tốt nghiệp Cao học nộ
|
||||
TS.BS Trần Chí Cường,Trưởng khoa,nam,19770101,,,,0,12899,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Đại học Y khoa năm 2000 tại trường Đại học Y Dược Cần Thơ Tốt nghiệp Bác sĩ Chuyên khoa 1 chuyên ngành Phẫu thuật Thần kinh (Ngoại Thần kinh) năm 200
|
||||
BÁC SĨ NỘI TRÚ- CHUYÊN KHOA 1 TRƯƠNG LÊ ANH KIỆT,Nhân viên,nam,19800101,,,,3,13612,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Bác sĩ Y khoa loại giỏi Trường Đại học Y dược Cần Thơ. Tốt nghiệp Bác sĩ nội trú- Chuyên khoa 1 Tai Mũi Họng bằng Xuất sắc Trường Đại học Y dược
|
||||
BS.CKI. Võ Văn Năm,Nhân viên,nam,19800101,,,,4,13613,,Giới tính: Nam Quá trình đào tạo: Tốt nghiệp Bác sĩ Y khoa tại Trường Đại học Y Dược Cần Thơ năm 2020 Tốt nghiệp Bác sĩ nội trú- Chuyên Khoa 1 Chuyên ngành Tai Mũi Họng tại Trường Đại học Y
|
||||
BS.CKI. Huỳnh Thị Nhật Bình,Nhân viên,nữ,19800101,,,,5,13616,,Giới tính: Nữ Quá trình đào tạo: Tốt nghiệp Bác sĩ Y khoa tại Trường Đại học Y Dược Cần Thơ năm 2021 Tốt nghiệp Bác sĩ nội trú- Chuyên Khoa 1 Chuyên ngành Tai Mũi Họng tại Trường Đại học Y
|
||||
|
@@ -0,0 +1,17 @@
|
||||
Title: Th.S HOÀNG KIM NGA
|
||||
image = 5394
|
||||
gender = nữ
|
||||
position = Phó giám đốc tài chính
|
||||
birthday = 20181003
|
||||
phone_number = 123
|
||||
email = bbb@gmail.com
|
||||
address = fdfdfdfd
|
||||
order = 2
|
||||
tg_post_views_count = 897
|
||||
type = 0
|
||||
sequence = 0
|
||||
<strong>Giới tính: </strong>Nữ
|
||||
|
||||
<strong>Quá trình đào tạo:</strong>
|
||||
<ul>
|
||||
<li>Tốt nghiệp Thạc sĩ Tà
|
||||
@@ -0,0 +1,55 @@
|
||||
import csv
|
||||
import sys
|
||||
|
||||
csv_file = '/home/x79/sisvietnamvn_01/Media/HÌNH ẢNH BÁC SĨ NGỒI PHÒNG KHÁM/ALL_IMAGES/DANH SÁCH BÁC SĨ NGỒI PHÒNG KHÁM.csv'
|
||||
|
||||
sql_statements = []
|
||||
|
||||
try:
|
||||
with open(csv_file, 'r', encoding='utf-8-sig') as f:
|
||||
reader = csv.DictReader(f)
|
||||
specialties = set()
|
||||
doctor_mappings = []
|
||||
for row in reader:
|
||||
doctor_code = row.get('MSNV', '').strip()
|
||||
specialty_name = row.get('Khoa / phòng', '').strip()
|
||||
if doctor_code and specialty_name:
|
||||
specialties.add(specialty_name)
|
||||
doctor_mappings.append((doctor_code, specialty_name))
|
||||
|
||||
# We will assume there's a sequence sis_specialty_seq or we just use IDs starting from 1000
|
||||
# Wait, the best way in Oracle is to use MERGE or just PL/SQL block
|
||||
|
||||
plsql = "BEGIN\n"
|
||||
for spec in specialties:
|
||||
safe_spec = spec.replace("'", "''")
|
||||
plsql += f"""
|
||||
DECLARE
|
||||
v_count NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO v_count FROM sis_specialty WHERE name = '{safe_spec}';
|
||||
IF v_count = 0 THEN
|
||||
INSERT INTO sis_specialty (id, name, active, created_by, created_date, last_modified_by, last_modified_date)
|
||||
VALUES (sequenceGenerator.NEXTVAL, '{safe_spec}', 1, 'system', sysdate, 'system', sysdate);
|
||||
END IF;
|
||||
END;
|
||||
"""
|
||||
|
||||
for doc_code, spec in doctor_mappings:
|
||||
safe_spec = spec.replace("'", "''")
|
||||
safe_code = doc_code.replace("'", "''")
|
||||
plsql += f"""
|
||||
UPDATE sis_doctor
|
||||
SET specialty_id = (SELECT id FROM sis_specialty WHERE name = '{safe_spec}' FETCH FIRST 1 ROWS ONLY)
|
||||
WHERE doctor_code = '{safe_code}';
|
||||
"""
|
||||
|
||||
plsql += "COMMIT;\nEND;\n/"
|
||||
|
||||
with open('/home/x79/sisvietnamvn_01/sisvietnamvn_main/src/main/resources/config/liquibase/data/map_specialties.sql', 'w', encoding='utf-8') as out_f:
|
||||
out_f.write(plsql)
|
||||
|
||||
print("Generated map_specialties.sql")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
@@ -0,0 +1,178 @@
|
||||
import csv
|
||||
import xml.etree.ElementTree as ET
|
||||
import sys
|
||||
import os
|
||||
|
||||
csv_file = '/home/x79/sisvietnamvn_01/sisvietnamvn_Trang chính thức hiện tại/DS_BacSi/DANH SÁCH BÁC SĨ NGỒI PHÒNG KHÁM_THÔNG TIN CHI TIẾT_FULL.csv'
|
||||
xml_file = '/home/x79/sisvietnamvn_01/sisvietnamvn_Trang chính thức hiện tại/DS_BacSi/bnhvinakhoaquctsiscnth.WordPress.2026-07-28 (5).xml'
|
||||
|
||||
# Read CSV
|
||||
csv_doctors = {}
|
||||
with open(csv_file, 'r', encoding='utf-8-sig') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
name = row.get('Họ và tên', '').strip()
|
||||
if not name:
|
||||
continue
|
||||
msnv = row.get('MSNV', '').strip()
|
||||
title = row.get('Chức danh', '').strip()
|
||||
degree = row.get('Trình độ chuyên môn', '').strip()
|
||||
specialty = row.get('Khoa/ Phòng', '').strip()
|
||||
education = row.get('Quá trình đào tạo', '').strip()
|
||||
experience = row.get('Quá trình công tác', '').strip()
|
||||
achievements = row.get('Hiệp hội chuyên môn', '').strip() + '\n' + row.get('Ghi chú', '').strip()
|
||||
|
||||
csv_doctors[name.lower()] = {
|
||||
'name': name,
|
||||
'doctor_code': msnv,
|
||||
'title': degree,
|
||||
'position': title,
|
||||
'specialty': specialty,
|
||||
'education': education,
|
||||
'experience': experience,
|
||||
'achievements': achievements,
|
||||
'active': 1 if msnv else 0
|
||||
}
|
||||
|
||||
# Read XML
|
||||
xml_doctors = {}
|
||||
tree = ET.parse(xml_file)
|
||||
root = tree.getroot()
|
||||
ns = {
|
||||
'wp': 'http://wordpress.org/export/1.2/',
|
||||
'content': 'http://purl.org/rss/1.0/modules/content/'
|
||||
}
|
||||
|
||||
for channel in root.findall('channel'):
|
||||
for item in channel.findall('item'):
|
||||
post_type = item.find('wp:post_type', ns)
|
||||
if post_type is None or post_type.text != 'doctor':
|
||||
continue
|
||||
|
||||
title_elem = item.find('title')
|
||||
if title_elem is None or not title_elem.text:
|
||||
continue
|
||||
|
||||
name = title_elem.text.strip()
|
||||
if not name:
|
||||
continue
|
||||
|
||||
doc = {'name': name}
|
||||
|
||||
# Extract department category
|
||||
for cat in item.findall('category'):
|
||||
if cat.get('domain') == 'department':
|
||||
doc['specialty'] = cat.text.strip()
|
||||
break
|
||||
|
||||
for meta in item.findall('wp:postmeta', ns):
|
||||
key = meta.find('wp:meta_key', ns)
|
||||
val = meta.find('wp:meta_value', ns)
|
||||
if key is not None and val is not None and val.text:
|
||||
k = key.text
|
||||
v = val.text.strip()
|
||||
if k == 'doctor_information_position':
|
||||
doc['position'] = v
|
||||
elif k == 'doctor_information_specialty':
|
||||
doc['specialty'] = v
|
||||
elif k == 'doctor_information_experience':
|
||||
doc['experience'] = v
|
||||
elif k == 'doctor_information_education':
|
||||
doc['education'] = v
|
||||
elif k == 'doctor_information_achievements':
|
||||
doc['achievements'] = v
|
||||
elif k == 'doctor_information_gender':
|
||||
doc['gender'] = v
|
||||
elif k == 'doctor_information_phone':
|
||||
doc['phone_number'] = v
|
||||
elif k == 'doctor_information_email':
|
||||
doc['email'] = v
|
||||
elif k == 'doctor_information_avatar':
|
||||
doc['avatar_url'] = v
|
||||
elif k == 'doctor_information_booking':
|
||||
doc['booking_url'] = v
|
||||
elif k == 'doctor_information_title':
|
||||
doc['title'] = v
|
||||
|
||||
xml_doctors[name.lower()] = doc
|
||||
|
||||
# Merge
|
||||
merged_doctors = {}
|
||||
for name_key, xml_doc in xml_doctors.items():
|
||||
merged_doctors[name_key] = xml_doc
|
||||
|
||||
# Priorities: CSV overwrites XML
|
||||
for name_key, csv_doc in csv_doctors.items():
|
||||
# Find matching XML doctor by checking if CSV name is substring of XML name
|
||||
matched_xml_key = None
|
||||
for xml_key in merged_doctors.keys():
|
||||
# Remove spaces and dashes for comparison
|
||||
clean_csv = name_key.replace(' ', '').replace('-', '')
|
||||
clean_xml = xml_key.replace(' ', '').replace('-', '')
|
||||
if clean_csv in clean_xml or clean_xml in clean_csv:
|
||||
matched_xml_key = xml_key
|
||||
break
|
||||
|
||||
if matched_xml_key:
|
||||
existing = merged_doctors[matched_xml_key]
|
||||
for k, v in csv_doc.items():
|
||||
if v and str(v).strip():
|
||||
existing[k] = v
|
||||
# Ensure we use the clean plain name from CSV, not the long XML title
|
||||
existing['name'] = csv_doc['name']
|
||||
else:
|
||||
merged_doctors[name_key] = csv_doc
|
||||
|
||||
# Write SQL
|
||||
plsql = "BEGIN\n"
|
||||
plsql += " DELETE FROM sis_doctor_schedule;\n"
|
||||
plsql += " DELETE FROM sis_doctor;\n"
|
||||
|
||||
for doc in merged_doctors.values():
|
||||
name = doc.get('name', '').replace("'", "''")
|
||||
code = doc.get('doctor_code', '').replace("'", "''")
|
||||
title = doc.get('title', '').replace("'", "''")
|
||||
pos = doc.get('position', '').replace("'", "''")
|
||||
gender = doc.get('gender', '').replace("'", "''")
|
||||
phone = doc.get('phone_number', '').replace("'", "''")
|
||||
email = doc.get('email', '').replace("'", "''")
|
||||
spec = doc.get('specialty', '').replace("'", "''")
|
||||
edu = doc.get('education', '').replace("'", "''")
|
||||
exp = doc.get('experience', '').replace("'", "''")
|
||||
ach = doc.get('achievements', '').replace("'", "''")
|
||||
avt = doc.get('avatar_url', '').replace("'", "''")
|
||||
bk = doc.get('booking_url', '').replace("'", "''")
|
||||
active = doc.get('active', 0)
|
||||
|
||||
plsql += f"""
|
||||
DECLARE
|
||||
v_count NUMBER;
|
||||
v_spec_id NUMBER := NULL;
|
||||
BEGIN
|
||||
IF '{spec}' IS NOT NULL THEN
|
||||
SELECT COUNT(*) INTO v_count FROM sis_specialty WHERE name = '{spec}';
|
||||
IF v_count = 0 THEN
|
||||
INSERT INTO sis_specialty (id, name, slug, created_by, created_date, last_modified_by, last_modified_date)
|
||||
VALUES (sequence_generator.NEXTVAL, '{spec}', '{spec}', 'system', sysdate, 'system', sysdate);
|
||||
END IF;
|
||||
SELECT id INTO v_spec_id FROM sis_specialty WHERE name = '{spec}' FETCH FIRST 1 ROWS ONLY;
|
||||
END IF;
|
||||
|
||||
INSERT INTO sis_doctor (
|
||||
id, name, doctor_code, title, position, gender, phone_number, email,
|
||||
specialty_id, education, work_experience, achievements, avatar_url, booking_url,
|
||||
active, display_order, created_by, created_date, last_modified_by, last_modified_date
|
||||
) VALUES (
|
||||
sequence_generator.NEXTVAL, '{name}', '{code}', '{title}', '{pos}', '{gender}', '{phone}', '{email}',
|
||||
v_spec_id, '{edu}', '{exp}', '{ach}', '{avt}', '{bk}',
|
||||
{active}, 0, 'system', sysdate, 'system', sysdate
|
||||
);
|
||||
END;
|
||||
"""
|
||||
|
||||
plsql += "COMMIT;\nEND;\n/"
|
||||
|
||||
with open('/home/x79/sisvietnamvn_01/sisvietnamvn_main/src/main/resources/config/liquibase/data/merge_doctors.sql', 'w', encoding='utf-8') as f:
|
||||
f.write(plsql)
|
||||
|
||||
print("Generated merge_doctors.sql")
|
||||
@@ -0,0 +1,129 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
import csv
|
||||
import sys
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
try:
|
||||
tree = ET.parse('/home/x79/sisvietnamvn_01/sisvietnamvn_Trang chính thức hiện tại/bnhvinakhoaquctsiscnth.WordPress.2026-07-28 (5).xml')
|
||||
root = tree.getroot()
|
||||
except Exception as e:
|
||||
print(f"Error parsing XML: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
ns = {
|
||||
'wp': 'http://wordpress.org/export/1.2/',
|
||||
'content': 'http://purl.org/rss/1.0/modules/content/'
|
||||
}
|
||||
|
||||
attachments = {}
|
||||
for channel in root.findall('channel'):
|
||||
for item in channel.findall('item'):
|
||||
pt = item.find('wp:post_type', ns)
|
||||
if pt is not None and pt.text == 'attachment':
|
||||
post_id = item.find('wp:post_id', ns)
|
||||
url = item.find('wp:attachment_url', ns)
|
||||
if post_id is not None and url is not None:
|
||||
attachments[post_id.text] = url.text
|
||||
|
||||
csv_mapping = {}
|
||||
with open('/home/x79/sisvietnamvn_01/Media/HÌNH ẢNH BÁC SĨ NGỒI PHÒNG KHÁM/ALL_IMAGES/DANH SÁCH BÁC SĨ NGỒI PHÒNG KHÁM.csv', 'r', encoding='utf-8-sig') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
name = row.get('Họ và tên', '').strip()
|
||||
msnv = row.get('MSNV', '').strip()
|
||||
if name:
|
||||
csv_mapping[name.lower()] = msnv
|
||||
|
||||
def escape_sql(text):
|
||||
if not text:
|
||||
return "NULL"
|
||||
return "'" + text.replace("'", "''") + "'"
|
||||
|
||||
def parse_html_content(html):
|
||||
if not html:
|
||||
return None, None, None
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
|
||||
education = ""
|
||||
experience = ""
|
||||
achievements = ""
|
||||
|
||||
current_section = None
|
||||
|
||||
# We will just split it simply for now, but a more robust way:
|
||||
# Just save the entire raw HTML into work_experience if we can't easily parse
|
||||
return escape_sql(html), "NULL", "NULL"
|
||||
|
||||
sql_statements = []
|
||||
# TRUNCATE TABLE sis_doctor CASCADE? We should probably just insert.
|
||||
# But there might be old data. Let's just generate INSERTs.
|
||||
|
||||
doctor_id = 1000
|
||||
|
||||
for channel in root.findall('channel'):
|
||||
for item in channel.findall('item'):
|
||||
pt = item.find('wp:post_type', ns)
|
||||
if pt is not None and pt.text == 'employee':
|
||||
title_el = item.find('title')
|
||||
raw_title = title_el.text.strip() if title_el is not None and title_el.text else ""
|
||||
|
||||
# Extract name and title (e.g. "TS.BS TRẦN CHÍ CƯỜNG" -> title="TS.BS", name="TRẦN CHÍ CƯỜNG")
|
||||
parts = raw_title.split(' ', 1)
|
||||
doctor_title = parts[0] if '.' in parts[0] else ''
|
||||
doctor_name = parts[1] if doctor_title and len(parts) > 1 else raw_title
|
||||
if not doctor_title:
|
||||
doctor_name = raw_title
|
||||
|
||||
doctor_name_lower = doctor_name.lower()
|
||||
|
||||
msnv = ""
|
||||
for csv_name, code in csv_mapping.items():
|
||||
if csv_name in doctor_name_lower or doctor_name_lower in csv_name:
|
||||
msnv = code
|
||||
break
|
||||
|
||||
meta_dict = {}
|
||||
for meta in item.findall('wp:postmeta', ns):
|
||||
k = meta.find('wp:meta_key', ns)
|
||||
v = meta.find('wp:meta_value', ns)
|
||||
if k is not None and k.text and v is not None:
|
||||
meta_dict[k.text] = v.text or ""
|
||||
|
||||
position = meta_dict.get('position', '')
|
||||
gender = meta_dict.get('gender', '')
|
||||
birthday = meta_dict.get('birthday', '')
|
||||
phone = meta_dict.get('phone_number', '')
|
||||
email = meta_dict.get('email', '')
|
||||
address = meta_dict.get('address', '')
|
||||
order = meta_dict.get('order', '0')
|
||||
image_id = meta_dict.get('image', '')
|
||||
avatar_url = attachments.get(image_id, '')
|
||||
|
||||
content_el = item.find('content:encoded', ns)
|
||||
html_content = content_el.text if content_el is not None else ""
|
||||
exp, edu, ach = parse_html_content(html_content)
|
||||
|
||||
if not order.isdigit():
|
||||
order = '0'
|
||||
|
||||
# INSERT statement
|
||||
# Use sequenceGenerator.nextval or just let JPA handle it? Since we insert via SQL, we use sequence_generator.nextval
|
||||
sql = f"""
|
||||
INSERT INTO sis_doctor (
|
||||
id, name, title, avatar_url, doctor_code, active,
|
||||
position, gender, birthday, phone_number, email, address, display_order,
|
||||
work_experience, created_by, created_date, last_modified_by, last_modified_date
|
||||
) VALUES (
|
||||
{doctor_id}, {escape_sql(doctor_name)}, {escape_sql(doctor_title)}, {escape_sql(avatar_url)}, {escape_sql(msnv)}, 1,
|
||||
{escape_sql(position)}, {escape_sql(gender)}, {escape_sql(birthday)}, {escape_sql(phone)}, {escape_sql(email)}, {escape_sql(address)}, {order},
|
||||
{exp}, 'system', sysdate, 'system', sysdate
|
||||
);"""
|
||||
sql_statements.append(sql.strip())
|
||||
doctor_id += 1
|
||||
|
||||
with open('/home/x79/sisvietnamvn_01/sisvietnamvn_main/import_doctors.sql', 'w', encoding='utf-8') as f:
|
||||
f.write("DELETE FROM sis_doctor;\n")
|
||||
f.write("\n".join(sql_statements))
|
||||
f.write("\nCOMMIT;\n")
|
||||
|
||||
print("Generated import_doctors.sql")
|
||||
@@ -0,0 +1,25 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
tree = ET.parse('/home/x79/sisvietnamvn_01/sisvietnamvn_Trang chính thức hiện tại/bnhvinakhoaquctsiscnth.WordPress.2026-07-28 (5).xml')
|
||||
root = tree.getroot()
|
||||
|
||||
ns = {
|
||||
'wp': 'http://wordpress.org/export/1.2/',
|
||||
'content': 'http://purl.org/rss/1.0/modules/content/',
|
||||
'dc': 'http://purl.org/dc/elements/1.1/'
|
||||
}
|
||||
|
||||
for channel in root.findall('channel'):
|
||||
for item in channel.findall('item'):
|
||||
post_type = item.find('wp:post_type', ns)
|
||||
if post_type is None or post_type.text != 'doctor':
|
||||
continue
|
||||
print('Title:', item.find('title').text)
|
||||
print('Post Type:', post_type.text)
|
||||
|
||||
for meta in item.findall('wp:postmeta', ns):
|
||||
key = meta.find('wp:meta_key', ns)
|
||||
val = meta.find('wp:meta_value', ns)
|
||||
if key is not None and val is not None and key.text and not key.text.startswith('_'):
|
||||
print(f' Meta: {key.text} = {str(val.text)[:100]}')
|
||||
break
|
||||
@@ -0,0 +1 @@
|
||||
Post types: {'employee'}
|
||||
@@ -56,7 +56,7 @@ public class SecurityConfiguration {
|
||||
"/manage/login", "/css/**", "/images/**", "/js/**", "/vendor/**", "/fonts/**", "/login-assets/**", "/UMass*/**", "/Undergraduate*/**",
|
||||
"/favicon.ico", "/favicons/**", "/flex-finish/**", "/contact-us/**", "/uploads/**", "/upload/**", "/api/manage/snippets/**", "/page/**", "/news/article/**", "/post/**", "/error",
|
||||
"/about-us", "/specialty", "/doctor", "/bac-si/**", "/service", "/health-library", "/news-and-events", "/patient-support", "/medical-expert", "/umcers", "/bidding", "/contact-us", "/theme-assets/**", "/dao-tao/**", "/chuyen-khoa", "/chuyen-khoa/**",
|
||||
"/Đào tạo tại UMC_files/**")
|
||||
"/Đào tạo tại UMC_files/**", "/dat-lich", "/dat-lich/**", "/api/public/doctor-schedule/**")
|
||||
.permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/api/manage/media/upload").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||
|
||||
@@ -267,9 +267,9 @@ public class PageController {
|
||||
java.util.List<com.sisvietnamvn.web.domain.Doctor> doctors = doctorService.findAll();
|
||||
|
||||
// Show all doctors regardless of active status (so inactive still show up)
|
||||
// doctors = doctors.stream()
|
||||
// .filter(d -> Boolean.TRUE.equals(d.getActive()))
|
||||
// .collect(java.util.stream.Collectors.toList());
|
||||
doctors = doctors.stream()
|
||||
.filter(d -> Boolean.TRUE.equals(d.getActive()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
// Sort: 00001 and 00002 at the top
|
||||
doctors.sort((d1, d2) -> {
|
||||
@@ -411,6 +411,18 @@ public class PageController {
|
||||
|
||||
return "page";
|
||||
}
|
||||
@GetMapping("/dat-lich")
|
||||
public String getDatLichPage(Model model) {
|
||||
model.addAttribute("activeTheme", "umass");
|
||||
model.addAttribute("cachedDoctors", doctorApiSyncService.getCachedDoctors());
|
||||
return "pages/dat-lich";
|
||||
}
|
||||
|
||||
@GetMapping("/api/public/doctor-schedule/{doctorCode}")
|
||||
@org.springframework.web.bind.annotation.ResponseBody
|
||||
public org.springframework.http.ResponseEntity<List<Map<String, Object>>> getPublicSchedule(@PathVariable String doctorCode) {
|
||||
return getManualSchedule(doctorCode);
|
||||
}
|
||||
|
||||
@GetMapping("/manage/doctor-scheduler")
|
||||
public String getQuanLyLichKham(Model model) {
|
||||
|
||||
@@ -45,47 +45,6 @@ public class ManageDoctorController {
|
||||
populateModel(model, new Doctor(), true);
|
||||
return "manage/doctors/index";
|
||||
}
|
||||
|
||||
@GetMapping("/import-csv")
|
||||
@ResponseBody
|
||||
public String importCsv() {
|
||||
try {
|
||||
String file = "/home/x79/sisvietnamvn_01/Media/HÌNH ẢNH BÁC SĨ NGỒI PHÒNG KHÁM/ALL_IMAGES/DANH SÁCH BÁC SĨ NGỒI PHÒNG KHÁM.csv";
|
||||
java.util.List<String> lines = java.nio.file.Files.readAllLines(java.nio.file.Paths.get(file));
|
||||
int updatedCount = 0;
|
||||
for (int i = 1; i < lines.size(); i++) {
|
||||
String line = lines.get(i);
|
||||
String[] parts = line.split(",", -1);
|
||||
if (parts.length >= 7) {
|
||||
String doctorCode = parts[1].trim();
|
||||
String specialtyName = parts[6].trim();
|
||||
|
||||
if (!doctorCode.isEmpty() && !specialtyName.isEmpty()) {
|
||||
java.util.Optional<Doctor> docOpt = doctorService.findByDoctorCode(doctorCode);
|
||||
if (docOpt.isPresent()) {
|
||||
Doctor doctor = docOpt.get();
|
||||
|
||||
// Find or create specialty
|
||||
java.util.List<com.sisvietnamvn.web.domain.Specialty> specs = specialtyService.findAll();
|
||||
com.sisvietnamvn.web.domain.Specialty spec = specs.stream().filter(s -> s.getName().equalsIgnoreCase(specialtyName)).findFirst().orElse(null);
|
||||
if (spec == null) {
|
||||
spec = new com.sisvietnamvn.web.domain.Specialty();
|
||||
spec.setName(specialtyName);
|
||||
spec = specialtyService.save(spec);
|
||||
}
|
||||
|
||||
doctor.setSpecialty(spec);
|
||||
doctorService.save(doctor);
|
||||
updatedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "Import success! Updated " + updatedCount + " doctors.";
|
||||
} catch (Exception e) {
|
||||
return "Error: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public String createDoctor(@Valid @ModelAttribute("doctor") Doctor doctor,
|
||||
|
||||
@@ -60,6 +60,33 @@ public class Doctor extends AbstractAuditingEntity<Long> {
|
||||
@Column(name = "active")
|
||||
private Boolean active = true;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "position", length = 255)
|
||||
private String position;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "gender", length = 20)
|
||||
private String gender;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "birthday", length = 20)
|
||||
private String birthday;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "phone_number", length = 50)
|
||||
private String phoneNumber;
|
||||
|
||||
@Size(max = 100)
|
||||
@Column(name = "email", length = 100)
|
||||
private String email;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "address", length = 255)
|
||||
private String address;
|
||||
|
||||
@Column(name = "display_order")
|
||||
private Integer displayOrder;
|
||||
|
||||
// jhipster-needle-entity-add-field - JHipster will add fields here
|
||||
|
||||
// --- Getters and Setters ---
|
||||
@@ -153,6 +180,62 @@ public class Doctor extends AbstractAuditingEntity<Long> {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public String getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(String position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public String getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(String gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public String getBirthday() {
|
||||
return birthday;
|
||||
}
|
||||
|
||||
public void setBirthday(String birthday) {
|
||||
this.birthday = birthday;
|
||||
}
|
||||
|
||||
public String getPhoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public void setPhoneNumber(String phoneNumber) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Integer getDisplayOrder() {
|
||||
return displayOrder;
|
||||
}
|
||||
|
||||
public void setDisplayOrder(Integer displayOrder) {
|
||||
this.displayOrder = displayOrder;
|
||||
}
|
||||
|
||||
// --- End Getters and Setters ---
|
||||
|
||||
// --- equals, hashCode, toString ---
|
||||
@@ -178,6 +261,9 @@ public class Doctor extends AbstractAuditingEntity<Long> {
|
||||
", avatarUrl='" + getAvatarUrl() + "'" +
|
||||
", bookingUrl='" + getBookingUrl() + "'" +
|
||||
", doctorCode='" + getDoctorCode() + "'" +
|
||||
", position='" + getPosition() + "'" +
|
||||
", gender='" + getGender() + "'" +
|
||||
", email='" + getEmail() + "'" +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
package com.sisvietnamvn.web.domain;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.io.Serial;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.Lob;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.SequenceGenerator;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* A Post entity representing a blog/news article.
|
||||
* Maps to the "sis_post" database table.
|
||||
@@ -227,4 +241,9 @@ public class Post extends AbstractAuditingEntity<Long> {
|
||||
", status='" + getStatus() + "'" +
|
||||
"}";
|
||||
}
|
||||
|
||||
@jakarta.persistence.Transient
|
||||
public String getImageUrl() {
|
||||
return this.featuredImage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,44 +131,72 @@ public class SwiperSliderPlugin {
|
||||
private Optional<SwiperSliderAdminController.SliderGroup> findGroupOrSeed(String slug) {
|
||||
String json = settingService.getValue(SETTING_KEY, "[]");
|
||||
List<SwiperSliderAdminController.SliderGroup> groups = parseGroups(json);
|
||||
final String searchSlug = ("dao-tao-01".equals(slug)) ? "gallery" : slug;
|
||||
Optional<SwiperSliderAdminController.SliderGroup> groupOpt = groups.stream()
|
||||
.filter(g -> slug.equals(g.slug()))
|
||||
.filter(g -> slug.equals(g.slug()) || searchSlug.equals(g.slug()))
|
||||
.findFirst();
|
||||
|
||||
// If UMC_files is present, we replace all data with defaults (only happens once during migration)
|
||||
if (json.contains("UMC_files")) {
|
||||
// If UMC_files is present or slug not found, re-seed full data
|
||||
if (groupOpt.isEmpty() || json.contains("UMC_files")) {
|
||||
json = forceSeedAllData();
|
||||
groups = parseGroups(json);
|
||||
groupOpt = groups.stream()
|
||||
.filter(g -> slug.equals(g.slug()))
|
||||
.filter(g -> slug.equals(g.slug()) || searchSlug.equals(g.slug()))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
return groupOpt;
|
||||
}
|
||||
|
||||
private String forceSeedAllData() {
|
||||
String sampleData = "[{\"slug\":\"gallery\",\"name\":\"Gallery Đào Tạo\",\"items\":[" +
|
||||
private static final String DEFAULT_SLIDER_DATA = "[" +
|
||||
"{\"slug\":\"dao-tao-01\",\"name\":\"Gallery Đào Tạo\",\"items\":[" +
|
||||
"{\"title\":\"Khóa đào tạo 1\",\"imageUrl\":\"/images/dao-tao/image-6.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 2\",\"imageUrl\":\"/images/dao-tao/image-5.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 3\",\"imageUrl\":\"/images/dao-tao/image-4.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"{\"title\":\"Khóa đào tạo 3\",\"imageUrl\":\"/images/dao-tao/image-4.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 4\",\"imageUrl\":\"/images/dao-tao/image-3.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 5\",\"imageUrl\":\"/images/dao-tao/image-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 6\",\"imageUrl\":\"/images/dao-tao/image-1.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"]},{\"slug\":\"gallery\",\"name\":\"Gallery Đào Tạo\",\"items\":[" +
|
||||
"{\"title\":\"Khóa đào tạo 1\",\"imageUrl\":\"/images/dao-tao/image-6.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 2\",\"imageUrl\":\"/images/dao-tao/image-5.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 3\",\"imageUrl\":\"/images/dao-tao/image-4.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 4\",\"imageUrl\":\"/images/dao-tao/image-3.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 5\",\"imageUrl\":\"/images/dao-tao/image-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Khóa đào tạo 6\",\"imageUrl\":\"/images/dao-tao/image-1.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"]},{\"slug\":\"chung-chi\",\"name\":\"Cấp chứng chỉ\",\"items\":[" +
|
||||
"{\"title\":\"Lớp chứng chỉ 1\",\"imageUrl\":\"/images/dao-tao/image-3.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Lớp chứng chỉ 2\",\"imageUrl\":\"/images/dao-tao/image-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Lớp chứng chỉ 3\",\"imageUrl\":\"/images/dao-tao/image-1.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"{\"title\":\"Lớp chứng chỉ 3\",\"imageUrl\":\"/images/dao-tao/image-1.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Lớp chứng chỉ 4\",\"imageUrl\":\"/images/dao-tao/training-1.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Lớp chứng chỉ 5\",\"imageUrl\":\"/images/dao-tao/training-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Lớp chứng chỉ 6\",\"imageUrl\":\"/images/dao-tao/training-3.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"]},{\"slug\":\"giay-chung-nhan\",\"name\":\"Cấp giấy chứng nhận\",\"items\":[" +
|
||||
"{\"title\":\"Giấy chứng nhận 1\",\"imageUrl\":\"/images/dao-tao/training-4.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Giấy chứng nhận 2\",\"imageUrl\":\"/images/dao-tao/training-3.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"{\"title\":\"Giấy chứng nhận 2\",\"imageUrl\":\"/images/dao-tao/training-3.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Giấy chứng nhận 3\",\"imageUrl\":\"/images/dao-tao/training-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Giấy chứng nhận 4\",\"imageUrl\":\"/images/dao-tao/training-1.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Giấy chứng nhận 5\",\"imageUrl\":\"/images/dao-tao/image-5.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Giấy chứng nhận 6\",\"imageUrl\":\"/images/dao-tao/image-6.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"]},{\"slug\":\"xac-nhan-thuc-hanh\",\"name\":\"Đào tạo cấp giấy xác nhận quá trình thực hành\",\"items\":[" +
|
||||
"{\"title\":\"Xác nhận thực hành 1\",\"imageUrl\":\"/images/dao-tao/PTNS2707.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Xác nhận thực hành 2\",\"imageUrl\":\"/images/dao-tao/training-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"{\"title\":\"Xác nhận thực hành 2\",\"imageUrl\":\"/images/dao-tao/training-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Xác nhận thực hành 3\",\"imageUrl\":\"/images/dao-tao/training-3.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Xác nhận thực hành 4\",\"imageUrl\":\"/images/dao-tao/training-4.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Xác nhận thực hành 5\",\"imageUrl\":\"/images/dao-tao/image-1.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Xác nhận thực hành 6\",\"imageUrl\":\"/images/dao-tao/image-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"]},{\"slug\":\"hinh-anh-thuc-hanh\",\"name\":\"Hình ảnh thực hành lâm sàng\",\"items\":[" +
|
||||
"{\"title\":\"Thực hành lâm sàng 1\",\"imageUrl\":\"/images/dao-tao/training-1.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Thực hành lâm sàng 2\",\"imageUrl\":\"/images/dao-tao/training-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"{\"title\":\"Thực hành lâm sàng 2\",\"imageUrl\":\"/images/dao-tao/training-2.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Thực hành lâm sàng 3\",\"imageUrl\":\"/images/dao-tao/training-3.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Thực hành lâm sàng 4\",\"imageUrl\":\"/images/dao-tao/training-4.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Thực hành lâm sàng 5\",\"imageUrl\":\"/images/dao-tao/image-4.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}," +
|
||||
"{\"title\":\"Thực hành lâm sàng 6\",\"imageUrl\":\"/images/dao-tao/image-5.jpeg\",\"linkUrl\":\"\",\"description\":\"\"}" +
|
||||
"]}]";
|
||||
settingService.setValue(SETTING_KEY, sampleData);
|
||||
LOG.info("Forced seeding of default swiper slider data for all 5 training sliders");
|
||||
return sampleData;
|
||||
|
||||
private String forceSeedAllData() {
|
||||
settingService.setValue(SETTING_KEY, DEFAULT_SLIDER_DATA);
|
||||
LOG.info("Forced seeding of default swiper slider data for all training sliders");
|
||||
return DEFAULT_SLIDER_DATA;
|
||||
}
|
||||
|
||||
private List<SwiperSliderAdminController.SliderGroup> parseGroups(String json) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">
|
||||
|
||||
<changeSet id="20260728163000-1" author="antigravity">
|
||||
<addColumn tableName="sis_doctor">
|
||||
<column name="position" type="varchar(255)">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
<column name="gender" type="varchar(20)">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
<column name="birthday" type="varchar(20)">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
<column name="phone_number" type="varchar(50)">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
<column name="email" type="varchar(100)">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
<column name="address" type="varchar(255)">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
<column name="display_order" type="int">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">
|
||||
|
||||
<changeSet id="20260728164000-1" author="antigravity">
|
||||
<sqlFile path="config/liquibase/data/import_doctors.sql"
|
||||
relativeToChangelogFile="false"
|
||||
encoding="utf8"
|
||||
splitStatements="true"
|
||||
stripComments="true"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">
|
||||
|
||||
<changeSet id="20260728165000-1" author="antigravity">
|
||||
<sqlFile path="config/liquibase/data/map_specialties.sql"
|
||||
relativeToChangelogFile="false"
|
||||
encoding="utf8"
|
||||
splitStatements="false"
|
||||
stripComments="true"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">
|
||||
|
||||
<changeSet id="20260728166000-1" author="antigravity">
|
||||
<sqlFile path="config/liquibase/data/update_active_doctors.sql"
|
||||
relativeToChangelogFile="false"
|
||||
encoding="utf8"
|
||||
splitStatements="true"
|
||||
stripComments="true"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">
|
||||
|
||||
<changeSet id="20260728171000-1" author="antigravity">
|
||||
<sqlFile path="config/liquibase/data/merge_doctors.sql"
|
||||
relativeToChangelogFile="false"
|
||||
encoding="utf8"
|
||||
splitStatements="true"
|
||||
stripComments="true"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">
|
||||
|
||||
<changeSet id="20260728172000-1" author="antigravity">
|
||||
<sqlFile path="config/liquibase/data/merge_doctors.sql"
|
||||
relativeToChangelogFile="false"
|
||||
encoding="utf8"
|
||||
splitStatements="true"
|
||||
stripComments="true"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.6.xsd">
|
||||
|
||||
<changeSet id="20260728173000-1" author="antigravity">
|
||||
<sqlFile path="config/liquibase/data/merge_doctors.sql"
|
||||
relativeToChangelogFile="false"
|
||||
encoding="utf8"
|
||||
splitStatements="false"
|
||||
stripComments="true"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,4 @@
|
||||
BEGIN
|
||||
COMMIT;
|
||||
END;
|
||||
/
|
||||
@@ -0,0 +1,3 @@
|
||||
UPDATE sis_doctor SET active = 0 WHERE doctor_code IS NULL OR doctor_code = '';
|
||||
UPDATE sis_doctor SET active = 1 WHERE doctor_code IS NOT NULL AND doctor_code != '';
|
||||
COMMIT;
|
||||
@@ -52,4 +52,9 @@
|
||||
<include file="config/liquibase/changelog/20260724151500_alter_setting_value_to_clob.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260728140500_add_contact_fields_to_page.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260728160000_add_specialty_category.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260728163000_add_xml_fields_to_doctor.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260728164000_import_doctors_data.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260728165000_map_specialties.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260728166000_update_active_doctors.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260728173000_fix_xml_specialties.xml" relativeToChangelogFile="false"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
@@ -81,31 +81,23 @@
|
||||
<div style="-webkit-text-stroke-width:0px;border-style:solid;border-width:0px;box-sizing:border-box;color:rgb(51, 51, 51);font-family:Roboto, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";font-size:16px;font-style:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-weight:400;letter-spacing:normal;margin:0px;orphans:2;padding:0px;text-align:justify;text-decoration-color:initial;text-decoration-style:initial;text-decoration-thickness:initial;text-indent:0px;text-transform:none;white-space:normal;widows:2;word-spacing:0px;">Phòng KHĐT cùng Đơn vị lập kế hoạch, dự trù kinh phí cho các lớp đào tạo với các hình thức như đào tạo y khoa liên tục (CME), hội nghị, hội thảo, lớp học, … Những chương trình này có thể tổ chức tại Bệnh viện hoặc tổ chức tại các địa điểm bên ngoài tùy theo nhu cầu thực tế để đưa hoạt động đào tạo thực sự trở thành một mũi nhọn, một đặc trưng của bệnh viện.</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="relative md:flex md:items-center md:gap-x-2">
|
||||
<button class="btn-navigation md:size-[42px] size-[32px] flex-shrink-0 items-center justify-center rounded-full btn-navigation-gallery-prev md:flex hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up size-4 -rotate-90">
|
||||
<path d="m18 15-6-6-6 6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="swiper swiper-gallery w-full">
|
||||
<div class="swiper-wrapper">
|
||||
<th:block th:utext="${hookManager.applyFilters('swiper_slider_items', '', 'gallery')}"></th:block>
|
||||
</div>
|
||||
<div class="relative md:flex md:items-center md:gap-x-2">
|
||||
<button class="btn-navigation md:size-[42px] size-[32px] flex-shrink-0 items-center justify-center rounded-full btn-navigation-dao-tao-01-prev md:flex hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up size-4 -rotate-90">
|
||||
<path d="m18 15-6-6-6 6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="swiper swiper-dao-tao-01 w-full">
|
||||
<div class="swiper-wrapper">
|
||||
<th:block th:utext="${hookManager.applyFilters('swiper_slider_items', '', 'dao-tao-01')}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-navigation md:size-[42px] size-[32px] flex-shrink-0 items-center justify-center rounded-full btn-navigation-gallery-next md:flex hidden">
|
||||
<button class="btn-navigation md:size-[42px] size-[32px] flex-shrink-0 items-center justify-center rounded-full btn-navigation-dao-tao-01-next md:flex hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down size-4 -rotate-90">
|
||||
<path d="m6 9 6 6 6-6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="swiper-pagination swiper-pagination-training-gallery space-x-[13px] !relative [&_.swiper-pagination-bullet-active]:!bg-primary-600 [&_.swiper-pagination-bullet-active]:!opacity-100 [&_.swiper-pagination-bullet-active]:before:opacity-100 mt-6 flex justify-center [&_.swiper-pagination-bullet]:!relative [&_.swiper-pagination-bullet]:before:content-[''] [&_.swiper-pagination-bullet]:before:size-[14px] [&_.swiper-pagination-bullet]:before:bg-transparent [&_.swiper-pagination-bullet]:before:rounded-full [&_.swiper-pagination-bullet]:before:border [&_.swiper-pagination-bullet]:before:border-primary-600 md:[&_.swiper-pagination-bullet]:before:top-[-3px] [&_.swiper-pagination-bullet]:before:top-[-3px] [&_.swiper-pagination-bullet]:before:left-[-2.7px] [&_.swiper-pagination-bullet]:before:absolute [&_.swiper-pagination-bullet]:before:block [&_.swiper-pagination-bullet]:before:opacity-0 [&_.swiper-pagination-bullet]:!size-[8px] [&_.swiper-pagination-bullet]:!bg-gray-600 md:hidden swiper-pagination-clickable swiper-pagination-bullets swiper-pagination-horizontal">
|
||||
<span class="swiper-pagination-bullet swiper-pagination-bullet-active"></span>
|
||||
<span class="swiper-pagination-bullet"></span>
|
||||
<span class="swiper-pagination-bullet"></span>
|
||||
<span class="swiper-pagination-bullet"></span>
|
||||
<span class="swiper-pagination-bullet"></span>
|
||||
<span class="swiper-pagination-bullet"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="xl:py-12 md:py-8 py-6 bg-[#f6f6f6] xl:space-y-12 md:space-y-8 space-y-6">
|
||||
@@ -116,12 +108,12 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex md:gap-2 gap-1">
|
||||
<button class="btn-navigation-khoa-hoc-prev md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group swiper-button-disabled swiper-button-lock" disabled="">
|
||||
<button class="btn-navigation-khoa-hoc-prev md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up size-4 -rotate-90">
|
||||
<path d="m18 15-6-6-6 6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-navigation-khoa-hoc-next md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group swiper-button-disabled swiper-button-lock" disabled="">
|
||||
<button class="btn-navigation-khoa-hoc-next md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down size-4 -rotate-90">
|
||||
<path d="m6 9 6 6 6-6"></path>
|
||||
</svg>
|
||||
@@ -131,22 +123,39 @@
|
||||
</div>
|
||||
<div class="relative max-md:px-4 md:mb-6 mb-4 lg:mb-8">
|
||||
<div class="swiper swiper-khoa-hoc w-full">
|
||||
<div class="swiper-wrapper">
|
||||
<!-- 1. ĐÃ XÓA gap-3 -->
|
||||
<div class="swiper-wrapper" style="height: 450px;">
|
||||
<th:block th:each="post : ${daoTaoPosts}">
|
||||
<div class="swiper-slide h-auto max-w-[288px] mr-2 lg:!mr-3 xl:!mr-4" style="width: 288px; max-width: 100%;">
|
||||
<a th:href="@{/dao-tao/{slug}(slug=${post.slug})}" class="group h-full flex flex-col items-start gap-y-4 lg:gap-y-5 rounded-2xl bg-white p-4 pb-5 shadow-sm transition-shadow hover:shadow-md border border-transparent hover:border-primary-100">
|
||||
<div class="relative w-full aspect-square overflow-hidden rounded-xl">
|
||||
<img th:src="${post.imageUrl != null and !#strings.isEmpty(post.imageUrl) ? post.imageUrl : '/images/dao-tao/training-1.jpeg'}" th:alt="${post.title}" class="absolute inset-0 size-full object-cover transition-transform duration-500 group-hover:scale-105">
|
||||
<!-- 2. Dùng h-auto để Swiper tự động stretch các card cao bằng nhau -->
|
||||
<div class="swiper-slide h-auto" style="width: 288px; max-width: 100%; margin: 5px; background-color: var(--color-white);">
|
||||
|
||||
<!-- Thẻ a (card) đã set h-full để giãn kịch trần -->
|
||||
<a th:href="@{/dao-tao/{slug}(slug=${post.slug})}" class="group h-full flex flex-col items-start">
|
||||
|
||||
<!-- 3. Đã thêm overflow-hidden vào khung ảnh -->
|
||||
<div class="w-full rounded-xl mb-3">
|
||||
<img th:src="${post.imageUrl != null and !#strings.isEmpty(post.imageUrl) ? post.imageUrl : '/images/dao-tao/training-1.jpeg'}"
|
||||
th:alt="${post.title}"
|
||||
class="size-full aspect-square object-cover transition-transform duration-500 group-hover:scale-105">
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col justify-between w-full">
|
||||
<h3 class="heading-5 text-gray-900 group-hover:text-primary-600 transition-colors line-clamp-3" th:text="${post.title}">Tiêu đề khóa học</h3>
|
||||
|
||||
<!-- 4. Gom Tiêu đề và Trích dẫn vào chung 1 thẻ flex-1 -->
|
||||
<div class="flex flex-1 flex-col justify-start w-full gap-y-2">
|
||||
<h3 class="heading-5 text-gray-900 group-hover:text-primary-600 transition-colors line-clamp-3"
|
||||
th:text="${post.title}">Tiêu đề khóa học</h3>
|
||||
|
||||
<!-- Đoạn text trích dẫn -->
|
||||
<p class="body-3 text-gray-600 line-clamp-3"
|
||||
th:text="${post.excerpt}">Giới thiệu ngắn gọn</p>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="lg:container max-lg:pl-5 max-md:px-0">
|
||||
<div class="flex justify-between items-end md:mb-6 mb-4 lg:mb-8 max-lg:pr-5 max-md:px-4">
|
||||
@@ -155,12 +164,12 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex md:gap-2 gap-1">
|
||||
<button class="btn-navigation-cap-chung-chi-prev md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group swiper-button-disabled swiper-button-lock" disabled="">
|
||||
<button class="btn-navigation-cap-chung-chi-prev md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up size-4 -rotate-90">
|
||||
<path d="m18 15-6-6-6 6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-navigation-cap-chung-chi-next md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group swiper-button-disabled swiper-button-lock" disabled="">
|
||||
<button class="btn-navigation-cap-chung-chi-next md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down size-4 -rotate-90">
|
||||
<path d="m6 9 6 6 6-6"></path>
|
||||
</svg>
|
||||
@@ -183,12 +192,12 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex md:gap-2 gap-1">
|
||||
<button class="btn-navigation-cap-giay-chung-nhan-prev md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group swiper-button-disabled swiper-button-lock" disabled="">
|
||||
<button class="btn-navigation-cap-giay-chung-nhan-prev md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up size-4 -rotate-90">
|
||||
<path d="m18 15-6-6-6 6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-navigation-cap-giay-chung-nhan-next md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group swiper-button-disabled swiper-button-lock" disabled="">
|
||||
<button class="btn-navigation-cap-giay-chung-nhan-next md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down size-4 -rotate-90">
|
||||
<path d="m6 9 6 6 6-6"></path>
|
||||
</svg>
|
||||
@@ -211,12 +220,12 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex md:gap-2 gap-1">
|
||||
<button class="btn-navigation-dao-tao-cap-giay-xac-nhan-qua-trinh-thuc-hanh-prev md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group swiper-button-disabled swiper-button-lock" disabled="">
|
||||
<button class="btn-navigation-dao-tao-cap-giay-xac-nhan-qua-trinh-thuc-hanh-prev md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-up size-4 -rotate-90">
|
||||
<path d="m18 15-6-6-6 6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-navigation-dao-tao-cap-giay-xac-nhan-qua-trinh-thuc-hanh-next md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group swiper-button-disabled swiper-button-lock" disabled="">
|
||||
<button class="btn-navigation-dao-tao-cap-giay-xac-nhan-qua-trinh-thuc-hanh-next md:size-10 size-8 flex items-center justify-center rounded-full bg-white shadow-sm border border-gray-100 hover:bg-primary-600 hover:text-white transition-colors cursor-pointer group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down size-4 -rotate-90">
|
||||
<path d="m6 9 6 6 6-6"></path>
|
||||
</svg>
|
||||
@@ -301,10 +310,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!--$-->
|
||||
<!--/$-->
|
||||
<!--$-->
|
||||
<!--/$-->
|
||||
</main>
|
||||
<div class="fixed bottom-6 right-6 z-[49] flex flex-col gap-3 items-end">
|
||||
<a href="tel:19002827" aria-label="Gọi điện thoại" class="flex h-10 w-10 items-center justify-center rounded-full bg-primary-700 shadow-lg transition-colors hover:bg-primary-800" style="opacity: 1; transform: none; transform-origin: 50% 50% 0px;">
|
||||
@@ -318,358 +323,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<!--/$-->
|
||||
<next-route-announcer style="position: absolute;">
|
||||
<template shadowrootmode="open">
|
||||
<div aria-live="assertive" id="__next-route-announcer__" role="alert" style="position: absolute; border: 0px; height: 1px; margin: -1px; padding: 0px; width: 1px; clip: rect(0px, 0px, 0px, 0px); overflow: hidden; white-space: nowrap; overflow-wrap: normal;"></div>
|
||||
</template>
|
||||
</next-route-announcer>
|
||||
<div id="easychatgpt-widget" data-ecg-initialized="true" style="--ui-logo: https://assets.geteasy.ai/workspaces/2353/bots/2770/public/0fd2dbe4-63bf-4d8e-871f-ab903f800fca.png; --ui-name: Trợ lý AI của UMC; --ui-size: medium; --ui-theme: light; --ui-alignment-offset-mobile-vertical: 125px; --ui-alignment-offset-mobile-horizontal: 15px; --ui-alignment-offset-desktop-vertical: 130px; --ui-alignment-offset-desktop-horizontal: 20px; --ui-alignment-position: right; --ui-icon-image: https://assets.geteasy.ai/workspaces/2353/bots/2770/public/5026d034-feea-46db-a0c1-60c078ef5d5d.png; --ui-bubble_chat-offset-mobile-size: small; --ui-bubble_chat-offset-mobile-custom: 60px; --ui-bubble_chat-offset-desktop-size: small; --ui-bubble_chat-offset-desktop-custom: 60px; --ui-primary-color: rgb(8, 82, 158); --ui-chat-button-icon: <svg width="30" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="block z-10 text-white align-middle border-gray-200"><path d="m3 21 1.9-5.7a8.5 8.5 0 1 1 3.8 3.8z" class="border-gray-200"></path></svg>; --ui-automatically-expand: false; --ui-show-assistant-avatar: true;">
|
||||
<template shadowrootmode="open">
|
||||
<div data-v-app="">
|
||||
<div workspaceuuid="d734fb80-4d39-4fd3-8b8c-d6af95ad4489">
|
||||
<div class="chat-widget position-right btn-size-desktop-small btn-size-mobile-small" style="display: none; --size-desktop-custom: 60px; --size-mobile-custom: 60px; --z-index-chatview: 9999999999; --z-index-bubblechat: 999999990;">
|
||||
<div class="w-screen grow sm:w-[425px] sm:h-[720px] sm:max-h-[calc(100vh-6rem)] z-[var(--z-index-chatview)] overflow-hidden ecg-chat-view sm:rounded-3xl box-shadow-md max-sm:border-t-0 max-sm:border-x-0 fixed max-sm:right-0 sm:right-4 duration-500 opacity-0 invisible translate-y-[150%] h-dvh rounded-none max-sm:top-0">
|
||||
<div id="ei-chat-container" class="flex flex-col h-full w-full bg-gray-50 h-full w-full">
|
||||
<div class="flex-1 overflow-hidden h-full">
|
||||
<!---->
|
||||
<div id="chat-view" class="flex flex-col overflow-hidden bg-white h-full w-full pb-1">
|
||||
<div class="relative ecg-widget-header">
|
||||
<div class="flex items-center justify-between p-3.5 text-white bg-primary">
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="p-1.5 rounded-lg hover:bg-white/10 text-white" aria-label="Quay lại">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="20" class="lucide lucide-chevron-left-icon">
|
||||
<path d="m15 18-6-6 6-6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<!---->
|
||||
<h2 class="text-base font-medium tracking-tight">Trợ lý AI của UMC</h2>
|
||||
</div>
|
||||
<div class="flex gap-1.5">
|
||||
<!---->
|
||||
<div class="flex items-center justify-center rounded-lg size-8 bg-white/10 hover:bg-white/20 cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="14" class="lucide lucide-refresh-ccw-icon">
|
||||
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
|
||||
<path d="M3 3v5h5"></path>
|
||||
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"></path>
|
||||
<path d="M16 16h5v5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<button aria-label="eac-close-chat" class="size-8 rounded-lg flex items-center justify-center bg-white/10 hover:bg-white/20">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="16" class="lucide lucide-minus-icon">
|
||||
<path d="M5 12h14"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="messages-wrapper">
|
||||
<div class="chat-banners block chat-content-item">
|
||||
<div class="embla-wrapper">
|
||||
<div class="embla">
|
||||
<div class="embla__container" style="transform: translate3d(0px, 0px, 0px);">
|
||||
<div class="embla__slide w-full h-[150px]">
|
||||
<div target="_blank" class="block w-full h-full">
|
||||
<img src="https://assets.geteasy.ai/workspaces/2353/bots/2770/public/fabf8d12-7b48-4257-a754-78288d866339.png" alt="ecg-banner" class="object-cover w-full h-full">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
<button class="close-banner-btn">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-3">
|
||||
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col flex-grow px-2 pt-4 space-y-4 duration-100 lg:px-4 chat-content-item">
|
||||
<div class="space-y-3 mb-4">
|
||||
<div class="messages-group message-assistant">
|
||||
<span class="relative flex flex-shrink-0 overflow-hidden bg-white size-8 message-avatar opacity-0 rounded">
|
||||
<img src="https://assets.geteasy.ai/workspaces/2353/bots/2770/public/0fd2dbe4-63bf-4d8e-871f-ab903f800fca.png" alt="Trợ lý AI của UMC" class="object-cover w-full h-full">
|
||||
</span>
|
||||
<div class="messages">
|
||||
<div class="relative flex-1 group w-fit max-w-[92%]">
|
||||
<!---->
|
||||
<div class="relative w-full mr-auto">
|
||||
<div class="message message-assistant">
|
||||
<div class="relative max-w-full w-fit">
|
||||
<div class="relative px-4 py-3.5 min-w-11 bg-gray-100 rounded-xl prose-sm prose max-sm:select-none">
|
||||
<p>Xin chào, Em là trợ lý ảo của UMC 👋</p>
|
||||
</div>
|
||||
<button type="button" aria-label="Trả lời" title="Trả lời" class="reply-btn opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity duration-200 flex items-center justify-center size-6 rounded-full bg-gray-50 shadow-sm text-gray-500 hover:bg-gray-100 hover:text-gray-600 absolute top-1/2 -translate-y-1/2 -right-7">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-quote-icon size-3">
|
||||
<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"></path>
|
||||
<path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="absolute right-1.5 -bottom-3 opacity-0 md:group-hover:opacity-100 transition-opacity duration-300">
|
||||
<div data-v-76058769="" class="relative reaction-container">
|
||||
<div data-v-76058769="" class="flex items-center group-reactions">
|
||||
<!---->
|
||||
<button data-v-76058769="" class="reaction-button flex items-center justify-center size-[26px] border border-gray-100 shadow-sm rounded-full bg-white select-none transition-[transform,filter,background-color] duration-[150ms,200ms,200ms] ease-[cubic-bezier(0.175,0.885,0.32,1.275),ease,ease] hover:scale-110 active:scale-95 will-change-transform-opacity grayscale text-sm">
|
||||
<svg data-v-76058769="" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="14" class="lucide lucide-heart-icon text-gray-500">
|
||||
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div data-v-76058769="" class="absolute -top-2 z-10 -translate-y-full emoji-picker sm:top-0 origin-center-bottom will-change-[transform,opacity] translate-x-0 -right-0 sm:-right-3">
|
||||
<div data-v-76058769="" class="flex justify-center items-center px-2 py-0.5 rounded-3xl select-none bg-white/90" style="box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;">
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-base sm:text-sm translate-y-[1px]">❤️</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Yêu thích</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.03s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-base sm:text-sm translate-y-[1px]">👍</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Thích</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.06s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-lg sm:text-base">🤣</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Haha</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.09s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-lg sm:text-base">😮</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Wow</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.12s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-lg sm:text-base">😭</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Buồn</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.15s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-base sm:text-sm translate-y-[1px]">👎</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Không thích</div>
|
||||
</button>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="message-1" class="messages-group message-assistant !mt-2">
|
||||
<span class="relative flex flex-shrink-0 overflow-hidden bg-white size-8 message-avatar opacity-100 rounded">
|
||||
<img src="https://assets.geteasy.ai/workspaces/2353/bots/2770/public/0fd2dbe4-63bf-4d8e-871f-ab903f800fca.png" alt="Trợ lý AI của UMC" class="object-cover w-full h-full">
|
||||
</span>
|
||||
<div class="messages">
|
||||
<div class="relative flex-1 group w-fit max-w-[92%]">
|
||||
<!---->
|
||||
<div class="relative w-full mr-auto">
|
||||
<div class="message message-assistant">
|
||||
<div class="relative max-w-full w-fit">
|
||||
<div class="relative px-4 py-3.5 min-w-11 bg-gray-100 rounded-xl prose-sm prose max-sm:select-none">
|
||||
<p>Em rất sẵn lòng hỗ trợ Anh/Chị 😊</p>
|
||||
</div>
|
||||
<button type="button" aria-label="Trả lời" title="Trả lời" class="reply-btn opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity duration-200 flex items-center justify-center size-6 rounded-full bg-gray-50 shadow-sm text-gray-500 hover:bg-gray-100 hover:text-gray-600 absolute top-1/2 -translate-y-1/2 -right-7">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-quote-icon size-3">
|
||||
<path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"></path>
|
||||
<path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="absolute right-1.5 -bottom-3 opacity-100">
|
||||
<div data-v-76058769="" class="relative reaction-container">
|
||||
<div data-v-76058769="" class="flex items-center group-reactions">
|
||||
<!---->
|
||||
<button data-v-76058769="" class="reaction-button flex items-center justify-center size-[26px] border border-gray-100 shadow-sm rounded-full bg-white select-none transition-[transform,filter,background-color] duration-[150ms,200ms,200ms] ease-[cubic-bezier(0.175,0.885,0.32,1.275),ease,ease] hover:scale-110 active:scale-95 will-change-transform-opacity grayscale text-sm">
|
||||
<svg data-v-76058769="" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="14" class="lucide lucide-heart-icon text-gray-500">
|
||||
<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div data-v-76058769="" class="absolute -top-2 z-10 -translate-y-full emoji-picker sm:top-0 origin-center-bottom will-change-[transform,opacity] translate-x-[46%] right-0">
|
||||
<div data-v-76058769="" class="flex justify-center items-center px-2 py-0.5 rounded-3xl select-none bg-white/90" style="box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;">
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-base sm:text-sm translate-y-[1px]">❤️</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Yêu thích</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.03s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-base sm:text-sm translate-y-[1px]">👍</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Thích</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.06s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-lg sm:text-base">🤣</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Haha</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.09s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-lg sm:text-base">😮</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Wow</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.12s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-lg sm:text-base">😭</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Buồn</div>
|
||||
</button>
|
||||
<button data-v-76058769="" class="emoji-btn bg-transparent border-none cursor-pointer flex items-center justify-center size-[28px] sm:size-[26px] rounded-3xl hover:bg-gray-100 hover:scale-115 relative transition-[transform,background-color] duration-150 ease-in-out will-change-transform" style="--delay: 0.15s;">
|
||||
<span data-v-76058769="" class="leading-[100%] text-base sm:text-sm translate-y-[1px]">👎</span>
|
||||
<div data-v-76058769="" class="custom-tooltip absolute -top-[25px] left-1/2 -translate-x-1/2 scale-[0.8] bg-black/75 text-white text-[10px] px-1.5 py-0.5 rounded whitespace-nowrap opacity-0 pointer-events-none transition-[opacity,transform] duration-200 ease-in-out z-[60] hidden hover-hover:group-hover/emoji:opacity-100 hover-hover:group-hover/emoji:scale-100 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:border-4 after:border-solid after:border-t-black/75 after:border-x-transparent after:border-b-transparent">Không thích</div>
|
||||
</button>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 duration-200 pb-0">
|
||||
<div class="flex flex-col items-end py-4 space-y-2">
|
||||
<div class="quick-prompt-tag active">Giới thiệu về bệnh viện</div>
|
||||
<div class="quick-prompt-tag active">Đặt lịch khám</div>
|
||||
<div class="quick-prompt-tag active">Thủ tục khám bệnh bảo hiểm y tế</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="relative z-30 bg-white">
|
||||
<div class="w-full px-3 pt-1">
|
||||
<div class="border border-gray-200 rounded-2xl">
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="relative flex w-full">
|
||||
<div class="flex absolute top-1/2 -translate-y-1/2 left-1.5 z-10 items-center quick-prompts">
|
||||
<button type="button" class="flex items-center justify-center transition-colors rounded-full size-8 overflow-hidden hover:bg-gray-50 bg-white" title="Gợi ý nhanh">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="18" class="lucide lucide-menu-icon text-gray-500">
|
||||
<line x1="4" x2="20" y1="12" y2="12"></line>
|
||||
<line x1="4" x2="20" y1="6" y2="6"></line>
|
||||
<line x1="4" x2="20" y1="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
<!---->
|
||||
</div>
|
||||
<textarea id="eac-chat-input" class="resize-none rounded-3xl input" placeholder="Nhập tin nhắn..." rows="1" style="height: 44px;"></textarea>
|
||||
<div class="absolute z-10 flex items-center duration-200 top-1/2 -translate-y-1/2 right-1">
|
||||
<!---->
|
||||
<div class="w-fit relative group">
|
||||
<input type="file" class="hidden" accept=".pdf,.doc,.docx,.xls,.xlsx,.csv,.jpg,.jpeg,.png,.webp,.bmp,.tiff,.tif,.heic,.avif,.mp3,.wav,.ogg,.m4a" multiple="">
|
||||
<button type="button" class="size-9 flex items-center justify-center rounded-full sm:hover:bg-gray-100 cursor-pointer text-gray-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="18" class="lucide lucide-paperclip-icon">
|
||||
<path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<!---->
|
||||
</div>
|
||||
<button type="button" class="bg-primary text-white sm:hover:opacity-80 flex items-center justify-center ml-1.5 duration-200 rounded-full size-9">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" size="16" class="lucide lucide-send-horizontal-icon">
|
||||
<path d="m3 3 3 9-3 9 19-9Z"></path>
|
||||
<path d="M6 12h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drawer-form" tabindex="-1"></div>
|
||||
<div class="drawer-form" tabindex="-1"></div>
|
||||
<!---->
|
||||
<div class="drawer-overlay-custom"></div>
|
||||
<!---->
|
||||
<div class="drawer-overlay-custom"></div>
|
||||
<!---->
|
||||
<div class="drawer-overlay-custom"></div>
|
||||
<!---->
|
||||
<div class="drawer-overlay-custom"></div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<div class="flex items-center justify-center w-full py-1 flex-row bg-white">
|
||||
<!---->
|
||||
<a href="https://geteasy.ai/" target="_blank" rel="nofollow noreferrer" class="text-[10px] text-gray-500 font-medium flex items-center space-x-1 px-2 pb-0.5">
|
||||
<span>Powered by</span>
|
||||
<div class="flex items-center space-x-1">
|
||||
<svg width="20" height="6.25" viewBox="0 0 32 10" fill="none" xmlns="http://www.w3.org/2000/svg" class="mt-px">
|
||||
<path d="M29.0312 2.39062V2.39062C21.8342 9.58768 10.1655 9.58768 2.9684 2.39062V2.39062" stroke="url(#paint0_linear_950_398)" stroke-width="3" stroke-linecap="square"></path>
|
||||
<defs>
|
||||
<lineargradient id="paint0_linear_950_398" x1="9.48412" y1="8.90634" x2="22.5155" y2="-4.12509" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#3A087F"></stop>
|
||||
<stop offset="40%" stop-color="#A535EB"></stop>
|
||||
<stop offset="70%" stop-color="#FCA63E"></stop>
|
||||
<stop offset="88%" stop-color="#54D5A4"></stop>
|
||||
<stop offset="100%" stop-color="#1B8AF7"></stop>
|
||||
</lineargradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<span class="font-bold text-gray-900">Easy AI</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="z-[var(--z-index-bubblechat)] chat-button-group fixed bottom-[var(--ui-alignment-offset-vertical)] space-y-2">
|
||||
<div>
|
||||
<div class="flex-col space-y-2 sm:space-y-3 group items-end hidden sm:flex">
|
||||
<div class="pr-0.5 duration-500 group-hover:block inner-close hidden">
|
||||
<button class="flex overflow-hidden relative justify-center items-center w-6 h-6 md:w-5 md:h-5 rounded-full hover:opacity-70">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="relative z-10 w-4 md:w-3 text-white">
|
||||
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
<div class="absolute inset-0 bg-gray-500/60"></div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="translate-y-0 opacity-100 visible px-3 pt-2 cursor-pointer pb-1 box-shadow-md bg-white rounded-xl relative w-[180px] sm:w-[200px] duration-1000 delay-75">
|
||||
<div class="items-center space-x-1 sm:space-x-2 mb-1 sm:mb-1.5 hidden sm:flex">
|
||||
<div class="overflow-hidden size-5 sm:size-6 shrink-0 rounded">
|
||||
<img src="https://assets.geteasy.ai/workspaces/2353/bots/2770/public/0fd2dbe4-63bf-4d8e-871f-ab903f800fca.png" alt="Trợ lý AI của UMC" class="object-cover w-full h-full">
|
||||
</div>
|
||||
<div class="text-sm font-medium text-primary leading-[120%]">Trợ lý AI của UMC</div>
|
||||
</div>
|
||||
<div>
|
||||
<div data-v-62f66be8="" class="vertical-custom-slide duration-500" style="height: 42px;">
|
||||
<div data-v-62f66be8="" class="duration-1000 vertical-move" style="transform: translateY(-42px);">
|
||||
<div data-v-62f66be8="" class="flex-1 text-sm text-gray-700 duration-1000 vertical-slide-item opacity-0">Xin chào, Em là trợ lý ảo của UMC 👋</div>
|
||||
<div data-v-62f66be8="" class="flex-1 text-sm text-gray-700 duration-1000 vertical-slide-item opacity-100">Em rất sẵn lòng hỗ trợ Anh/Chị 😊</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="duration-300 relative w-fit border-[3px] rounded-lg delay-300 ml-auto border-transparent">
|
||||
<!---->
|
||||
<button aria-label="chat-button" class="flex relative justify-center items-center cursor-pointer chat-button disabled:pointer-events-none disabled:opacity-50 group rounded-lg bg-transparent shadow overflow-hidden">
|
||||
<img src="https://assets.geteasy.ai/workspaces/2353/bots/2770/public/5026d034-feea-46db-a0c1-60c078ef5d5d.png" alt="Trợ lý AI của UMC" class="object-cover w-full h-full">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<link rel="stylesheet" href="https://widget.geteasy.ai/dist/widget/main.css?v=1784950551115">
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<th:block layout:fragment="scripts">
|
||||
<!-- Swiper JS & Initialization -->
|
||||
<script th:src="@{/js/swiper-bundle.min.js}"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
|
||||
<script>
|
||||
@@ -679,10 +333,10 @@
|
||||
return;
|
||||
}
|
||||
const sliderConfigs = [{
|
||||
slug: 'gallery',
|
||||
prev: '.btn-navigation-gallery-prev',
|
||||
next: '.btn-navigation-gallery-next',
|
||||
pagination: '.swiper-pagination-training-gallery'
|
||||
slug: 'dao-tao-01',
|
||||
prev: '.btn-navigation-dao-tao-01-prev',
|
||||
next: '.btn-navigation-dao-tao-01-next',
|
||||
pagination: ''
|
||||
}, {
|
||||
slug: 'khoa-hoc',
|
||||
prev: '.btn-navigation-khoa-hoc-prev',
|
||||
@@ -711,10 +365,17 @@
|
||||
}];
|
||||
sliderConfigs.forEach(function(config) {
|
||||
const el = document.querySelector('.swiper-' + config.slug);
|
||||
if (el && !el.classList.contains('swiper-initialized')) {
|
||||
if (el) {
|
||||
if (el.swiper) {
|
||||
el.swiper.destroy(true, true);
|
||||
}
|
||||
new Swiper('.swiper-' + config.slug, {
|
||||
slidesPerView: 'auto',
|
||||
spaceBetween: 16,
|
||||
observer: true,
|
||||
observeParents: true,
|
||||
watchOverflow: false,
|
||||
rewind: true,
|
||||
navigation: {
|
||||
nextEl: config.next,
|
||||
prevEl: config.prev,
|
||||
@@ -734,6 +395,13 @@
|
||||
}
|
||||
window.addEventListener('load', initDaoTaoSliders);
|
||||
</script>
|
||||
</div>
|
||||
<th:block layout:fragment="scripts">
|
||||
<script>
|
||||
if (typeof initDaoTaoSliders === 'function') {
|
||||
initDaoTaoSliders();
|
||||
}
|
||||
</script>
|
||||
</th:block>
|
||||
</body>
|
||||
</html>
|
||||
@@ -156,10 +156,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div th:utext="${doctorIntroHtml}"></div>
|
||||
|
||||
<div style="display: flex; flex-direction: row; justify-content: start; align-items: center; gap: var(--s2);" class="mb-6 lg:mt-6">
|
||||
<button type="button" class="btn space-x-2 text-center label-3 w-[286px] !py-2 disabled:opacity-50 disabled:cursor-not-allowed text-white hover:opacity-90 transition-opacity" th:data-url="${doctor.bookingUrl != null and !doctor.bookingUrl.isEmpty() ? doctor.bookingUrl : '#'}" onclick="window.location.href=this.getAttribute('data-url')">
|
||||
<div>Đặt lịch khám</div>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<a href="tel:18001115" style="color: white; text-decoration: none; width: 100%; display: flex; flex-direction: row; gap: 10px; justify-content: center; align-items: center;">Đặt lịch khám
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_83_297)">
|
||||
<path d="M0 16.26C0 17.3 0.366667 18.18 1.1 18.9C1.83333 19.62 2.72 19.9867 3.76 20H16.26C17.2867 20 18.1667 19.6333 18.9 18.9C19.6333 18.1667 20 17.2867 20 16.26V3.76C20 2.94667 19.7667 2.22 19.3 1.58C18.8333 0.94 18.2333 0.493333 17.5 0.24V1.88C17.5 2.4 17.32 2.84667 16.96 3.22C16.6 3.59333 16.1533 3.77333 15.62 3.76C15.0867 3.74667 14.6467 3.56667 14.3 3.22C13.9533 2.87333 13.7733 2.42667 13.76 1.88V0H6.26V1.88C6.26 2.4 6.07333 2.84667 5.7 3.22C5.32667 3.59333 4.88667 3.77333 4.38 3.76C3.87333 3.74667 3.42667 3.56667 3.04 3.22C2.65333 2.87333 2.47333 2.42667 2.5 1.88V0.24C1.76667 0.506667 1.16667 0.953333 0.7 1.58C0.233333 2.20667 0 2.93333 0 3.76L0 16.26ZM2.5 16.26V6.26H17.5V16.26C17.5 16.6067 17.38 16.9 17.14 17.14C16.9 17.38 16.6067 17.5 16.26 17.5H3.76C3.41333 17.5 3.11333 17.38 2.86 17.14C2.60667 16.9 2.48667 16.6067 2.5 16.26ZM3.76 1.88C3.76 2.05333 3.82 2.2 3.94 2.32C4.06 2.44 4.20667 2.5 4.38 2.5C4.55333 2.5 4.7 2.44 4.82 2.32C4.94 2.2 5 2.05333 5 1.88V0H3.76V1.88ZM5 15H7.5V12.5H5V15ZM5 11.26H7.5V8.76H5V11.26ZM8.76 15H11.26V12.5H8.76V15ZM8.76 11.26H11.26V8.76H8.76V11.26ZM12.5 15H15V12.5H12.5V15ZM12.5 11.26H15V8.76H12.5V11.26ZM15 1.88C15 2.05333 15.06 2.2 15.18 2.32C15.3 2.44 15.4467 2.5 15.62 2.5C15.7933 2.5 15.94 2.44 16.06 2.32C16.18 2.2 16.2467 2.05333 16.26 1.88V0H15V1.88Z" fill="white"></path>
|
||||
</g>
|
||||
@@ -169,19 +170,14 @@
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
</button>
|
||||
<button type="button" class="btn space-x-2 text-center label-3 w-[286px] !py-2 disabled:opacity-50 disabled:cursor-not-allowed text-white hover:opacity-90 transition-opacity" th:data-url="${doctor.bookingUrl != null and !doctor.bookingUrl.isEmpty() ? doctor.bookingUrl : '#'}" onclick="window.location.href=this.getAttribute('data-url')">
|
||||
<div>Đặt lịch khám</div>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_83_297)">
|
||||
<path d="M0 16.26C0 17.3 0.366667 18.18 1.1 18.9C1.83333 19.62 2.72 19.9867 3.76 20H16.26C17.2867 20 18.1667 19.6333 18.9 18.9C19.6333 18.1667 20 17.2867 20 16.26V3.76C20 2.94667 19.7667 2.22 19.3 1.58C18.8333 0.94 18.2333 0.493333 17.5 0.24V1.88C17.5 2.4 17.32 2.84667 16.96 3.22C16.6 3.59333 16.1533 3.77333 15.62 3.76C15.0867 3.74667 14.6467 3.56667 14.3 3.22C13.9533 2.87333 13.7733 2.42667 13.76 1.88V0H6.26V1.88C6.26 2.4 6.07333 2.84667 5.7 3.22C5.32667 3.59333 4.88667 3.77333 4.38 3.76C3.87333 3.74667 3.42667 3.56667 3.04 3.22C2.65333 2.87333 2.47333 2.42667 2.5 1.88V0.24C1.76667 0.506667 1.16667 0.953333 0.7 1.58C0.233333 2.20667 0 2.93333 0 3.76L0 16.26ZM2.5 16.26V6.26H17.5V16.26C17.5 16.6067 17.38 16.9 17.14 17.14C16.9 17.38 16.6067 17.5 16.26 17.5H3.76C3.41333 17.5 3.11333 17.38 2.86 17.14C2.60667 16.9 2.48667 16.6067 2.5 16.26ZM3.76 1.88C3.76 2.05333 3.82 2.2 3.94 2.32C4.06 2.44 4.20667 2.5 4.38 2.5C4.55333 2.5 4.7 2.44 4.82 2.32C4.94 2.2 5 2.05333 5 1.88V0H3.76V1.88ZM5 15H7.5V12.5H5V15ZM5 11.26H7.5V8.76H5V11.26ZM8.76 15H11.26V12.5H8.76V15ZM8.76 11.26H11.26V8.76H8.76V11.26ZM12.5 15H15V12.5H12.5V15ZM12.5 11.26H15V8.76H12.5V11.26ZM15 1.88C15 2.05333 15.06 2.2 15.18 2.32C15.3 2.44 15.4467 2.5 15.62 2.5C15.7933 2.5 15.94 2.44 16.06 2.32C16.18 2.2 16.2467 2.05333 16.26 1.88V0H15V1.88Z" fill="white"></path>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_83_297">
|
||||
<rect width="20" height="20" fill="white"></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<a href="tel:18001115" style="color: white; text-decoration: none; width: 100%; display: flex; flex-direction: row; gap: 10px; justify-content: center; align-items: center;">Liên hệ
|
||||
<svg class="text-white" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
|
||||
</a>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -576,23 +572,24 @@
|
||||
<div class="jam-accordion-content">
|
||||
<div class="prose doctor-info-prose transition-all duration-200 !text-[14px] space-y-2 xl:m-3 m-2" th:utext="${doctor.workExperience}"></div>
|
||||
</div>
|
||||
<div class="jam-accordion active" th:if="${doctor.achievements != null and !doctor.achievements.isEmpty()}">
|
||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div class="bg-primary-600 xl:p-3 p-2 flex justify-between items-center cursor-pointer jam-accordion-title" tabindex="0" aria-expanded="true" aria-controls="association-content">
|
||||
<div class="title-3 text-white">Hiệp hội chuyên môn & Thành tựu</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down w-4 h-4 text-white duration-150 -rotate-180">
|
||||
<path d="m6 9 6 6 6-6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="jam-accordion-content">
|
||||
<div class="prose doctor-info-prose transition-all duration-200 !text-[14px] xl:m-3 m-2" th:utext="${doctor.achievements}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jam-accordion active" th:if="${doctor.achievements != null and !doctor.achievements.isEmpty()}">
|
||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div class="bg-primary-600 xl:p-3 p-2 flex justify-between items-center cursor-pointer jam-accordion-title" tabindex="0" aria-expanded="true" aria-controls="association-content">
|
||||
<div class="title-3 text-white">Hiệp hội chuyên môn & Thành tựu</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down w-4 h-4 text-white duration-150 -rotate-180">
|
||||
<path d="m6 9 6 6 6-6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="jam-accordion-content">
|
||||
<div class="prose doctor-info-prose transition-all duration-200 !text-[14px] xl:m-3 m-2" th:utext="${doctor.achievements}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -602,5 +599,38 @@
|
||||
<!--/$-->undefined
|
||||
<!--$-->undefined
|
||||
<!--/$-->undefined </main>undefined</div>undefined
|
||||
<th:block layout:fragment="scripts">
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var doctorCode = /*[[${doctor.doctorCode}]]*/ 'default';
|
||||
if (doctorCode && doctorCode !== 'default') {
|
||||
const container = document.getElementById('schedule-' + doctorCode);
|
||||
if (container) {
|
||||
fetch('/api/public/doctor-schedule/' + doctorCode)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && data.length > 0) {
|
||||
let html = '<ul class="pl-0 mb-0 list-unstyled space-y-2">';
|
||||
data.forEach(day => {
|
||||
let dayText = day.dayOfWeek === 1 ? 'Chủ nhật' : 'Thứ ' + day.dayOfWeek;
|
||||
let dateText = day.date ? ' <span class="text-gray-500">(' + day.date + ')</span>' : '';
|
||||
html += '<li class="flex items-center gap-2"><i class="fas fa-check-circle text-green-500"></i> <strong>' + dayText + '</strong>' + dateText + '</li>';
|
||||
});
|
||||
html += '</ul>';
|
||||
container.innerHTML = html;
|
||||
} else {
|
||||
container.innerHTML = '<span class="text-gray-500 italic">Bác sĩ chưa có lịch khám tuần này</span>';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
container.innerHTML = '<span class="text-red-500">Lỗi tải dữ liệu lịch khám</span>';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
/*]]>*/
|
||||
</script>
|
||||
</th:block>
|
||||
</body>undefined
|
||||
</html>
|
||||
@@ -55,11 +55,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="xl:px-6 px-4 xl:pb-6 pb-4 xl:mt-3 mt-2 flex items-center justify-between gap-x-2">
|
||||
<a class="btn btn-outline-primary !inline-flex w-[149px] text-center !px-0 group items-center justify-center gap-1.5" th:href="@{/dat-lich}">
|
||||
<a class="btn btn-outline-primary !inline-flex w-[149px] text-center !px-0 group items-center justify-center gap-1.5" style="text-decoration: none;" th:href="@{/bac-si/{code}(code=${doc.doctorCode})}">
|
||||
<div class="text-white">Đặt lịch khám</div>
|
||||
<svg class="text-white" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="4" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg>
|
||||
</a>
|
||||
<a class="btn btn-outline-primary !inline-flex w-[149px] text-center !px-0 group items-center justify-center gap-1.5" href="tel:18001115">
|
||||
<a class="btn btn-outline-primary !inline-flex w-[149px] text-center !px-0 group items-center justify-center gap-1.5" style="text-decoration: none;" href="tel:18001115">
|
||||
<div class="text-white">Liên hệ</div>
|
||||
<svg class="text-white" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
|
||||
</a>
|
||||
|
||||
@@ -67,6 +67,35 @@
|
||||
placeholder="e.g. BS. CKII">
|
||||
</div>
|
||||
|
||||
<!-- Position -->
|
||||
<div class="form-group">
|
||||
<label for="doctorPosition" class="font-weight-bold">Position</label>
|
||||
<input type="text" class="form-control" id="doctorPosition" th:field="*{position}"
|
||||
placeholder="e.g. Trưởng khoa">
|
||||
</div>
|
||||
|
||||
<!-- Gender -->
|
||||
<div class="form-group">
|
||||
<label for="doctorGender" class="font-weight-bold">Gender</label>
|
||||
<select class="form-control" id="doctorGender" th:field="*{gender}">
|
||||
<option value="">— Select Gender —</option>
|
||||
<option value="nam">Nam</option>
|
||||
<option value="nữ">Nữ</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div class="form-group">
|
||||
<label for="doctorPhone" class="font-weight-bold">Phone Number</label>
|
||||
<input type="text" class="form-control" id="doctorPhone" th:field="*{phoneNumber}">
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="form-group">
|
||||
<label for="doctorEmail" class="font-weight-bold">Email</label>
|
||||
<input type="email" class="form-control" id="doctorEmail" th:field="*{email}">
|
||||
</div>
|
||||
|
||||
<!-- Specialty -->
|
||||
<div class="form-group">
|
||||
<label for="specialtyId" class="font-weight-bold">Specialty</label>
|
||||
@@ -122,6 +151,14 @@
|
||||
placeholder="https://booking.bvdaihoc.com.vn/...">
|
||||
</div>
|
||||
|
||||
<!-- Active (Show on Web) -->
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="doctorActive" th:field="*{active}">
|
||||
<label class="custom-control-label font-weight-bold" for="doctorActive">Show on Web (Active)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a th:if="${!isNew}" th:href="@{/manage/doctors}" class="btn btn-secondary btn-sm">
|
||||
@@ -156,11 +193,12 @@
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th width="5%">ID</th>
|
||||
<th width="30%">Name</th>
|
||||
<th width="15%">Title</th>
|
||||
<th width="20%">Specialty</th>
|
||||
<th width="10%">Active</th>
|
||||
<th width="5%">Booking</th>
|
||||
<th width="20%">Name</th>
|
||||
<th width="15%">Title/Position</th>
|
||||
<th width="10%">Gender</th>
|
||||
<th width="15%">Contact</th>
|
||||
<th width="15%">Specialty</th>
|
||||
<th width="5%">Active</th>
|
||||
<th width="15%">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -171,7 +209,13 @@
|
||||
<strong th:text="${doc.name}"></strong>
|
||||
</td>
|
||||
<td>
|
||||
<span th:text="${doc.title}"></span>
|
||||
<span th:text="${doc.title}"></span><br>
|
||||
<small class="text-muted" th:text="${doc.position}"></small>
|
||||
</td>
|
||||
<td th:text="${doc.gender}"></td>
|
||||
<td>
|
||||
<small th:if="${doc.phoneNumber != null && !doc.phoneNumber.isEmpty()}" th:text="${doc.phoneNumber}"></small><br th:if="${doc.phoneNumber != null && !doc.phoneNumber.isEmpty()}">
|
||||
<small th:if="${doc.email != null && !doc.email.isEmpty()}" th:text="${doc.email}"></small>
|
||||
</td>
|
||||
<td>
|
||||
<span th:if="${doc.specialty != null}" th:text="${doc.specialty.name}" class="badge badge-info"></span>
|
||||
@@ -181,10 +225,6 @@
|
||||
<span th:if="${doc.active}" class="badge badge-success">Active</span>
|
||||
<span th:if="${!doc.active}" class="badge badge-secondary">Inactive</span>
|
||||
</td>
|
||||
<td>
|
||||
<a th:if="${doc.bookingUrl != null && !doc.bookingUrl.isEmpty()}" th:href="${doc.bookingUrl}" target="_blank" class="badge badge-success">Link</a>
|
||||
<span th:if="${doc.bookingUrl == null || doc.bookingUrl.isEmpty()}" class="text-muted">—</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a th:href="@{/manage/doctors/{id}/edit(id=${doc.id})}"
|
||||
class="btn btn-sm btn-outline-info mr-1" title="Edit">
|
||||
|
||||
@@ -60,5 +60,7 @@
|
||||
<script th:src="@{/js/ambient-video.js}"></script>
|
||||
<!-- wp_footer hook -->
|
||||
<th:block th:utext="${hookManager.doActionAndReturn('wp_footer')}"></th:block>
|
||||
<!-- Page Scripts Fragment -->
|
||||
<th:block layout:fragment="scripts"></th:block>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,86 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
import csv
|
||||
import sys
|
||||
import re
|
||||
|
||||
def clean_html(raw_html):
|
||||
if not raw_html:
|
||||
return ""
|
||||
cleanr = re.compile('<.*?>')
|
||||
cleantext = re.sub(cleanr, '', raw_html)
|
||||
return cleantext.strip().replace('\n', ' ')
|
||||
|
||||
try:
|
||||
tree = ET.parse('/home/x79/sisvietnamvn_01/sisvietnamvn_Trang chính thức hiện tại/bnhvinakhoaquctsiscnth.WordPress.2026-07-28 (5).xml')
|
||||
root = tree.getroot()
|
||||
except Exception as e:
|
||||
print(f"Error parsing XML: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
ns = {
|
||||
'wp': 'http://wordpress.org/export/1.2/',
|
||||
'content': 'http://purl.org/rss/1.0/modules/content/'
|
||||
}
|
||||
|
||||
# First pass to build a dictionary of attachment URLs
|
||||
attachments = {}
|
||||
for channel in root.findall('channel'):
|
||||
for item in channel.findall('item'):
|
||||
post_type_el = item.find('wp:post_type', ns)
|
||||
if post_type_el is not None and post_type_el.text == 'attachment':
|
||||
post_id_el = item.find('wp:post_id', ns)
|
||||
attachment_url_el = item.find('wp:attachment_url', ns)
|
||||
if post_id_el is not None and attachment_url_el is not None:
|
||||
attachments[post_id_el.text] = attachment_url_el.text
|
||||
|
||||
csv_path = '/home/x79/sisvietnamvn_01/sisvietnamvn_main/doctors_from_xml.csv'
|
||||
with open(csv_path, 'w', newline='', encoding='utf-8') as csvfile:
|
||||
fieldnames = ['Name', 'Position', 'Gender', 'Birthday', 'Phone', 'Email', 'Address', 'Order', 'ImageID', 'ImageURL', 'Content']
|
||||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
|
||||
for channel in root.findall('channel'):
|
||||
for item in channel.findall('item'):
|
||||
post_type_el = item.find('wp:post_type', ns)
|
||||
if post_type_el is not None and post_type_el.text == 'employee':
|
||||
title = item.find('title')
|
||||
name = title.text if title is not None else ""
|
||||
|
||||
content_el = item.find('content:encoded', ns)
|
||||
content = clean_html(content_el.text) if content_el is not None else ""
|
||||
|
||||
doctor_data = {
|
||||
'Name': name,
|
||||
'Position': '',
|
||||
'Gender': '',
|
||||
'Birthday': '',
|
||||
'Phone': '',
|
||||
'Email': '',
|
||||
'Address': '',
|
||||
'Order': '',
|
||||
'ImageID': '',
|
||||
'ImageURL': '',
|
||||
'Content': content[:200] # truncate for CSV view
|
||||
}
|
||||
|
||||
for meta in item.findall('wp:postmeta', ns):
|
||||
key_el = meta.find('wp:meta_key', ns)
|
||||
val_el = meta.find('wp:meta_value', ns)
|
||||
|
||||
if key_el is not None and key_el.text and val_el is not None:
|
||||
k = key_el.text
|
||||
v = val_el.text or ""
|
||||
if k == 'position': doctor_data['Position'] = v
|
||||
elif k == 'gender': doctor_data['Gender'] = v
|
||||
elif k == 'birthday': doctor_data['Birthday'] = v
|
||||
elif k == 'phone_number': doctor_data['Phone'] = v
|
||||
elif k == 'email': doctor_data['Email'] = v
|
||||
elif k == 'address': doctor_data['Address'] = v
|
||||
elif k == 'order': doctor_data['Order'] = v
|
||||
elif k == 'image':
|
||||
doctor_data['ImageID'] = v
|
||||
doctor_data['ImageURL'] = attachments.get(v, '')
|
||||
|
||||
writer.writerow(doctor_data)
|
||||
|
||||
print(f"Successfully wrote CSV to {csv_path}")
|
||||