diff --git a/sisvietnamvn_Trang chính thức hiện tại/bnhvinakhoaquctsiscnth.WordPress.2026-07-13.xml b/sisvietnamvn_Trang chính thức hiện tại/bnhvinakhoaquctsiscnth.WordPress.2026-07-13.xml new file mode 100644 index 00000000..2c3438f5 --- /dev/null +++ b/sisvietnamvn_Trang chính thức hiện tại/bnhvinakhoaquctsiscnth.WordPress.2026-07-13.xml @@ -0,0 +1,1369 @@ + + + + + + + + + + + + + + + + + + + + + + + Bệnh Viện Đa Khoa Quốc Tế S.I.S Cần Thơ + https://sisvietnam.vn + Cứu tinh cho bệnh nhân đột quỵ + Mon, 13 Jul 2026 12:29:06 +0000 + vi + 1.2 + https://sisvietnam.vn + https://sisvietnam.vn + + 9 + 12 + 18 + 13 + + + https://wordpress.org/?v=5.7.15 + + + https://sisvietnam.vn/wp-content/uploads/2018/08/cropped-favicon-1-32x32.png + Bệnh Viện Đa Khoa Quốc Tế S.I.S Cần Thơ + https://sisvietnam.vn + 32 + 32 + + + + <![CDATA[Phẫu thuật nội soi giúp người phụ nữ thoát nguy cơ liệt chân do thoát vị đĩa đệm hiếm gặp]]> + https://sisvietnam.vn/phau-thuat-noi-soi-giup-nguoi-phu-nu-thoat-nguy-co-liet-chan-do-thoat-vi-dia-dem-hiem-gap/ + Wed, 01 Jul 2026 03:56:40 +0000 + + https://sisvietnam.vn/?p=16116 + + +

+ + + +

Mới đây, Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ đã phẫu thuật nội soi cột sống cho một bệnh nhân bị thoát vị đĩa đệm L1–L2 hiếm gặp, nằm sát tủy sống và có nguy cơ gây chèn ép thần kinh trung ương nặng nếu chậm xử trí. Trước đó, bệnh nhân xuất hiện đau lưng kèm yếu nhanh hai chân, gần như mất khả năng đi lại trong thời gian ngắn.

+ + + +

+ + + +

Theo chia sẻ của chị T.T.L (42 tuổi, ngụ xã Trường Thành, TP. Cần Thơ), bệnh khởi phát sau một lần mang vác thùng hàng nặng khoảng 5kg. Ban đầu chỉ là những cơn đau lưng thoáng qua nên chị chủ quan.

+ + + +

“Tôi đi chích thuốc, uống thuốc rồi điều trị nhiều cách nhưng không hết. Càng ngày chân càng yếu, đi lại khó khăn hơn. Sau đó tôi phải vịn đồ đạc trong nhà để đi, rồi đến mức muốn ngồi dậy cũng phải nhờ chồng đỡ”, chị L. kể lại.

+ + + +

Do lo sợ phẫu thuật cột sống nên người bệnh tiếp tục trì hoãn việc điều trị chuyên sâu. Tuy nhiên, tình trạng ngày càng nặng hơn, xuất hiện yếu hai chân tiến triển khiến chị gần như không còn khả năng tự vận động.

+ + + +

Sau khi đến khám tại Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ, kết quả chụp cộng hưởng từ (MRI) cho thấy bệnh nhân bị thoát vị đĩa đệm tầng L1-L2, khối thoát vị chèn ép nghiêm trọng vào cấu trúc thần kinh trung tâm.

+ + + +
Phẫu thuật nội soi giúp người phụ nữ thoát nguy cơ liệt chân do thoát vị đĩa đệm hiếm gặp
+ + + +

Yếu hai chân là dấu hiệu cảnh báo cần được can thiệp khẩn cấp

+ + + +

BS.CKI Nguyễn Quang Hưng – Trưởng Đơn vị Phẫu thuật Thần kinh, Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ cho biết, đa số trường hợp thoát vị đĩa đệm không cần phẫu thuật và có thể điều trị bảo tồn. Tuy nhiên, khi xuất hiện các dấu hiệu thần kinh nặng như yếu cơ hoặc giảm khả năng vận động, người bệnh cần được đánh giá chuyên khoa sớm.

+ + + +

“Điều quan trọng là phải phân biệt giữa đau khiến bệnh nhân không dám cử động và yếu cơ thật sự. Nếu sau khi giảm đau mà bệnh nhân vẫn không thể nhấc chân lên được thì đây là dấu hiệu tổn thương thần kinh đáng lo ngại. Trong những trường hợp như vậy, việc can thiệp sớm có ý nghĩa rất lớn đối với khả năng phục hồi vận động của người bệnh”- BS Hưng nhận định.

+ + + +
Phẫu thuật nội soi giúp người phụ nữ thoát nguy cơ liệt chân do thoát vị đĩa đệm hiếm gặp
+ + + +

Theo bác sĩ, vị trí L1-L2 là dạng thoát vị tương đối hiếm gặp. Trong khi phần lớn các ca thoát vị đĩa đệm xuất hiện ở tầng L4-L5 hoặc L5-S1 do chịu tải trọng lớn của cơ thể, thì tầng L1-L2 nằm gần tủy sống. Vì vậy, chỉ cần một khối thoát vị không quá lớn cũng có thể gây chèn ép nghiêm trọng, dẫn đến đau dữ dội, yếu hai chân hoặc nguy cơ tàn phế nếu điều trị muộn.

+ + + +

Nội soi qua lỗ liên hợp – giải pháp tối ưu cho vị trí tổn thương phức tạp

+ + + +

Sau hội chẩn chuyên môn, ê-kíp phẫu thuật quyết định lựa chọn kỹ thuật nội soi cột sống qua lỗ liên hợp để xử lý khối thoát vị.

+ + + +

Theo BS Hưng, đây là kỹ thuật ít xâm lấn, tận dụng đường vào tự nhiên của cột sống để tiếp cận trực tiếp vị trí tổn thương, giúp hạn chế tối đa việc tác động lên các cấu trúc thần kinh và mô lành xung quanh.

+ + + +

“Ở vị trí gần tủy sống như trường hợp này, nếu tiếp cận bằng phương pháp truyền thống sẽ gặp nhiều khó khăn hơn và có nguy cơ ảnh hưởng đến các cấu trúc thần kinh. Với kỹ thuật nội soi qua lỗ liên hợp, chúng tôi chỉ cần mài một phần rất nhỏ cấu trúc xương để mở rộng đường vào, từ đó tiếp cận chính xác khối thoát vị và giải phóng chèn ép hiệu quả”- BS Hưng cho biết.

+ + + +

Ca phẫu thuật được triển khai khẩn trương nhờ hệ thống nội soi cột sống hiện đại cùng quy trình cấp cứu được đầu tư đồng bộ tại Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ.

+ + + +

Phục hồi nhanh sau phẫu thuật

+ + + +

Theo ghi nhận của ê-kíp điều trị, ngay sau phẫu thuật, mức độ đau của bệnh nhân giảm khoảng 80-90%, tình trạng tê chân cải thiện rõ rệt. Người bệnh có thể tự trở mình, nâng chân và tập đi lại thuận lợi.

+ + + +

“Tỉnh dậy sau mổ, tôi thử cử động rồi nhấc chân lên được. Cảm giác đau giảm hẳn khiến tôi rất bất ngờ. Bây giờ tôi có thể ngồi, đi lại gần như bình thường. Nếu biết bệnh nặng như vậy từ sớm thì tôi đã không đi điều trị lòng vòng suốt gần hai tháng” - chị L. chia sẻ.

+ + + +

Không nên chủ quan với các dấu hiệu thần kinh

+ + + +

Các chuyên gia khuyến cáo, đau lưng kéo dài kèm theo tê chân, yếu chân, đi lại khó khăn hoặc giảm khả năng vận động là những dấu hiệu cảnh báo quan trọng mà người dân không nên bỏ qua.

+ + + +

Việc thăm khám sớm tại các cơ sở có chuyên khoa sâu về thần kinh – cột sống giúp xác định chính xác nguyên nhân, lựa chọn phương pháp điều trị phù hợp và tránh bỏ lỡ “thời điểm vàng” phục hồi chức năng vận động.

+ + + +

Với hệ thống phẫu thuật nội soi cột sống hiện đại cùng đội ngũ bác sĩ nhiều kinh nghiệm, Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ hiện đang triển khai thường quy nhiều kỹ thuật ít xâm lấn trong điều trị bệnh lý cột sống, giúp người bệnh giảm đau nhanh, rút ngắn thời gian nằm viện và sớm trở lại cuộc sống bình thường.

+ + + +

Kim Cương, ảnh: Đức Thịnh, Tuấn Anh

+]]>
+ + 16116 + + + + + + + + + 0 + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + <![CDATA[Chạy đua với thời gian cứu nam bác sĩ xuất huyết não do vỡ túi phình, hôn mê sâu]]> + https://sisvietnam.vn/chay-dua-voi-thoi-gian-cuu-nam-bac-si-xuat-huyet-nao-do-vo-tui-phinh-hon-me-sau/ + Thu, 02 Jul 2026 03:15:00 +0000 + + https://sisvietnam.vn/?p=16144 + + +

+ + + +

Từ người trực tiếp cứu chữa bệnh nhân, nam bác sĩ bất ngờ trở thành người bệnh khi rơi vào hôn mê sâu do vỡ túi phình mạch máu não. Trong tình thế "ngàn cân treo sợi tóc", cuộc chạy đua với thời gian của nhiều chuyên khoa tại Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ đã giúp người bệnh vượt qua cửa tử và từng bước hồi phục.

+ + + +

+ + + +

Nam bác sĩ, 51 tuổi được chuyển đến Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ trong tình trạng đột quỵ nguy kịch. Khi nhập viện, bệnh nhân hôn mê sâu, thang điểm Glasgow chỉ khoảng 6 điểm, không mở mắt, tay chân chỉ còn đáp ứng rất yếu.

+ + + +

Kết quả chẩn đoán hình ảnh khẩn cấp xác định bệnh nhân bị xuất huyết não lượng nhiều do vỡ túi phình mạch máu não, một trong những cấp cứu thần kinh có tỷ lệ tử vong rất cao.

+ + + +
Chạy đua với thời gian cứu nam bác sĩ xuất huyết não do vỡ túi phình, hôn mê sâu
+ + + +

Cuộc chạy đua giành sự sống từ sự phối hợp đa chuyên khoa

+ + + +

Ngay sau khi kích hoạt quy trình báo động đỏ, các ê-kíp Cấp cứu, Chẩn đoán hình ảnh, Nội thần kinh, Can thiệp mạch não, Ngoại Thần kinh và Hồi sức tích cực đã khẩn trương hội chẩn, thống nhất chiến lược điều trị. Bệnh nhân được can thiệp nội mạch xử trí túi phình, tiếp tục đặt dẫn lưu não thất và hồi sức tích cực tại ICU.

+ + + +

Theo BS Trần Hữu Phước – Khoa Phẫu thuật gây mê hồi sức (ICU), mặc dù tiên lượng ban đầu rất nặng, nhưng nhờ được tiếp cận điều trị kịp thời và phối hợp đa chuyên khoa chặt chẽ, người bệnh đã có những chuyển biến tích cực.

+ + + +

Hiện, sau gần 10 ngày điều trị tích cực, bệnh nhân đã dần tỉnh, thực hiện được y lệnh, được chỉ định rút nội khí quản và tiếp tục chương trình phục hồi chức năng. Kết quả chụp kiểm tra cho thấy lượng máu xuất huyết đã giảm đáng kể, túi phình gây chảy máu đã được xử trí hoàn toàn.

+ + + +
Chạy đua với thời gian cứu nam bác sĩ xuất huyết não do vỡ túi phình, hôn mê sâu
+ + + +

"Đây là trường hợp nếu không được can thiệp kịp thời thì nguy cơ tái xuất huyết và tử vong là rất cao. Kết quả điều trị hôm nay là sự phối hợp nhịp nhàng của nhiều chuyên khoa, từ tiếp nhận cấp cứu, chẩn đoán hình ảnh, can thiệp mạch não, ngoại thần kinh đến hồi sức tích cực", BS Trần Hữu Phước chia sẻ.

+ + + +

Các chuyên gia cũng khuyến cáo, vỡ túi phình động mạch não là tình trạng cấp cứu tối khẩn, có thể xảy ra đột ngột ngay cả ở những người chưa từng được chẩn đoán bệnh lý mạch máu não. Khi xuất hiện các dấu hiệu như đau đầu dữ dội đột ngột, nôn ói, rối loạn ý thức, yếu liệt hoặc hôn mê, người bệnh cần được đưa đến cơ sở có khả năng cấp cứu đột quỵ và can thiệp thần kinh càng sớm càng tốt. Mỗi phút chậm trễ đều có thể làm mất đi cơ hội sống và phục hồi của người bệnh.

+ + + +

Trường hợp trên thêm một lần cho thấy vai trò quyết định của mô hình điều trị đa chuyên khoa, cùng hệ thống chẩn đoán và can thiệp thần kinh hiện đại, trong việc nâng cao cơ hội cứu sống và giảm di chứng cho những ca xuất huyết não do vỡ túi phình vốn được xem là một trong những thách thức lớn nhất của chuyên ngành thần kinh – đột quỵ.

+ + + +

Kim Cương, ảnh: Đức Thịnh, Tuấn Anh

+]]>
+ + 16144 + + + + + + + + + 0 + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + <![CDATA[[CẦN THƠ] _ BẢN TIN TUYỂN DỤNG THÁNG 07/2026]]> + https://sisvietnam.vn/can-tho-_-ban-tin-tuyen-dung-thang-07-2026/ + Sat, 04 Jul 2026 02:52:44 +0000 + + https://sisvietnam.vn/?p=16150 + + +

ỨNG VIÊN NỘP HỒ SƠ TẠI ĐÂY: https://forms.gle/pZA5YhLAVeaYFsgw7

+ + + +

THEO DÕI FANPAGE ĐỂ CẬP NHẬT CÁC TIN TUYỂN DỤNG MỚI NHẤT:

+ + + +

Tuyển dụng - Đào tạo Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ

+ + + +

CÁC VỊ TRÍ TUYỂN DỤNG:

+ + + +

A – KHỐI CHUYÊN MÔN

+ + + +

I) BÁC SĨ:

+ + + +

Yêu cầu chung: Bác sĩ Đại học đã có Giấy phép hành nghề chuyên khoa hoặc Bác sĩ sau Đại học đã tốt nghiệp/sắp tốt nghiệp.

+ + + +

1) TRƯỞNG KHOA KIỂM SOÁT NHIỄM KHUẨN

+ + + +

2) BÁC SĨ MẮT

+ + + +

3) BÁC SĨ CẤP CỨU

+ + + +

4) BÁC SĨ ICU

+ + + +

5) BÁC SĨ NỘI THẦN KINH

+ + + +

6) BÁC SĨ NGOẠI THẦN KINH

+ + + +

7) BÁC SĨ DINH DƯỠNG Tuyển gấp

+ + + +

8) BÁC SĨ CHẨN ĐOÁN HÌNH ẢNH

+ + + +

9) BÁC SĨ THÚ YMới

+ + + +

II) ĐIỀU DƯỠNG - KỸ THUẬT VIÊN - CẬN LÂM SÀNG:

+ + + +

1) ĐIỀU DƯỠNG VIÊN Tuyển gấp & nhận hồ sơ liên tục

+ + + +

2) ĐIỀU DƯỠNG GÂY MÊ HỒI SỨC Tuyển gấp

+ + + +

3) ĐIỀU DƯỠNG DỤNG CỤ PHÒNG MỔ Tuyển gấp

+ + + +

4) KỸ THUẬT VIÊN CHẨN ĐOÁN HÌNH ẢNH

+ + + +

5) CHUYÊN VIÊN DINH DƯỠNG Tuyển gấp

+ + + +

CHẾ ĐỘ HỖ TRỢ ĐẶC BIỆT ĐỐI VỚI VỊ TRÍ ĐIỀU DƯỠNG, KỸ THUẬT VIÊN (nếu phỏng vấn đạt)

+ + + +

Mở rộng cơ hội cho sinh viên mới tốt nghiệp hoặc sắp tốt nghiệp, chưa có kinh nghiệm hoặc chưa có Giấy phép hành nghề.

+ + + +

Khi có Giấy xác nhận tốt nghiệp tạm thời hoặc Giấy xác nhận đã hoàn thành chương trình học là ứng viên có thể ứng tuyển (Còn bằng tốt nghiệp chính thức thì bổ sung sau).

+ + + +

Được miễn phí đào tạo và xác nhận thực hành để cấp Giấy phép hành nghề.

+ + + +

Được hưởng lương và các chế độ hỗ trợ từ Bệnh viện trong suốt quá trình thực hành tại bệnh viện.

+ + + +

Được là nhân viên cơ hữu của Bệnh viện sau khi thực hành xong.

+ + + +

B - KHỐI NGHIỆP VỤ:

+ + + +

1) TRỢ LÝ GIÁM ĐỐC ĐIỀU HÀNH

+ + + +

2) CHUYÊN VIÊN SALES Tuyển gấp

+ + + +

3) CHUYÊN VIÊN SALES MARKETING Tuyển gấp

+ + + +

4) CHUYÊN VIÊN KIỂM TRA HỒ SƠ BỆNH ÁN Tuyển gấp

+ + + +

5) CHUYÊN VIÊN RÀ SOÁT DANH MỤC BẢO HIỂM Y TẾ Tuyển gấp

+ + + +

6) NHÂN VIÊN BẢO TRÌ ĐIỆN, ĐIỆN LẠNH Nhận hồ sơ liên tục

+ + + +

7) NHÂN VIÊN BẢO TRÌ THIẾT BỊ Y TẾ Nhận hồ sơ liên tục

+ + + +

8) CHUYÊN VIÊN KẾ TOÁN KHO

+ + + +

9) CHUYÊN VIÊN KẾ HOẠCH TỔNG HỢP

+ + + +

THÔNG TIN LIÊN HỆ:

+ + + +

– Bộ phận tuyển dụng (Mobile/Zalo): 0862 075 115 (trong giờ hành chính)

+ + + +

– Hotline (cước gọi miễn phí – liên hệ 24/7): 18001115

+ + + +

– Email tuyển dụng: hr@dotquy.vn

+ + + +

– Website: http://sisvietnam.vn

+ + + +

– Địa điểm làm việc: BỆNH VIỆN ĐA KHOA QUỐC TẾ S.I.S CẦN THƠ (số 397, Nguyễn Văn Cừ, phường An Bình, thành phố Cần Thơ).

+ + + +

THỜI GIAN TỔ CHỨC PHỎNG VẤN: DỰ KIẾN TRONG THÁNG 07/2026 HOẶC KHI CÓ ĐỦ SỐ LƯỢNG HỒ SƠ PHÙ HỢP.

+ + + +

LƯU Ý:

+ + + +

– Thông thường hồ sơ ứng tuyển của các vị trí sẽ được xem xét trong vòng 7 ngày đến 20 ngày, kể từ ngày ứng tuyển (tùy từng vị trí).

+ + + +

- Đối với các hồ sơ phù hợp, Phòng Nhân sự sẽ liên hệ để mời tham gia phỏng vấn.

+ + + +

– Đối với các hồ sơ chưa đạt yêu cầu hoặc chưa cần tuyển cho nhu cầu hiện tại, Phòng Nhân sự sẽ không liên hệ lại và sẽ lưu lại hồ sơ cho các vị trí phù hợp hơn trong tương lai.

+ + + +

CHẾ ĐỘ PHÚC LỢI: HẤP DẪN, CẠNH TRANH

+ + + +

1. Lương, Thưởng:

+ + + +

– Mức lương: Thỏa thuận theo năng lực

+ + + +

– Lương tháng 13

+ + + +

– Thưởng Tết âm lịch

+ + + +

– Thưởng Tết dương lịch

+ + + +

– Thưởng theo hiệu quả kinh doanh

+ + + +

– Xem xét tăng lương định kỳ

+ + + +

– Phụ cấp thâm niên

+ + + +

– Thưởng vào các dịp lễ lớn: 30/04 & 01/05, 02/09

+ + + +

– Thưởng ngày Thầy thuốc Việt nam (27/02)

+ + + +

2. Chăm sóc sức khỏe:

+ + + +

- Được tham gia bảo hiểm đầy đủ (Bảo hiểm xã hội, Bảo hiểm y tế, Bảo hiểm thất nghiệp)

+ + + +

- Được đăng ký tham gia BHYT tại bệnh viện ĐKQT S.I.S Cần Thơ

+ + + +

- Được khám sức khoẻ định kỳ miễn phí tại bệnh viện ĐKQT S.I.S Cần Thơ

+ + + +

- Được miễn giảm chi phí khám chữa bệnh cho bản thân nhân viên và người thân (tùy mối quan hệ & khi đủ điều kiện về thâm niên làm việc)

+ + + +

- Được chụp MRI Não miễn phí (đối với cán bộ nhân viên có thâm niên làm việc tại bệnh viện từ 5 năm trở lên)

+ + + +

- Được miễn giảm chi phí chụp MRI cho cán bộ nhân viên và người thân (tùy mối quan hệ và tùy theo chương trình hỗ trợ theo từng thời điểm)

+ + + +

- Được hỗ trợ từ chương trình của Quỹ Từ thiện (tùy hoàn cảnh)

+ + + +

3. Quà tặng:

+ + + +

– Quà Tết

+ + + +

– Quà Trung Thu

+ + + +

– Quà sinh nhật

+ + + +

– Hưởng chế độ hiếu, hỷ

+ + + +

– Quà 08/03, Quà 20/10 (đối với cán bộ nhân viên nữ)

+ + + +

– Chương trình chăm lo cho con của cán bộ nhân viên bệnh viện như tổ chức chương trình vui chơi/quà tặng nhân ngày Quốc tế thiếu nhi 1/6, Tết trung thu

+ + + +

4. Chế độ khác:

+ + + +

– Cơ hội thăng tiến rộng mở và không giới hạn

+ + + +

– Đào tạo: được đào tạo liên tục để nâng cao năng lực chuyên môn và kỹ năng. Đặc biệt, sẽ được đào tạo, xác nhận thực hành để cấp giấy phép hành nghề theo quy định (đối với khối chuyên môn)

+ + + +

– Môi trường làm việc chuyên nghiệp, tiếp cận các dịch vụ kỹ thuật cao cùng với các trang thiết bị hiện đại; nhiều cơ hội học hỏi, phát triển

+ + + +

– Đi du dịch hàng năm (trong hoặc ngoài nước)

+ + + +

– Hỗ trợ ăn trưa, cơm trực, cơm tăng ca

+ + + +

– Được cấp đồng phục miễn phí hàng năm

+ + + +

– Công tác phí (ăn uống, đi lại, lưu trú) theo quy định công tác phí của bệnh viện

+ + + +

– Gửi xe miễn phí

+ + + +

– Và các chế độ khác theo luật định và tùy theo tình hình hoạt động của bệnh viện

+]]>
+ + 16150 + + + + + + + + + 0 + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + <![CDATA[Đau đầu chóng mặt suốt 20 ngày, cụ bà u80 phát hiện khối nấm xoang bướm xâm lấn nền sọ, chèn ép động mạch cảnh]]> + https://sisvietnam.vn/dau-dau-chong-mat-suot-20-ngay-cu-ba-u80-phat-hien-khoi-nam-xoang-buom-xam-lan-nen-so-chen-ep-dong-mach-canh/ + Mon, 06 Jul 2026 06:11:54 +0000 + + https://sisvietnam.vn/?p=16159 + + +

+ + + +

Đau đầu dữ dội kéo dài gần ba tuần nhưng điều trị không thuyên giảm, cụ bà U80 được phát hiện mắc viêm xoang bướm do nấm đã xâm lấn nền sọ, chèn ép động mạch cảnh trong và gây nhìn đôi. Ca bệnh được đánh giá đặc biệt nguy hiểm do tổn thương nằm sát nhiều cấu trúc thần kinh và mạch máu quan trọng.

+ + + +

+ + + +

Mới đây, các bác sĩ Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ đã điều trị thành công cho bà B.T.L. (78 tuổi, ngụ phường Hưng Phú, TP. Cần Thơ) sau gần 20 ngày đau đầu, chóng mặt kéo dài nhưng không tìm được nguyên nhân chính xác.

+ + + +

Khi nhập viện, bệnh nhân đau đầu dữ dội, nhìn đôi, nôn ói nhiều, kèm rối loạn điện giải do hạ kali máu và đường huyết chưa được kiểm soát.

+ + + +
+ + + +

Kết quả chụp cộng hưởng từ (MRI) não phát hiện tổn thương nằm trong xoang bướm, lan vào xoang hang và gây hẹp động mạch cảnh trong. Sau hội chẩn liên chuyên khoa kết hợp chụp cắt lớp vi tính (CT), các bác sĩ xác định bệnh nhân bị u nấm xoang bướm đã phá hủy thành xoang, xâm lấn nền sọ.

+ + + +

BS.CKI Trương Lê Anh Kiệt, chuyên khoa Tai Mũi Họng, Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ cho biết, viêm xoang bướm là bệnh lý ít gặp nhưng đặc biệt nguy hiểm do xoang bướm nằm sâu trong nền sọ, tiếp giáp nhiều cấu trúc sống còn như động mạch cảnh trong, dây thần kinh thị giác và các dây thần kinh vận nhãn. Khi tổn thương lan rộng, người bệnh có thể xuất hiện nhìn đôi, giảm thị lực, liệt vận nhãn, thậm chí đối mặt nguy cơ biến chứng nội sọ hoặc đe dọa tính mạng nếu ảnh hưởng đến các mạch máu lớn.

+ + + +
+ + + +

Ca bệnh càng trở nên thách thức khi bệnh nhân đang sử dụng thuốc kháng kết tập tiểu cầu kép (DAPT), làm tăng nguy cơ chảy máu trong phẫu thuật. Trong khi đó, khối nấm nằm sát động mạch cảnh trong đoạn xoang hang và nhiều cấu trúc thần kinh quan trọng. Chỉ cần sai lệch rất nhỏ trong quá trình bóc tách cũng có thể gây tổn thương mạch máu hoặc thần kinh với hậu quả nặng nề.

+ + + +

Sau khi cân nhắc kỹ lưỡng giữa nguy cơ và lợi ích, ê-kíp quyết định phẫu thuật nội soi kết hợp hệ thống định vị 3D Navigation. Công nghệ này giúp xác định chính xác vị trí tổn thương theo thời gian thực, hỗ trợ phẫu thuật viên định hướng trong không gian ba chiều với độ chính xác cao, đặc biệt hữu ích khi xử trí các tổn thương nằm sát nền sọ hoặc các cấu trúc thần kinh - mạch máu quan trọng.

+ + + +

Nhờ kinh nghiệm của ê-kíp cùng sự hỗ trợ của hệ thống định vị 3D Navigation, toàn bộ quá trình bóc tách và lấy khối tổn thương diễn ra an toàn, lượng máu mất rất ít dù bệnh nhân đang sử dụng thuốc làm tăng nguy cơ chảy máu. Đồng thời, các cấu trúc quan trọng như động mạch cảnh trong và vùng xoang hang được bảo tồn.

+ + + +

Theo BS.CKI Trương Lê Anh Kiệt, đây là một trong những ca phẫu thuật nhiều thách thức bởi tổn thương đã lan rộng vào nền sọ trên nền bệnh nhân lớn tuổi và có nhiều bệnh lý đi kèm. Thành công của ca mổ không chỉ nằm ở việc lấy sạch tổn thương mà còn phải kiểm soát nguy cơ chảy máu, đồng thời bảo tồn tối đa các cấu trúc thần kinh và mạch máu quan trọng.

+ + + +

Sau phẫu thuật, bệnh nhân giảm rõ rệt cơn đau đầu, không còn nhìn đôi, hết nôn ói và có thể tự ngồi ăn uống. Các chỉ số xét nghiệm dần trở về giới hạn bình thường, cho thấy quá trình hồi phục thuận lợi.

+ + + +
+ + + +

“Tôi cảm ơn bác sĩ cũng như mấy cô, mấy chú điều dưỡng ở đây nhiều lắm, nhiệt tình quá trời” – Bà B.T.L vui mừng nói sau khi đã được điều trị thành công.

+ + + +

Qua trường hợp này, các bác sĩ khuyến cáo người dân không nên chủ quan với tình trạng đau đầu kéo dài, đặc biệt khi kèm theo nhìn đôi, nhìn mờ, nôn ói hoặc sợ ánh sáng. Ở người lớn tuổi, những triệu chứng này không chỉ gợi ý bệnh lý thần kinh mà còn có thể là dấu hiệu của các tổn thương nguy hiểm vùng nền sọ, trong đó có viêm xoang bướm xâm lấn. Việc thăm khám sớm kết hợp các phương pháp chẩn đoán hình ảnh hiện đại như MRI, CT scan sẽ giúp phát hiện chính xác nguyên nhân gây bệnh, từ đó lựa chọn hướng điều trị phù hợp, hạn chế nguy cơ biến chứng nặng.

+ + + +

Theo BS.CKI Trương Lê Anh Kiệt, với sự phối hợp đa chuyên khoa cùng hệ thống chẩn đoán hình ảnh hiện đại và công nghệ định vị 3D Navigation trong phẫu thuật nội soi, thời gian qua Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ đã tiếp nhận và điều trị thành công nhiều trường hợp bệnh lý Tai Mũi Họng phức tạp, đặc biệt là các tổn thương mũi xoang xâm lấn nền sọ hoặc nằm sát những cấu trúc thần kinh, mạch máu quan trọng. Việc làm chủ các kỹ thuật chuyên sâu không chỉ giúp nâng cao độ chính xác trong phẫu thuật mà còn góp phần giảm nguy cơ biến chứng và mang lại cơ hội hồi phục tốt hơn cho người bệnh.

+ + + +

Kim Cương, ảnh: Đức Thịnh, Tuấn Anh

+]]>
+ + 16159 + + + + + + + + + 0 + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + <![CDATA[Phối hợp xuyên quốc gia cứu cậu bé 8 tuổi]]> + https://sisvietnam.vn/phoi-hop-xuyen-quoc-gia-cuu-cau-be-8-tuoi/ + Tue, 07 Jul 2026 08:53:47 +0000 + + https://sisvietnam.vn/?p=16166 + + +

Chuyến đi từ Campuchia sang Việt Nam đã mở ra cơ hội điều trị cho cậu bé 8 tuổi sau nhiều ngày đau đầu và nôn ói. Kết quả thăm khám xác định nguyên nhân đến từ một bệnh lý nguy hiểm trong não.

+ + + +

+ + + +

Tưởng con bị ngộ độc thực phẩm

+ + + +

Theo lời mẹ bé, trước khi đến trường, bé V.Đ.K ăn một ly chè rồi bất ngờ than đau một bên tai, nhìn mờ và nôn liên tục. Ban đầu, gia đình không hề nghĩ đến bệnh lý não.

+ + + +

Em cũng tưởng là giống như bị ngộ độc thực phẩm thôi, mẹ bé K. kể lại.

+ + + +

Gia đình đưa bé đến bệnh viện tại Campuchia để thăm khám. Sau một đêm, tình trạng nôn giảm nhưng bé bắt đầu đau đầu dữ dội và cơn đau kéo dài liên tục suốt hai ngày. Kết quả chụp CT xác định bé K. bị xuất huyết não, gia đình tin tưởng chuyển con sang Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ.

+ + + +

Trong suốt quãng đường di chuyển, bé vẫn tỉnh táo nhưng không thể ngồi dậy vì mỗi lần ngồi đều đau đầu dữ dội kèm nôn ói.

+ + + +

Không yếu liệt vẫn có thể là đột quỵ

+ + + +

BS.CKII Ngô Minh Tuấn - Trưởng đơn vị can thiệp mạch máu tạng ngoại biên, Bệnh viện S.I.S Cần Thơ cho biết, khi nhập viện, bé K. còn tỉnh, không có biểu hiện yếu liệt như nhiều người vẫn hình dung ở bệnh nhân đột quỵ.

+ + + +
+ + + +

Bệnh nhân chỉ duy nhất có một triệu chứng là đau đầu, và đau đầu rất nhiều, kèm nôn ói. Mặc dù đã dùng thuốc nhưng triệu chứng không giảm, bác sĩ Tuấn chia sẻ.

+ + + +

Kết quả chụp MRI 3 Tesla ghi nhận ổ xuất huyết tại thùy thái dương trái, nghi ngờ nguyên nhân do dị dạng động tĩnh mạch não.

+ + + +

Qua chụp DSA, chúng tôi xác nhận bệnh nhân có một dị dạng động tĩnh mạch ở thùy thái dương bên trái, đồng thời có nhiều túi giả phình bên trong, đây chính là nguyên nhân gây xuất huyết ở bệnh nhi, bác sĩ Tuấn cho biết.

+ + + +

Sau hội chẩn, ê-kíp can thiệp quyết định tắc ba nhánh mạch máu bất thường, ưu tiên xử lý trước các nhánh mang túi giả phình có nguy cơ vỡ cao nhất.

+ + + +
+ + + +

Chỉ một ngày sau can thiệp, bé K.đã tỉnh táo, cơn đau đầu giảm dần, ăn uống và sinh hoạt trở lại bình thường.

+ + + +

Thấy con hồi phục tốt, mẹ bé xúc động chia sẻ: Trong suốt thời gian bé can thiệp, em ngồi ở ngoài khóc suốt. Nghe tới xuất huyết não là mình sợ lắm rồi. Vậy mà các bác sĩ đã cứu con em thành công. Em cảm ơn các bác sĩ rất nhiều!

+ + + +

BS.CKII Ngô Minh Tuấn cũng đặc biệt gửi lời cảm ơn đến các đồng nghiệp tại Campuchia đã nhanh chóng nhận diện tình trạng bệnh, phối hợp chặt chẽ và tin tưởng chuyển bệnh nhi đến Bệnh viện Đa khoa Quốc tế S.I.S Cần Thơ. Theo bác sĩ, chính sự phối hợp kịp thời giữa hai đơn vị đã góp phần quan trọng giúp bé K. được can thiệp đúng thời điểm.

+ + + +

Xuất huyết não ở trẻ em không còn hiếm gặp

+ + + +

Theo BS.CKII Ngô Minh Tuấn, xuất huyết não ở trẻ em hiện nay không còn là tình trạng quá hiếm gặp. Nếu ở người lớn, xuất huyết não thường liên quan đến tăng huyết áp thì ở trẻ em, nguyên nhân chủ yếu là các bệnh lý mạch máu não như dị dạng mạch máu, dị dạng động tĩnh mạch hoặc huyết khối tĩnh mạch não.

+ + + +

Không phải bệnh nhi nào cũng yếu liệt hay hôn mê. Nhiều trường hợp chỉ có đau đầu dữ dội, nôn ói kéo dài hoặc đau đầu không rõ nguyên nhân, không cải thiện dù đã điều trị. Đây đều có thể là dấu hiệu cảnh báo một bệnh lý nguy hiểm trong não, bác sĩ Tuấn cho biết.

+ + + +

Theo bác sĩ, điều quan trọng không chỉ là xử lý tình trạng xuất huyết mà còn phải xác định và điều trị nguyên nhân. Nếu bệnh lý mạch máu não không được xử trí đúng cách, người bệnh vẫn có nguy cơ tái xuất huyết. Lần xuất huyết đầu có thể nhẹ và vượt qua, nhưng những lần sau sẽ làm tăng đáng kể nguy cơ tử vong và tàn phế.

+ + + +

Vì vậy, khi trẻ có biểu hiện đau đầu kéo dài, đau dữ dội, nôn ói, co giật hoặc các triệu chứng thần kinh bất thường, gia đình cần đưa trẻ đến cơ sở y tế có chuyên khoa để được chẩn đoán và điều trị kịp thời.

+ + + +

Huỳnh Anh, ảnh: Minh Đạt, Xuân Ngọc

+]]>
+ + 16166 + + + + + + + + + 0 + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + <![CDATA[Rối loạn nhịp tim kéo dài khiến người đàn ông từng đột quỵ, chức năng tim dần hồi phục sau triệt đốt điện sinh lý]]> + https://sisvietnam.vn/roi-loan-nhip-tim-keo-dai-khien-nguoi-dan-ong-tung-dot-quy-chuc-nang-tim-dan-hoi-phuc-sau-triet-dot-dien-sinh-ly/ + Wed, 08 Jul 2026 03:25:00 +0000 + + https://sisvietnam.vn/?p=16173 + + +

+ + + +

Từng bị đột quỵ não do rối loạn nhịp tim và nhiều lần điều trị nội khoa nhưng bệnh vẫn tái phát, người đàn ông tiếp tục nhập viện trong tình trạng hồi hộp, mệt mỏi kéo dài. Sau can thiệp đốt điện sinh lý tim bằng sóng cao tần, nhịp tim ổn định, chức năng co bóp cơ tim từng bước cải thiện, giảm nguy cơ suy tim và đột quỵ tái phát.

+ + + +
Rối loạn nhịp tim kéo dài khiến người đàn ông từng đột quỵ, chức năng tim dần hồi phục sau triệt đốt điện sinh lý
+ + + +

+ + + +

Từ những cơn tim đập nhanh đến hậu quả suy tim và đột quỵ

+ + + +

Không chỉ đơn thuần là cảm giác hồi hộp, những cơn tim đập nhanh xuất hiện nhiều lần đã âm thầm ảnh hưởng đến sức khỏe của ông T.V.V. (64 tuổi, xã Ngọc Chúc, tỉnh An Giang). Theo thời gian, người bệnh thường xuyên mệt mỏi, khó thở mỗi khi xuất hiện cơn rối loạn nhịp.

+ + + +

Đáng lo ngại hơn, khoảng ba năm trước, ông từng bị đột quỵ não. Dù được cấp cứu và điều trị kịp thời, biến cố này vẫn để lại di chứng nhất định.

+ + + +

Sau đột quỵ, bệnh nhân tiếp tục được điều trị nội khoa để kiểm soát rối loạn nhịp tim. Tuy nhiên, các cơn nhịp nhanh vẫn tái phát từng đợt, khiến tình trạng khó thở, mệt mỏi ngày càng rõ hơn và làm suy giảm chức năng tim.

+ + + +

Khi đến điều trị, bệnh nhân được xác định mắc nhịp nhanh kịch phát trên thất. Nhịp tim trong cơn có thời điểm lên tới khoảng 180–200 lần/phút. Tình trạng kéo dài khiến các buồng tim giãn, cơ tim suy yếu, chức năng co bóp chỉ còn khoảng 35%, thấp hơn nhiều so với mức bình thường khoảng 60–70%.

+ + + +

+ + + +
Rối loạn nhịp tim kéo dài khiến người đàn ông từng đột quỵ, chức năng tim dần hồi phục sau triệt đốt điện sinh lý
+ + + +

Hình ảnh minh họa trong lần đốt điện sinh lý tim.

+ + + +

Khi điều trị bằng thuốc không còn đủ

+ + + +

Theo BS. CKII Nguyễn Mạnh Cường – Phó khoa Nội tổng hợp của Bệnh viện, rối loạn nhịp tim kéo dài không chỉ gây khó chịu mà còn có thể dẫn đến những biến chứng nghiêm trọng như suy tim hoặc làm tăng nguy cơ đột quỵ nếu không được kiểm soát hiệu quả.

+ + + +

Trong trường hợp này, bệnh nhân đã được điều trị nội khoa tích cực nhưng vẫn liên tục xuất hiện các cơn nhịp nhanh. Đây là thời điểm cần cân nhắc phương pháp điều trị can thiệp nhằm giải quyết tận gốc ổ phát sinh rối loạn nhịp.

+ + + +

Tuy nhiên, ca bệnh cũng đặt ra không ít thách thức. Do tình trạng suy tim kéo dài, các buồng tim đã giãn lớn, khiến việc xác định chính xác vị trí ổ rối loạn nhịp trở nên khó khăn hơn so với những trường hợp thông thường.

+ + + +

Dấu ấn chuyên môn: Triệt đốt điện sinh lý bằng sóng cao tần

+ + + +

+ + + +

Trước bệnh cảnh phức tạp, ê-kíp quyết định thực hiện đốt điện sinh lý tim bằng sóng cao tần. BS.CKII Nguyễn Mạnh Cường cho biết, quá trình can thiệp được hỗ trợ bởi hệ thống lập bản đồ điện học buồng tim, giúp tái tạo hình ảnh giải phẫu của tim, xác định chính xác vị trí phát sinh rối loạn nhịp để tập trung triệt đốt.

+ + + +

Việc xác định đúng ổ rối loạn nhịp giúp rút ngắn thời gian can thiệp, giảm thời gian chiếu tia X cho người bệnh, đồng thời nâng cao độ chính xác và hiệu quả điều trị, đặc biệt ở những trường hợp có buồng tim giãn lớn hoặc cấu trúc tim thay đổi do suy tim kéo dài.

+ + + +

Sau can thiệp, nhịp tim của bệnh nhân trở về khoảng 60–70 lần/phút và duy trì ổn định.

+ + + +
Rối loạn nhịp tim kéo dài khiến người đàn ông từng đột quỵ, chức năng tim dần hồi phục sau triệt đốt điện sinh lý
+ + + +

Nam bệnh nhân phục hồi tốt sau can thiệp đốt điện sinh lý tim.

+ + + +

Đáng chú ý, chức năng co bóp của tim cũng cải thiện theo thời gian. Nếu trước điều trị, phân suất tống máu chỉ khoảng 35%, thì khi tái khám, chỉ số này đã tăng lên khoảng 40–50%, cho thấy cơ tim đang từng bước hồi phục sau khi nguyên nhân gây rối loạn nhịp được xử lý.

+ + + +

Sau 7 ngày điều trị, bệnh nhân xuất viện. Khi tái khám sau 20 ngày, sức khỏe phục hồi tốt, không còn tình trạng mệt mỏi như trước.

+ + + +

Không nên xem nhẹ những cơn tim đập nhanh

+ + + +

Theo BS.CKII Nguyễn Mạnh Cường, nhiều người thường chủ quan khi xuất hiện các cơn hồi hộp hoặc tim đập nhanh vì nghĩ chỉ là biểu hiện thoáng qua. Tuy nhiên, ở một số trường hợp, rối loạn nhịp tim kéo dài có thể khiến tim phải hoạt động quá sức, lâu dần dẫn đến giãn buồng tim, suy tim, thậm chí làm tăng nguy cơ đột quỵ.

+ + + +
Rối loạn nhịp tim kéo dài khiến người đàn ông từng đột quỵ, chức năng tim dần hồi phục sau triệt đốt điện sinh lý
+ + + +

BS. CKII Nguyễn Mạnh Cường – Phó khoa Nội tổng hợp trong phòng lab điều trị cho người bệnh.

+ + + +

+ + + +

Bác sĩ khuyến cáo, nếu thường xuyên xuất hiện các cơn tim đập nhanh, hồi hộp, đánh trống ngực, khó thở, mệt khi gắng sức hoặc từng được chẩn đoán rối loạn nhịp tim như nhịp nhanh kịch phát trên thất, nhanh nhĩ, nhanh thất hay rung nhĩ, người bệnh nên đến cơ sở y tế chuyên sâu để được thăm khám và đánh giá sớm.

+ + + +

Với sự phát triển của các kỹ thuật điện sinh lý tim hiện nay, nhiều trường hợp rối loạn nhịp có thể được điều trị triệt để bằng can thiệp đốt điện sinh lý bằng sóng cao tần. Điều này không chỉ giúp kiểm soát triệu chứng mà còn góp phần giảm nguy cơ suy tim, đột tử và đột quỵ trong tương lai.

+ + + +

Những cơn tim đập nhanh kéo dài không chỉ ảnh hưởng đến chất lượng cuộc sống mà còn có thể là dấu hiệu của một rối loạn nhịp tim nguy hiểm. Phát hiện sớm, điều trị đúng thời điểm và can thiệp khi có chỉ định là yếu tố quan trọng giúp bảo tồn chức năng tim, hạn chế biến chứng và cải thiện tiên lượng lâu dài cho người bệnh.

+ + + +

Kim Cương, ảnh: Đức Thịnh, Tuấn Anh

+]]>
+ + 16173 + + + + + + + + + 0 + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + <![CDATA[Balloon-assisted advancement of aspiration catheters across underlying intracranial stenosis during mechanical<br>thrombectomy: a technical note]]> + https://sisvietnam.vn/balloon-assisted-advancement-of-aspiration-catheters-across-underlying-intracranial-stenosis-during-mechanicalthrombectomy-a-technical-note/ + Wed, 08 Jul 2026 02:55:55 +0000 + + https://sisvietnam.vn/?p=16174 + + +

+ + + + + + + +

Nguồn: Link

+]]>
+ + 16174 + + + + + + + + + 0 + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ \ No newline at end of file diff --git a/sisvietnamvn_Trang chính thức hiện tại/bnhvinakhoaquctsiscnth.WordPress.2026-07-13.xml:Zone.Identifier b/sisvietnamvn_Trang chính thức hiện tại/bnhvinakhoaquctsiscnth.WordPress.2026-07-13.xml:Zone.Identifier new file mode 100644 index 00000000..d6c1ec68 Binary files /dev/null and b/sisvietnamvn_Trang chính thức hiện tại/bnhvinakhoaquctsiscnth.WordPress.2026-07-13.xml:Zone.Identifier differ diff --git a/sisvietnamvn_main/html_snippets/content_main_blocks/temp b/sisvietnamvn_main/html_snippets/content_main_blocks/temp new file mode 100644 index 00000000..fd46cb9b --- /dev/null +++ b/sisvietnamvn_main/html_snippets/content_main_blocks/temp @@ -0,0 +1,31 @@ +
+
+
+
+

TÌM HIỂUCHUYÊN KHOA

+
+
+ +
+
diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/NewsEventsSeeder.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/NewsEventsSeeder.java new file mode 100644 index 00000000..27866262 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/NewsEventsSeeder.java @@ -0,0 +1,110 @@ +package com.sisvietnamvn.web; + +import com.sisvietnamvn.web.domain.ComponentTemplate; +import com.sisvietnamvn.web.repository.ComponentTemplateRepository; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Component +public class NewsEventsSeeder implements CommandLineRunner { + + private final ComponentTemplateRepository repository; + + public NewsEventsSeeder(ComponentTemplateRepository repository) { + this.repository = repository; + } + + @Override + @Transactional + public void run(String... args) throws Exception { + seedNewsGrid(); + seedEventsList(); + } + + private void seedNewsGrid() { + String slug = "news-grid"; + Optional opt = repository.findBySlug(slug); + ComponentTemplate t = opt.orElse(new ComponentTemplate()); + t.setSlug(slug); + t.setName("News Grid"); + t.setDescription("Dynamic grid layout for news."); + t.setActive(true); + + String html = + "\n" + + "
\n" + + " {{#each news_posts}}\n" + + "
\n" + + "
\"{{title}}\"
\n" + + "
\n" + + " TIN TỨC\n" + + "

{{title}}

\n" + + "
{{date}}
\n" + + "
\n" + + "
\n" + + " {{/each}}\n" + + "
\n"; + + t.setHtmlTemplate(html); + repository.save(t); + System.out.println("====== SUCCESS: SEEDED news-grid ComponentTemplate ====="); + } + + private void seedEventsList() { + String slug = "events-list"; + Optional opt = repository.findBySlug(slug); + ComponentTemplate t = opt.orElse(new ComponentTemplate()); + t.setSlug(slug); + t.setName("Events List"); + t.setDescription("Vertical list layout for events with event times."); + t.setActive(true); + + String html = + "\n" + + "
\n" + + " {{#each event_posts}}\n" + + "
\n" + + "
\n" + + " {{eventMonth}}\n" + + " {{eventDay}}\n" + + "
\n" + + "
\n" + + "

{{title}}

\n" + + "

{{eventTimeStr}}
{{excerpt}}

\n" + + "
\n" + + "
\n" + + " {{/each}}\n" + + "
\n"; + + t.setHtmlTemplate(html); + repository.save(t); + System.out.println("====== SUCCESS: SEEDED events-list ComponentTemplate ====="); + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/config/ComponentTemplateSeeder.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/config/ComponentTemplateSeeder.java new file mode 100644 index 00000000..86102b61 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/config/ComponentTemplateSeeder.java @@ -0,0 +1,25 @@ +package com.sisvietnamvn.web.config; + +import com.sisvietnamvn.web.service.ComponentTemplateService; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * Seeds default Component Templates on application startup. + */ +@Component +@Order(10) +public class ComponentTemplateSeeder implements CommandLineRunner { + + private final ComponentTemplateService componentTemplateService; + + public ComponentTemplateSeeder(ComponentTemplateService componentTemplateService) { + this.componentTemplateService = componentTemplateService; + } + + @Override + public void run(String... args) { + componentTemplateService.seedDefaults(); + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageComponentTemplateController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageComponentTemplateController.java new file mode 100644 index 00000000..19cabf44 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageComponentTemplateController.java @@ -0,0 +1,91 @@ +package com.sisvietnamvn.web.controller.manage; + +import com.sisvietnamvn.web.domain.ComponentTemplate; +import com.sisvietnamvn.web.security.AuthoritiesConstants; +import com.sisvietnamvn.web.service.ComponentTemplateService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +/** + * Controller for managing Component Templates in the admin panel. + * Provides CRUD operations for reusable HTML component templates. + */ +@Controller +@RequestMapping("/manage/components") +@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\")") +public class ManageComponentTemplateController { + + private final ComponentTemplateService componentTemplateService; + + public ManageComponentTemplateController(ComponentTemplateService componentTemplateService) { + this.componentTemplateService = componentTemplateService; + } + + @GetMapping + public String listTemplates(Model model) { + model.addAttribute("templates", componentTemplateService.findAll()); + return "manage/components/list"; + } + + @GetMapping("/new") + public String newTemplateForm(Model model) { + model.addAttribute("template", new ComponentTemplate()); + model.addAttribute("isNew", true); + return "manage/components/form"; + } + + @PostMapping("/create") + public String createTemplate(@ModelAttribute ComponentTemplate template, RedirectAttributes redirectAttributes) { + ComponentTemplate saved = componentTemplateService.save(template); + redirectAttributes.addFlashAttribute("successMessage", "Component template created successfully."); + return "redirect:/manage/components/" + saved.getId() + "/edit"; + } + + @GetMapping("/{id}/edit") + public String editTemplateForm(@PathVariable Long id, Model model, RedirectAttributes redirectAttributes) { + return componentTemplateService.findById(id).map(template -> { + model.addAttribute("template", template); + model.addAttribute("isNew", false); + return "manage/components/form"; + }).orElseGet(() -> { + redirectAttributes.addFlashAttribute("errorMessage", "Component template not found."); + return "redirect:/manage/components"; + }); + } + + @PostMapping("/{id}") + public String updateTemplate(@PathVariable Long id, @ModelAttribute ComponentTemplate template, RedirectAttributes redirectAttributes) { + template.setId(id); + ComponentTemplate saved = componentTemplateService.save(template); + redirectAttributes.addFlashAttribute("successMessage", "Component template updated successfully."); + return "redirect:/manage/components/" + saved.getId() + "/edit"; + } + + @PostMapping("/{id}/delete") + public String deleteTemplate(@PathVariable Long id, RedirectAttributes redirectAttributes) { + componentTemplateService.deleteById(id); + redirectAttributes.addFlashAttribute("successMessage", "Component template deleted successfully."); + return "redirect:/manage/components"; + } + + @PostMapping("/{id}/duplicate") + public String duplicateTemplate(@PathVariable Long id, RedirectAttributes redirectAttributes) { + return componentTemplateService.findById(id).map(template -> { + ComponentTemplate duplicated = new ComponentTemplate(); + duplicated.setName(template.getName() + " (Copy)"); + duplicated.setSlug(template.getSlug() + "-copy-" + System.currentTimeMillis()); + duplicated.setHtmlTemplate(template.getHtmlTemplate()); + duplicated.setDescription(template.getDescription()); + duplicated.setActive(false); + ComponentTemplate saved = componentTemplateService.save(duplicated); + redirectAttributes.addFlashAttribute("successMessage", "Component template duplicated successfully."); + return "redirect:/manage/components/" + saved.getId() + "/edit"; + }).orElseGet(() -> { + redirectAttributes.addFlashAttribute("errorMessage", "Component template not found."); + return "redirect:/manage/components"; + }); + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManagePageController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManagePageController.java index 4929e53f..64e61ad5 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManagePageController.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManagePageController.java @@ -1,20 +1,27 @@ package com.sisvietnamvn.web.controller.manage; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + import com.sisvietnamvn.web.domain.Page; import com.sisvietnamvn.web.domain.PageStatus; import com.sisvietnamvn.web.hook.HookManager; -import com.sisvietnamvn.web.service.PageService; -import jakarta.validation.Valid; -import java.util.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springframework.security.access.prepost.PreAuthorize; import com.sisvietnamvn.web.security.AuthoritiesConstants; +import com.sisvietnamvn.web.service.PageService; + +import jakarta.validation.Valid; /** * Controller for managing CMS Pages in the admin panel. * Provides CRUD operations for static pages. @@ -129,7 +136,7 @@ public class ManagePageController { page.setId(id); pageService.save(page); redirectAttributes.addFlashAttribute("successMessage", "Page updated successfully!"); - return "redirect:/manage/pages"; + return "redirect:/manage/pages/" + page.getId() + "/edit"; } /** diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/ComponentTemplate.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/ComponentTemplate.java new file mode 100644 index 00000000..149b49e4 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/ComponentTemplate.java @@ -0,0 +1,109 @@ +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.Objects; + +/** + * Entity representing a reusable Component Template. + * Stores HTML templates with {{placeholder}} syntax that can be + * rendered with dynamic data (menus, posts, etc.) via shortcodes. + */ +@Entity +@Table(name = "sis_component_template") +public class ComponentTemplate extends AbstractAuditingEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") + @SequenceGenerator(name = "sequenceGenerator") + private Long id; + + @NotNull + @Size(max = 255) + @Column(name = "slug", length = 255, nullable = false, unique = true) + private String slug; + + @NotNull + @Size(max = 255) + @Column(name = "name", length = 255, nullable = false) + private String name; + + @Column(name = "html_template", columnDefinition = "TEXT") + private String htmlTemplate; + + @Size(max = 500) + @Column(name = "description", length = 500) + private String description; + + @Column(name = "active") + private boolean active = true; + + // Getters and Setters + + @Override + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getSlug() { + return slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHtmlTemplate() { + return htmlTemplate; + } + + public void setHtmlTemplate(String htmlTemplate) { + this.htmlTemplate = htmlTemplate; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ComponentTemplate that = (ComponentTemplate) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Post.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Post.java index e70d2182..b14a69d9 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Post.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Post.java @@ -60,6 +60,9 @@ public class Post extends AbstractAuditingEntity { @Column(name = "layout", length = 20, nullable = false) private PostLayout layout = PostLayout.STANDARD; + @Column(name = "event_time") + private java.time.Instant eventTime; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "category_id") private Category category; @@ -143,10 +146,6 @@ public class Post extends AbstractAuditingEntity { return layout; } - public void setLayout(PostLayout layout) { - this.layout = layout; - } - public Category getCategory() { return category; } @@ -155,6 +154,14 @@ public class Post extends AbstractAuditingEntity { this.category = category; } + public java.time.Instant getEventTime() { + return eventTime; + } + + public void setEventTime(java.time.Instant eventTime) { + this.eventTime = eventTime; + } + public Set getTags() { return tags; } diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/ComponentTemplateRepository.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/ComponentTemplateRepository.java new file mode 100644 index 00000000..d37203b0 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/ComponentTemplateRepository.java @@ -0,0 +1,18 @@ +package com.sisvietnamvn.web.repository; + +import com.sisvietnamvn.web.domain.ComponentTemplate; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/** + * Spring Data JPA repository for the ComponentTemplate entity. + */ +@Repository +public interface ComponentTemplateRepository extends JpaRepository { + + Optional findBySlug(String slug); + + boolean existsBySlug(String slug); +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/PostRepository.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/PostRepository.java index a1b30ae3..2ac73a88 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/PostRepository.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/PostRepository.java @@ -81,5 +81,5 @@ public interface PostRepository extends JpaRepository { * Find all posts that have a specific tag. */ @Query("SELECT p FROM Post p JOIN p.tags t WHERE t.id = :tagId ORDER BY p.createdDate DESC") - List findByTagId(@Param("tagId") Long tagId); + List findByTagId(@Param("tagId") Long tagId, org.springframework.data.domain.Pageable pageable); } diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/security/CoreSettingsRegistrar.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/security/CoreSettingsRegistrar.java index a3af9c73..b2b039d3 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/security/CoreSettingsRegistrar.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/security/CoreSettingsRegistrar.java @@ -1,7 +1,5 @@ package com.sisvietnamvn.web.security; -import com.sisvietnamvn.web.security.AdminMenuManager; -import com.sisvietnamvn.web.security.AdminSettingsManager; import org.springframework.stereotype.Component; import jakarta.annotation.PostConstruct; @@ -15,11 +13,9 @@ import java.util.Map; public class CoreSettingsRegistrar { private final AdminSettingsManager settingsManager; - private final AdminMenuManager menuManager; - public CoreSettingsRegistrar(AdminSettingsManager settingsManager, AdminMenuManager menuManager) { + public CoreSettingsRegistrar(AdminSettingsManager settingsManager) { this.settingsManager = settingsManager; - this.menuManager = menuManager; } @PostConstruct @@ -60,6 +56,6 @@ public class CoreSettingsRegistrar { settingsManager.registerSetting(page, "date_format", "F j, Y"); // --- Menu Registration for Modules --- - menuManager.addMenuPage("HTML Snippets", "Snippets", "manage_options", "snippets", "fas fa-code", 30); + // (Snippets menu is now statically defined under Components) } } diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/ComponentTemplateService.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/ComponentTemplateService.java new file mode 100644 index 00000000..aef72e83 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/ComponentTemplateService.java @@ -0,0 +1,325 @@ +package com.sisvietnamvn.web.service; + +import com.sisvietnamvn.web.domain.ComponentTemplate; +import com.sisvietnamvn.web.domain.Menu; +import com.sisvietnamvn.web.domain.MenuItem; +import com.sisvietnamvn.web.repository.ComponentTemplateRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Service for managing Component Templates and rendering them with dynamic data. + * + *

Supports a simple built-in template engine with the following syntax:

+ *
    + *
  • {@code {{name}}} — replaced with the data source name
  • + *
  • {@code {{#each items}}...{{/each}}} — loops over items
  • + *
  • {@code {{label}}} — item label (inside loop)
  • + *
  • {@code {{url}}} — item URL (inside loop)
  • + *
  • {@code {{index}}} — zero-based index (inside loop)
  • + *
+ */ +@Service +@Transactional +public class ComponentTemplateService { + + private static final Logger LOG = LoggerFactory.getLogger(ComponentTemplateService.class); + + /** + * Pattern to match {{#each items}}...{{/each}} blocks. + * Uses DOTALL so the dot matches newlines inside the loop body. + */ + private static final Pattern EACH_PATTERN = Pattern.compile( + "\\{\\{#each\\s+([a-zA-Z0-9_]+)\\s*}}(.*?)\\{\\{/each\\s*}}", Pattern.DOTALL); + + private final ComponentTemplateRepository templateRepository; + private final MenuService menuService; + private final com.sisvietnamvn.web.repository.PostRepository postRepository; + private final com.sisvietnamvn.web.repository.TagRepository tagRepository; + + public ComponentTemplateService( + ComponentTemplateRepository templateRepository, + MenuService menuService, + com.sisvietnamvn.web.repository.PostRepository postRepository, + com.sisvietnamvn.web.repository.TagRepository tagRepository) { + this.templateRepository = templateRepository; + this.menuService = menuService; + this.postRepository = postRepository; + this.tagRepository = tagRepository; + } + + // ── CRUD ────────────────────────────────────────────────────────────── + + public List findAll() { + return templateRepository.findAll(); + } + + public Optional findById(Long id) { + return templateRepository.findById(id); + } + + public Optional findBySlug(String slug) { + return templateRepository.findBySlug(slug); + } + + public ComponentTemplate save(ComponentTemplate template) { + return templateRepository.save(template); + } + + public void deleteById(Long id) { + templateRepository.deleteById(id); + } + + public boolean existsBySlug(String slug) { + return templateRepository.existsBySlug(slug); + } + + // ── Rendering ───────────────────────────────────────────────────────── + + /** + * Renders the given component template (by slug) using the optionally + * specified data source. + * + * @param slug the unique slug of the ComponentTemplate + * @param dataSourceSpec the data source specification, e.g. "menu:CTA" or null + * @return the rendered HTML, or empty string if the template is not found/inactive + */ + @Transactional(readOnly = true) + public String render(String slug, String dataSourceSpec) { + Optional optTemplate = templateRepository.findBySlug(slug); + if (optTemplate.isEmpty() || !optTemplate.get().isActive()) { + LOG.warn("Component template not found or inactive: {}", slug); + return ""; + } + + String html = optTemplate.get().getHtmlTemplate(); + if (html == null || html.isEmpty()) { + return ""; + } + + String result = html; + // If data source specified, render with data + if (dataSourceSpec != null && !dataSourceSpec.isEmpty()) { + result = renderWithDataSource(html, dataSourceSpec); + } + + return "\n\n" + result + "\n\n"; + } + + /** + * Renders the given HTML template string with data from the specified source. + */ + private String renderWithDataSource(String html, String dataSourceSpec) { + String result = html; + String[] sources = dataSourceSpec.split(";"); + for (String source : sources) { + String s = source.trim(); + if (s.startsWith("menu:")) { + String location = s.substring("menu:".length()).trim(); + result = renderWithMenuData(result, location); + } else if (s.startsWith("posts:")) { + result = renderWithPostsData(result, s.substring("posts:".length()).trim()); + } else { + LOG.warn("Unknown data source type: {}", s); + } + } + return result; + } + + /** + * Renders the template with data from a Menu entity (looked up by location). + */ + private String renderWithMenuData(String html, String location) { + Optional optMenu = menuService.findByLocation(location); + if (optMenu.isEmpty()) { + LOG.warn("Menu not found for location: {}", location); + return ""; + } + + Menu menu = optMenu.get(); + String result = html.replace("{{name}}", menu.getName() != null ? menu.getName() : ""); + return processEachBlock(result, "items", menu.getItems(), null); + } + + /** + * Renders the template with data from Posts (looked up by tag). + */ + private String renderWithPostsData(String html, String params) { + String tagSlug = null; + int limit = 10; + String collection = "posts"; + for (String part : params.split(",")) { + String[] kv = part.split("="); + if (kv.length == 2) { + if ("tag".equals(kv[0].trim())) tagSlug = kv[1].trim(); + else if ("limit".equals(kv[0].trim())) { + try { limit = Integer.parseInt(kv[1].trim()); } catch (NumberFormatException ignored) {} + } + else if ("collection".equals(kv[0].trim())) collection = kv[1].trim(); + } + } + if (tagSlug == null) return html; + + Optional optTag = tagRepository.findBySlug(tagSlug); + if (optTag.isEmpty()) return html; + + List posts = postRepository.findByTagId( + optTag.get().getId(), org.springframework.data.domain.PageRequest.of(0, limit)); + + return processEachBlock(html, collection, null, posts); + } + + /** + * Processes all {{#each }}...{{/each}} blocks in the template. + */ + private String processEachBlock(String html, String targetCollection, java.util.Set menuItems, List posts) { + Matcher matcher = EACH_PATTERN.matcher(html); + StringBuilder sb = new StringBuilder(); + + java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("dd/MM/yyyy") + .withZone(java.time.ZoneId.systemDefault()); + + while (matcher.find()) { + String collectionName = matcher.group(1); + String loopBody = matcher.group(2); + + // If the template block doesn't match our target collection, leave it as is + if (!collectionName.equals(targetCollection)) { + matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0))); + continue; + } + + StringBuilder rendered = new StringBuilder(); + int index = 0; + + if ("items".equals(collectionName) && menuItems != null) { + for (MenuItem item : menuItems) { + if (item.getParent() != null) continue; + String itemHtml = loopBody; + itemHtml = itemHtml.replace("{{label}}", item.getLabel() != null ? item.getLabel() : ""); + itemHtml = itemHtml.replace("{{url}}", item.getUrl() != null ? item.getUrl() : "#"); + itemHtml = itemHtml.replace("{{index}}", String.valueOf(index)); + rendered.append(itemHtml); + index++; + } + } else if (posts != null) { + java.time.format.DateTimeFormatter monthFormatter = java.time.format.DateTimeFormatter.ofPattern("MMM").withZone(java.time.ZoneId.systemDefault()); + java.time.format.DateTimeFormatter dayFormatter = java.time.format.DateTimeFormatter.ofPattern("dd").withZone(java.time.ZoneId.systemDefault()); + java.time.format.DateTimeFormatter timeFormatter = java.time.format.DateTimeFormatter.ofPattern("hh:mm a").withZone(java.time.ZoneId.systemDefault()); + for (com.sisvietnamvn.web.domain.Post post : posts) { + String itemHtml = loopBody; + itemHtml = itemHtml.replace("{{title}}", post.getTitle() != null ? post.getTitle() : ""); + itemHtml = itemHtml.replace("{{slug}}", post.getSlug() != null ? post.getSlug() : ""); + itemHtml = itemHtml.replace("{{excerpt}}", post.getExcerpt() != null ? post.getExcerpt() : ""); + itemHtml = itemHtml.replace("{{featuredImageUrl}}", post.getFeaturedImage() != null ? post.getFeaturedImage() : ""); + String dateStr = post.getCreatedDate() != null ? formatter.format(post.getCreatedDate()) : ""; + itemHtml = itemHtml.replace("{{date}}", dateStr); + + if (post.getEventTime() != null) { + itemHtml = itemHtml.replace("{{eventMonth}}", monthFormatter.format(post.getEventTime())); + itemHtml = itemHtml.replace("{{eventDay}}", dayFormatter.format(post.getEventTime())); + itemHtml = itemHtml.replace("{{eventTimeStr}}", timeFormatter.format(post.getEventTime())); + } else { + itemHtml = itemHtml.replace("{{eventMonth}}", ""); + itemHtml = itemHtml.replace("{{eventDay}}", ""); + itemHtml = itemHtml.replace("{{eventTimeStr}}", ""); + } + + itemHtml = itemHtml.replace("{{index}}", String.valueOf(index)); + rendered.append(itemHtml); + index++; + } + } + + matcher.appendReplacement(sb, Matcher.quoteReplacement(rendered.toString())); + } + matcher.appendTail(sb); + + return sb.toString(); + } + + // ── Seed Data ───────────────────────────────────────────────────────── + + /** + * Seeds default component templates if they don't already exist. + * Called from application startup (e.g., ApplicationRunner or @PostConstruct). + */ + public void seedDefaults() { + seedCtaButtons(); + seedInfoDropdown(); + } + + private void seedCtaButtons() { + if (templateRepository.findBySlug("cta-buttons").isPresent()) { + return; + } + ComponentTemplate t = new ComponentTemplate(); + t.setSlug("cta-buttons"); + t.setName("CTA Buttons"); + t.setDescription("Renders menu items as a row of styled CTA buttons (UMass theme)."); + t.setActive(true); + t.setHtmlTemplate( + "{{#each items}}\n" + + "\n" + + "{{/each}}" + ); + templateRepository.save(t); + LOG.info("Seeded default component template: cta-buttons"); + } + + private void seedInfoDropdown() { + if (templateRepository.findBySlug("info-dropdown").isPresent()) { + return; + } + ComponentTemplate t = new ComponentTemplate(); + t.setSlug("info-dropdown"); + t.setName("Info Dropdown Menu"); + t.setDescription("Renders menu items as a dropdown navigation (UMass Info For style)."); + t.setActive(true); + t.setHtmlTemplate( + "" + ); + templateRepository.save(t); + LOG.info("Seeded default component template: info-dropdown"); + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/HtmlSnippetService.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/HtmlSnippetService.java index 2f1e8b23..099d984c 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/HtmlSnippetService.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/HtmlSnippetService.java @@ -21,13 +21,13 @@ public class HtmlSnippetService { private final HtmlSnippetRepository snippetRepository; private final SettingService settingService; private final ObjectMapper objectMapper; - private final com.sisvietnamvn.web.service.MenuService menuService; + private final ComponentTemplateService componentTemplateService; - public HtmlSnippetService(HtmlSnippetRepository snippetRepository, SettingService settingService, ObjectMapper objectMapper, com.sisvietnamvn.web.service.MenuService menuService) { + public HtmlSnippetService(HtmlSnippetRepository snippetRepository, SettingService settingService, ObjectMapper objectMapper, ComponentTemplateService componentTemplateService) { this.snippetRepository = snippetRepository; this.settingService = settingService; this.objectMapper = objectMapper; - this.menuService = menuService; + this.componentTemplateService = componentTemplateService; } public List findAll() { @@ -83,65 +83,34 @@ public class HtmlSnippetService { while (m.find()) { String location = m.group(1); String style = m.group(2) != null ? m.group(2) : "button"; - m.appendReplacement(sb, renderMenu(location, style)); + m.appendReplacement(sb, java.util.regex.Matcher.quoteReplacement(renderMenu(location, style))); } m.appendTail(sb); content = sb.toString(); - return content; + // Process component template shortcodes: + // [component:SLUG] + // [component:SLUG data-source="menu:CTA"] + // Also supports HTML-escaped brackets + java.util.regex.Matcher cm = java.util.regex.Pattern.compile( + "(?:\\[|[)component:([a-zA-Z0-9_-]+)(?:\\s+data-source=\"([^\"]+)\")?(?:\\]|])" + ).matcher(content); + StringBuffer csb = new StringBuffer(); + while (cm.find()) { + String componentSlug = cm.group(1); + String dataSource = cm.group(2); + String rendered = componentTemplateService.render(componentSlug, dataSource); + cm.appendReplacement(csb, java.util.regex.Matcher.quoteReplacement(rendered)); + } + cm.appendTail(csb); + content = csb.toString(); + + return "\n\n" + content + "\n\n"; } private String renderMenu(String location, String style) { - return menuService.findByLocation(location).map(menu -> { - StringBuilder sb = new StringBuilder(); - - if ("dropdown".equalsIgnoreCase(style)) { - sb.append("\n"); - } else { - // Default button style - for (com.sisvietnamvn.web.domain.MenuItem item : menu.getItems()) { - if (item.getParent() == null) { - sb.append("\n"); - } - } - } - return sb.toString(); - }).orElse(""); + String componentSlug = "dropdown".equalsIgnoreCase(style) ? "info-dropdown" : "cta-buttons"; + return componentTemplateService.render(componentSlug, "menu:" + location); } private String renderWidgets(String area) { diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/ImportExportService.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/ImportExportService.java index 9acd79ae..b43788ab 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/ImportExportService.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/ImportExportService.java @@ -129,11 +129,23 @@ public class ImportExportService { Document doc = dBuilder.parse(is); doc.getDocumentElement().normalize(); - if (!"sis_export".equals(doc.getDocumentElement().getNodeName())) { - log.warn("Invalid XML root element during import"); + String rootName = doc.getDocumentElement().getNodeName(); + if ("sis_export".equals(rootName)) { + return importSisFormat(doc); + } else if ("rss".equals(rootName)) { + return importWordPressFormat(doc); + } else { + log.warn("Invalid XML root element during import: {}", rootName); return false; } + } catch (Exception e) { + log.error("Error importing from XML", e); + return false; + } + } + private boolean importSisFormat(Document doc) { + try { // Import Posts NodeList postList = doc.getElementsByTagName("post"); for (int i = 0; i < postList.getLength(); i++) { @@ -192,14 +204,65 @@ public class ImportExportService { } } - // Media files are skipped during import since the actual file data is not present in XML. return true; } catch (Exception e) { - log.error("Error importing from XML", e); + log.error("Error processing SIS XML format", e); return false; } } + private boolean importWordPressFormat(Document doc) { + try { + NodeList itemList = doc.getElementsByTagName("item"); + for (int i = 0; i < itemList.getLength(); i++) { + Node node = itemList.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element el = (Element) node; + String postType = getElementValue(el, "wp:post_type"); + String title = getElementValue(el, "title"); + String slug = getElementValue(el, "wp:post_name"); + String content = getElementValue(el, "content:encoded"); + String excerpt = getElementValue(el, "excerpt:encoded"); + String statusStr = getElementValue(el, "wp:status"); + + PageStatus status = PageStatus.DRAFT; + if ("publish".equalsIgnoreCase(statusStr)) { + status = PageStatus.PUBLISHED; + } else if ("trash".equalsIgnoreCase(statusStr)) { + status = PageStatus.ARCHIVED; + } + + if ("post".equalsIgnoreCase(postType)) { + if (postRepository.findBySlug(slug).isEmpty()) { + Post post = new Post(); + post.setTitle(title); + post.setSlug(slug); + post.setContent(content); + post.setExcerpt(excerpt); + post.setStatus(status); + postRepository.save(post); + } + } else if ("page".equalsIgnoreCase(postType)) { + if (pageRepository.findBySlug(slug).isEmpty()) { + Page page = new Page(); + page.setTitle(title); + page.setSlug(slug); + page.setContent(content); + page.setStatus(status); + pageRepository.save(page); + } + } + // Ignoring attachments or custom post types for now + } + } + return true; + } catch (Exception e) { + log.error("Error processing WordPress WXR format", e); + return false; + } + } + + private void addElement(Document doc, Element parent, String tagName, String value) { Element el = doc.createElement(tagName); el.appendChild(doc.createTextNode(value != null ? value : "")); diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/sample.html b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/sample.html index c1f4844c..18ab3c02 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/sample.html +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/sample.html @@ -1,51 +1,10 @@ -
-
-
-
-
-
-
-
- Students collaborate using a driving simulator in the UMass Center for Transportation. -
- -
-
-
- - -
-
-
-
-
-

- Stroke International School 2026. -

- - [menu:INFO:dropdown] -
-
-
-
-
-
-
+ +
+
+ [component:news-grid data-source="posts:tag=news,limit=4,collection=news_posts"]
-
chỉnh \ No newline at end of file +
+ [component:events-list data-source="posts:tag=event,limit=4,collection=event_posts"] +
+
+ \ No newline at end of file diff --git a/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260713201500_add_component_template_entity.xml b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260713201500_add_component_template_entity.xml new file mode 100644 index 00000000..631da4c8 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260713201500_add_component_template_entity.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260713211700_add_event_time_to_post.xml b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260713211700_add_event_time_to_post.xml new file mode 100644 index 00000000..dd07f499 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260713211700_add_event_time_to_post.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml b/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml index f2818c36..bc0a1260 100644 --- a/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml +++ b/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml @@ -31,6 +31,8 @@ + + diff --git a/sisvietnamvn_main/src/main/resources/templates/fragments/manage-layout.html b/sisvietnamvn_main/src/main/resources/templates/fragments/manage-layout.html index 1828b04c..215de470 100644 --- a/sisvietnamvn_main/src/main/resources/templates/fragments/manage-layout.html +++ b/sisvietnamvn_main/src/main/resources/templates/fragments/manage-layout.html @@ -201,8 +201,8 @@
Custom Components:
- Buttons - Cards + Component Templates + HTML Snippets
diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/components/form.html b/sisvietnamvn_main/src/main/resources/templates/manage/components/form.html new file mode 100644 index 00000000..235537bf --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/templates/manage/components/form.html @@ -0,0 +1,222 @@ + + + + Edit Component Template + + + +
+ +
+

Component Template

+ + Back to Templates + +
+ + + + + +
+
+ +
+
+
+
Template Properties
+
+
+
+ + +
+
+ + + Used in shortcode: [component:slug] +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
Available Placeholders
+
+
+
+
Data Source Fields
+ {{name}} +
+
+
Loop (repeats for each item)
+ {{#each items}} +
+
+
Item Fields (inside loop)
+ {{label}} + {{url}} + {{index}} +
+
+
+ Tip: Click a placeholder tag to insert it at the cursor position in the editor. +
+
+
+ + +
+ + +
+
+
+
HTML Template
+
+
+ +
+
+
+ + +
+
+
Usage in Snippets
+
+ Static (no data):
+ [component:slug] +
+
+ With Menu data:
+ [component:slug data-source="menu:LOCATION"] +
+
+ Replace LOCATION with the menu location code (e.g. CTA, INFO, PRIMARY, FOOTER). +
+
+
+
+
+
+
+ +
+ + + +
+ + diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/components/list.html b/sisvietnamvn_main/src/main/resources/templates/manage/components/list.html new file mode 100644 index 00000000..b5df0c87 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/templates/manage/components/list.html @@ -0,0 +1,104 @@ + + + + Component Templates + + +
+ +
+

Component Templates

+ + Add New Template + +
+ + + + + + +
+
+
+
+
How to use
+
+ Use shortcode [component:SLUG data-source="menu:LOCATION"] inside any Snippet to render a Component Template with dynamic data. +
+
+
+ +
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
NameSlugDescriptionStatusActions
Name + slug +
+ Shortcode: [component:slug] +
+
+ Description + + Active + Inactive + + + + +
+ + +
+
+ + +
+
No Component Templates found.
+
+
+
+
+ + diff --git a/sisvietnamvn_main/src/main/resources/templates/themes/umass/content-main-blocks-cta.html b/sisvietnamvn_main/src/main/resources/templates/themes/umass/content-main-blocks-cta.html deleted file mode 100644 index 107f4961..00000000 --- a/sisvietnamvn_main/src/main/resources/templates/themes/umass/content-main-blocks-cta.html +++ /dev/null @@ -1,46 +0,0 @@ -
-
-
-
-

TÌM HIỂU CHUYÊN KHOA

-
-
- -
- -
-
-
-

TÌM HIỂUCHUYÊN KHOA

-
-
- -
-