diff --git a/.vscode/launch.json b/.vscode/launch.json index 5bb51767..38c9a528 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,15 +1,6 @@ { "version": "0.2.0", "configurations": [ - { - "type": "java", - "name": "Debug (Launch) - SisvietnamvnApp", - "request": "launch", - "mainClass": "com.sisvietnamvn.web.SisvietnamvnApp", - "projectName": "sisvietnamvn", - "cwd": "${workspaceFolder}/sisvietnamvn_main", - "vmArgs": "-Dspring.profiles.active=dev" - }, { "type": "java", "name": "Debug (Attach) - Port 5005", diff --git a/README.md b/README.md index 12cf94e1..dae185c9 100644 --- a/README.md +++ b/README.md @@ -126,4 +126,37 @@ Tôi đã chạy lệnh ./gradlew spotlessApply để tự động dọn dẹp v (Lưu ý: Có một lỗi riêng trong lệnh chạy test ./gradlew test liên quan đến cấu hình taskExecutor bị thiếu của dự án gốc đối với môi trường test, nhưng nó hoàn toàn không ảnh hưởng đến việc chạy ứng dụng thực tế bằng ./gradlew bootRun). -Bạn có thể refresh lại trang web để kiểm tra màu sắc trên menu header nhé, nó đã được sửa lại thành màu đen rồi! \ No newline at end of file +Bạn có thể refresh lại trang web để kiểm tra màu sắc trên menu header nhé, nó đã được sửa lại thành màu đen rồi! + +## How to Debug (VS Code) + +1. **Start the Application in Debug Mode**: + Open a terminal in the project directory (`sisvietnamvn_main`) and run: + ```bash + ./gradlew bootRun --debug-jvm + ``` + The application will start and pause, displaying: `Listening for transport dt_socket at address: 5005`. + +2. **Configure VS Code**: + Ensure your `.vscode/launch.json` has the following configuration: + ```json + { + "type": "java", + "name": "Debug (Attach) - Port 5005", + "request": "attach", + "hostName": "127.0.0.1", + "port": 5005, + "projectName": "sisvietnamvn" + } + ``` + +3. **Attach the Debugger**: + - Go to the **Run and Debug** view in VS Code (`Ctrl+Shift+D`). + - Select **Debug (Attach) - Port 5005** from the dropdown at the top. + - Click the green **Play** button (or press `F5`). + - The application will resume booting. + +4. **Set Breakpoints & Debug**: + - Open any Java file (e.g., `SwiperSliderPlugin.java`). + - Click in the left margin next to a line number to place a red breakpoint. + - Trigger the code (e.g., by navigating to the page in your browser). Execution will pause at your breakpoint, allowing you to inspect variables! \ No newline at end of file diff --git a/sisvietnamvn_main/fix_admin_form.py b/sisvietnamvn_main/fix_admin_form.py new file mode 100644 index 00000000..bdaca015 --- /dev/null +++ b/sisvietnamvn_main/fix_admin_form.py @@ -0,0 +1,458 @@ +with open("src/main/resources/templates/plugins/swiper-slider/admin-form.html", "w", encoding="utf-8") as f: + f.write(""" + + Swiper Slider Settings + + + +
+
+
+

Sửa Nhóm Slider

+ Quay lại Danh sách +
+ +
+ +
+ + + +
+
+
Thông tin cơ bản
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + + + + +
+
+
+ +
+
+
Danh sách Slides
+ +
+
+
+
+
+ + +
+
+ + +
+ +""") diff --git a/sisvietnamvn_main/sisvietnam_backup.dmp b/sisvietnamvn_main/sisvietnam_backup.dmp index 15851d2c..ac0a01f4 100644 Binary files a/sisvietnamvn_main/sisvietnam_backup.dmp and b/sisvietnamvn_main/sisvietnam_backup.dmp differ diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/config/SecurityConfiguration.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/config/SecurityConfiguration.java index 86764771..2823b988 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/config/SecurityConfiguration.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/config/SecurityConfiguration.java @@ -48,7 +48,7 @@ public class SecurityConfiguration { @Bean public SecurityFilterChain filterChain(HttpSecurity http, CustomOidcUserService customOidcUserService) { http.cors(withDefaults()) - .csrf(withDefaults()) + .csrf(csrf -> csrf.ignoringRequestMatchers("/api/manage/media/upload")) .authorizeHttpRequests(authz -> // prettier-ignore authz diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManagePostController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManagePostController.java index b37435da..370c6887 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManagePostController.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManagePostController.java @@ -60,7 +60,7 @@ public class ManagePostController { @RequestParam(value = "status", required = false) String statusStr, @RequestParam(value = "search", required = false) String search, @RequestParam(value = "page", defaultValue = "0") int page, - @RequestParam(value = "size", defaultValue = "10") int size, + @RequestParam(value = "size", defaultValue = "10000") int size, Model model) { LOG.debug("Request to list all posts (categoryId={}, status={}, search={}, page={}, size={})", categoryId, statusStr, search, page, size); diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/MediaController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/MediaController.java index 220236be..c6451a1a 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/MediaController.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/MediaController.java @@ -12,6 +12,7 @@ import com.sisvietnamvn.web.security.AuthoritiesConstants; import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -69,4 +70,9 @@ public class MediaController { return ResponseEntity.internalServerError().body(response); } } + + @GetMapping("/list") + public ResponseEntity> getMediaList() { + return ResponseEntity.ok(mediaService.findAll()); + } } diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/DoctorSchedule.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/DoctorSchedule.java index 7c78608b..9245bcf7 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/DoctorSchedule.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/DoctorSchedule.java @@ -24,7 +24,7 @@ public class DoctorSchedule implements Serializable { private Doctor doctor; @Column(name = "schedule_date", length = 50, nullable = false) - private String date; + private String scheduleDate; @Column(name = "day_of_week", nullable = false) private Integer dayOfWeek; @@ -47,12 +47,20 @@ public class DoctorSchedule implements Serializable { this.doctor = doctor; } + public String getScheduleDate() { + return scheduleDate; + } + + public void setScheduleDate(String scheduleDate) { + this.scheduleDate = scheduleDate; + } + public String getDate() { - return date; + return scheduleDate; } public void setDate(String date) { - this.date = date; + this.scheduleDate = date; } public Integer getDayOfWeek() { diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Page.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Page.java index 9a235e45..bb89a921 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Page.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Page.java @@ -5,6 +5,8 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import java.io.Serial; import java.util.Objects; +import java.util.Set; +import java.util.HashSet; /** * A CMS Page entity representing a static page (e.g. About Us, Contact, Working Hours). @@ -73,6 +75,14 @@ public class Page extends AbstractAuditingEntity { @Column(name = "specialty_category", length = 50) private String specialtyCategory; + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "sis_page_tag", + joinColumns = @JoinColumn(name = "page_id"), + inverseJoinColumns = @JoinColumn(name = "tag_id") + ) + private Set tags = new HashSet<>(); + // --- Getters and Setters --- @Override @@ -100,6 +110,14 @@ public class Page extends AbstractAuditingEntity { this.slug = slug; } + public Set getTags() { + return tags; + } + + public void setTags(Set tags) { + this.tags = tags; + } + public String getContent() { return content; } diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/sampleplugin/sampleplugin/SamplePluginHooks.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/sampleplugin/sampleplugin/SamplePluginHooks.java index f4b39e0f..6c98042b 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/sampleplugin/sampleplugin/SamplePluginHooks.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/sampleplugin/sampleplugin/SamplePluginHooks.java @@ -1,4 +1,4 @@ -package com.sisvietnamvn.web.plugins.sampleplugin; +package com.sisvietnamvn.web.plugins.sampleplugin.sampleplugin; import com.sisvietnamvn.web.domain.Page; import com.sisvietnamvn.web.hook.HookManager; diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/swiperslider/SwiperSliderAdminController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/swiperslider/SwiperSliderAdminController.java index a4dee69b..6302f720 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/swiperslider/SwiperSliderAdminController.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/swiperslider/SwiperSliderAdminController.java @@ -20,12 +20,23 @@ public class SwiperSliderAdminController { private final SettingService settingService; private final AdminContext adminContext; private final ObjectMapper objectMapper; + private final com.sisvietnamvn.web.service.CategoryService categoryService; + private final com.sisvietnamvn.web.service.TagService tagService; + private final com.sisvietnamvn.web.service.ComponentTemplateService componentTemplateService; private static final String SETTING_KEY = "plugin_swiper_slider_data"; - public SwiperSliderAdminController(SettingService settingService, AdminContext adminContext, ObjectMapper objectMapper) { + public SwiperSliderAdminController(SettingService settingService, + AdminContext adminContext, + ObjectMapper objectMapper, + com.sisvietnamvn.web.service.CategoryService categoryService, + com.sisvietnamvn.web.service.TagService tagService, + com.sisvietnamvn.web.service.ComponentTemplateService componentTemplateService) { this.settingService = settingService; this.adminContext = adminContext; this.objectMapper = objectMapper; + this.categoryService = categoryService; + this.tagService = tagService; + this.componentTemplateService = componentTemplateService; } private List getAllGroups() { @@ -68,6 +79,9 @@ public class SwiperSliderAdminController { model.addAttribute("groupJson", "{\"slug\":\"\", \"name\":\"\", \"layoutType\":\"\", \"outerTemplate\":\"\", \"itemTemplate\":\"\", \"items\":[]}"); model.addAttribute("isNew", true); model.addAttribute("pageTitle", "Thêm Nhóm Slider Mới"); + model.addAttribute("allCategories", categoryService.findAll()); + model.addAttribute("allTags", tagService.findAll()); + model.addAttribute("componentTemplates", componentTemplateService.findAll()); return "plugins/swiper-slider/admin-form"; } @@ -92,6 +106,9 @@ public class SwiperSliderAdminController { model.addAttribute("oldSlug", slug); model.addAttribute("isNew", false); model.addAttribute("pageTitle", "Sửa Nhóm Slider: " + group.name()); + model.addAttribute("allCategories", categoryService.findAll()); + model.addAttribute("allTags", tagService.findAll()); + model.addAttribute("componentTemplates", componentTemplateService.findAll()); return "plugins/swiper-slider/admin-form"; } @@ -151,11 +168,35 @@ public class SwiperSliderAdminController { return "redirect:/manage/plugins/swiper-slider"; } - public record SliderGroup(String slug, String name, String layoutType, String outerTemplate, String itemTemplate, String note, List items) { + public record SliderGroup( + String slug, + String name, + String layoutType, + String outerTemplate, + String itemTemplate, + String note, + String dataSource, + Long categoryId, + Long tagId, + List categoryIds, + List tagIds, + List mediaIds, + Integer itemLimit, + Boolean useExcerptAsDescription, + List items + ) { + public SliderGroup(String slug, String name, String layoutType, String outerTemplate, String itemTemplate, String note, List items) { + this(slug, name, layoutType, outerTemplate, itemTemplate, note, null, null, null, null, null, null, null, null, items); + } public String getSlug() { return slug; } public String getName() { return name; } public String getLayoutType() { return layoutType; } public String getNote() { return note; } + public String getDataSource() { return dataSource; } + public List getCategoryIds() { return categoryIds; } + public List getTagIds() { return tagIds; } + public Integer getItemLimit() { return itemLimit; } + public Boolean getUseExcerptAsDescription() { return useExcerptAsDescription; } public List getItems() { return items; } } public record SliderItem(String title, String imageUrl, String linkUrl, String description, String badge, String price, String dateStr) {} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/swiperslider/SwiperSliderPlugin.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/swiperslider/SwiperSliderPlugin.java index f85daa66..b7c3902b 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/swiperslider/SwiperSliderPlugin.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/plugins/swiperslider/SwiperSliderPlugin.java @@ -34,17 +34,20 @@ public class SwiperSliderPlugin { private final HookManager hookManager; private final SettingService settingService; private final ObjectMapper objectMapper; + private final jakarta.persistence.EntityManager entityManager; public SwiperSliderPlugin(PluginRepository pluginRepository, ComponentTemplateRepository templateRepository, HookManager hookManager, SettingService settingService, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + jakarta.persistence.EntityManager entityManager) { this.pluginRepository = pluginRepository; this.templateRepository = templateRepository; this.hookManager = hookManager; this.settingService = settingService; this.objectMapper = objectMapper; + this.entityManager = entityManager; } @EventListener(ApplicationReadyEvent.class) @@ -76,7 +79,6 @@ public class SwiperSliderPlugin { LOG.info("SwiperSliderPlugin is active. Seeding components and hooks..."); seedComponentTemplate(); - seedDefaultData(); // Hook to add menu to Plugins dropdown hookManager.addFilter("admin_menu_plugins", (content, args) -> { @@ -115,7 +117,7 @@ public class SwiperSliderPlugin { return text; } - Pattern pattern = Pattern.compile("\\[plugin:swiper-slider slug=\"([^\"]+)\"\\]"); + Pattern pattern = Pattern.compile("\\[plugin:swiper-slider slug=[\"']?([^\"'\\] ]+)[\"']?\\]"); Matcher matcher = pattern.matcher(text); StringBuilder result = new StringBuilder(); @@ -132,71 +134,9 @@ public class SwiperSliderPlugin { String json = settingService.getValue(SETTING_KEY, "[]"); List groups = parseGroups(json); final String searchSlug = ("dao-tao-01".equals(slug)) ? "gallery" : slug; - Optional groupOpt = groups.stream() + return groups.stream() .filter(g -> slug.equals(g.slug()) || searchSlug.equals(g.slug())) .findFirst(); - - // 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()) || searchSlug.equals(g.slug())) - .findFirst(); - } - - return groupOpt; - } - - 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 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ỉ 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 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 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 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\":\"\"}" + - "]}]"; - - 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 parseGroups(String json) { @@ -215,7 +155,8 @@ public class SwiperSliderPlugin { } SwiperSliderAdminController.SliderGroup group = groupOpt.get(); - if (group.items() == null || group.items().isEmpty()) { + List dynamicItems = fetchItemsForGroup(group); + if (dynamicItems.isEmpty()) { return ""; } @@ -241,7 +182,8 @@ public class SwiperSliderPlugin { } SwiperSliderAdminController.SliderGroup group = groupOpt.get(); - if (group.items() == null || group.items().isEmpty()) { + List dynamicItems = fetchItemsForGroup(group); + if (dynamicItems.isEmpty()) { return ""; } @@ -259,12 +201,14 @@ public class SwiperSliderPlugin { slideTemplate = getSlideTemplateForSlug("course-card"); } else if ("custom".equals(layoutType) && group.itemTemplate() != null && !group.itemTemplate().trim().isEmpty()) { slideTemplate = group.itemTemplate(); + } else if (layoutType != null && !layoutType.trim().isEmpty()) { + slideTemplate = getSlideTemplateForSlug(layoutType); } else { // Fallback to legacy slug-based template if no matching layoutType or empty custom template slideTemplate = getSlideTemplateForSlug(slug); } - for (SwiperSliderAdminController.SliderItem item : group.items()) { + for (SwiperSliderAdminController.SliderItem item : dynamicItems) { String slide = slideTemplate .replace("{{title}}", item.title() != null ? item.title() : "") .replace("{{imageUrl}}", item.imageUrl() != null ? item.imageUrl() : "") @@ -278,19 +222,78 @@ public class SwiperSliderPlugin { return slidesHtml.toString(); } + private List fetchItemsForGroup(SwiperSliderAdminController.SliderGroup group) { + List dynamicItems = new ArrayList<>(); + if ("POST".equals(group.dataSource())) { + String jpql = "SELECT p FROM Post p "; + List conditions = new ArrayList<>(); + conditions.add("p.status = 'PUBLISHED'"); + if (group.categoryIds() != null && !group.categoryIds().isEmpty()) { conditions.add("p.category.id IN :catIds"); } + if (group.tagIds() != null && !group.tagIds().isEmpty()) { conditions.add("EXISTS (SELECT 1 FROM p.tags t WHERE t.id IN :tagIds)"); } + if (!conditions.isEmpty()) { jpql += "WHERE " + String.join(" AND ", conditions) + " "; } + jpql += "ORDER BY p.createdDate DESC"; + jakarta.persistence.TypedQuery q = entityManager.createQuery(jpql, com.sisvietnamvn.web.domain.Post.class); + if (group.categoryIds() != null && !group.categoryIds().isEmpty()) { + java.util.List catIds = group.categoryIds().stream().map(Number::longValue).collect(java.util.stream.Collectors.toList()); + q.setParameter("catIds", catIds); + } + if (group.tagIds() != null && !group.tagIds().isEmpty()) { + java.util.List tagIds = group.tagIds().stream().map(Number::longValue).collect(java.util.stream.Collectors.toList()); + q.setParameter("tagIds", tagIds); + } + if (group.itemLimit() != null && group.itemLimit() > 0) q.setMaxResults(group.itemLimit()); + List posts = q.getResultList(); + for (com.sisvietnamvn.web.domain.Post p : posts) { + String desc = Boolean.TRUE.equals(group.useExcerptAsDescription()) ? p.getExcerpt() : ""; + dynamicItems.add(new SwiperSliderAdminController.SliderItem(p.getTitle(), p.getFeaturedImage(), "/post/" + p.getSlug(), desc, null, null, null)); + } + } else if ("PAGE".equals(group.dataSource())) { + String jpql = "SELECT p FROM Page p "; + List conditions = new ArrayList<>(); + conditions.add("p.status = 'PUBLISHED'"); + if (group.tagIds() != null && !group.tagIds().isEmpty()) { conditions.add("EXISTS (SELECT 1 FROM p.tags t WHERE t.id IN :tagIds)"); } + if (!conditions.isEmpty()) { jpql += "WHERE " + String.join(" AND ", conditions) + " "; } + jpql += "ORDER BY p.createdDate DESC"; + jakarta.persistence.TypedQuery q = entityManager.createQuery(jpql, com.sisvietnamvn.web.domain.Page.class); + if (group.tagIds() != null && !group.tagIds().isEmpty()) { + java.util.List tagIds = group.tagIds().stream().map(Number::longValue).collect(java.util.stream.Collectors.toList()); + q.setParameter("tagIds", tagIds); + } + if (group.itemLimit() != null && group.itemLimit() > 0) q.setMaxResults(group.itemLimit()); + List pages = q.getResultList(); + for (com.sisvietnamvn.web.domain.Page p : pages) { + dynamicItems.add(new SwiperSliderAdminController.SliderItem(p.getTitle(), p.getHeroImage(), "/" + p.getSlug(), null, null, null, null)); + } + } else if ("MEDIA".equals(group.dataSource())) { + String jpql = "SELECT m FROM Media m "; + if (group.mediaIds() != null && !group.mediaIds().isEmpty()) { + jpql += "WHERE m.id IN :mediaIds "; + } + jpql += "ORDER BY m.createdDate DESC"; + jakarta.persistence.TypedQuery q = entityManager.createQuery(jpql, com.sisvietnamvn.web.domain.Media.class); + if (group.mediaIds() != null && !group.mediaIds().isEmpty()) { + java.util.List mediaIds = group.mediaIds().stream().map(Number::longValue).collect(java.util.stream.Collectors.toList()); + q.setParameter("mediaIds", mediaIds); + } + if (group.itemLimit() != null && group.itemLimit() > 0) q.setMaxResults(group.itemLimit()); + List mediaList = q.getResultList(); + for (com.sisvietnamvn.web.domain.Media m : mediaList) { + dynamicItems.add(new SwiperSliderAdminController.SliderItem(m.getOriginalFilename(), m.getFileUrl(), m.getFileUrl(), null, null, null, null)); + } + } else { + if (group.items() != null) { + dynamicItems.addAll(group.items()); + } + } + return dynamicItems; + } + private String getSlideTemplateForSlug(String slug) { - if ("gallery".equals(slug)) { - return "
\"{{title}}\"
"; - } - if ("chung-chi".equals(slug) || "giay-chung-nhan".equals(slug) || "thuc-hanh".equals(slug) || "xac-nhan-thuc-hanh".equals(slug)) { - return ""; - } - if ("hinh-anh-thuc-hanh".equals(slug)) { - return "
\"{{title}}\"
"; - } - if ("course-card".equals(slug)) { - return "
"; + Optional optTpl = templateRepository.findBySlug("swiper-slide-" + slug); + if (optTpl.isPresent()) { + return optTpl.get().getHtmlTemplate(); } + return "
\"{{title}}\"
"; } @@ -303,31 +306,6 @@ public class SwiperSliderPlugin { return ""; } - private void seedDefaultData() { - String json = settingService.getValue(SETTING_KEY, ""); - if (json.isEmpty() || "[]".equals(json) || !json.contains("chung-chi") || json.contains("UMC_files")) { - String sampleData = "[{\"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\":\"\"}" + - "]},{\"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\":\"\"}" + - "]},{\"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\":\"\"}" + - "]},{\"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\":\"\"}" + - "]},{\"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\":\"\"}" + - "]}]"; - settingService.setValue(SETTING_KEY, sampleData); - LOG.info("Seeded default swiper slider data for all 5 training sliders"); - } - } private void seedComponentTemplate() { java.util.Optional opt = templateRepository.findBySlug("swiper-slider"); @@ -341,6 +319,25 @@ public class SwiperSliderPlugin { templateRepository.save(t); LOG.info("Seeded default component template: swiper-slider"); } + + seedSlideTemplate("swiper-slide-gallery", "Swiper Slide (Gallery)", "
\"{{title}}\"
"); + seedSlideTemplate("swiper-slide-chung-chi", "Swiper Slide (Chứng chỉ/Thẻ)", ""); + seedSlideTemplate("swiper-slide-hinh-anh-thuc-hanh", "Swiper Slide (Hình ảnh lớn)", "
\"{{title}}\"
"); + seedSlideTemplate("swiper-slide-course-card", "Swiper Slide (Course Card)", "
"); + } + + private void seedSlideTemplate(String slug, String name, String html) { + java.util.Optional opt = templateRepository.findBySlug(slug); + if (opt.isEmpty()) { + com.sisvietnamvn.web.domain.ComponentTemplate t = new com.sisvietnamvn.web.domain.ComponentTemplate(); + t.setSlug(slug); + t.setName(name); + t.setDescription("Template for " + name); + t.setActive(true); + t.setHtmlTemplate(html); + templateRepository.save(t); + LOG.info("Seeded default component template: {}", slug); + } } private String getDefaultTemplate() { @@ -352,7 +349,7 @@ public class SwiperSliderPlugin {
{{#each items}} -
+
{{title}}
diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/PageRepository.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/PageRepository.java index b96e6057..dea4eae2 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/PageRepository.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/PageRepository.java @@ -28,6 +28,11 @@ public interface PageRepository extends JpaRepository { */ Optional findByPageType(com.sisvietnamvn.web.domain.PageType pageType); + /** + * Find the first page matching a predefined CMS Page Type (prevents NonUniqueResultException). + */ + Optional findFirstByPageTypeOrderByIdAsc(com.sisvietnamvn.web.domain.PageType pageType); + /** * Find all pages with a given publication status. */ diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/MediaService.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/MediaService.java index e1df24a9..924ed9c4 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/MediaService.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/MediaService.java @@ -59,6 +59,7 @@ public class MediaService { Media media = new Media(); media.setFileUrl(fileUrl); media.setOriginalFilename(filename); + media.setStoredFilename(filename); media.setMediaType(MediaType.IMAGE); // Assume image for now mediaRepository.save(media); diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/PageService.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/PageService.java index aa7f647f..31796791 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/PageService.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/PageService.java @@ -67,7 +67,7 @@ public class PageService { @Transactional(readOnly = true) public Optional findByPageType(com.sisvietnamvn.web.domain.PageType pageType) { LOG.debug("Request to get Page by type : {}", pageType); - return pageRepository.findByPageType(pageType); + return pageRepository.findFirstByPageTypeOrderByIdAsc(pageType); } /** diff --git a/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260729104200_add_page_tag.xml b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260729104200_add_page_tag.xml new file mode 100644 index 00000000..be527802 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260729104200_add_page_tag.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml b/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml index 2746d62c..f1a7c34e 100644 --- a/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml +++ b/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml @@ -57,4 +57,5 @@ + diff --git a/sisvietnamvn_main/src/main/resources/static/css/custom.css b/sisvietnamvn_main/src/main/resources/static/css/custom.css index 00865231..f3327c14 100644 --- a/sisvietnamvn_main/src/main/resources/static/css/custom.css +++ b/sisvietnamvn_main/src/main/resources/static/css/custom.css @@ -1192,6 +1192,54 @@ figure.table table tr:hover { background-color: #f5f5f5; } +/* Container spacing */ +#selected_categoryIds, #selected_tagIds { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 8px; +} + +/* Dark Red Pill Badge */ +.category-badge { + display: inline-flex; + align-items: center; + background-color: #8b0000; /* Dark maroon / red background */ + color: #ffffff; /* White text */ + padding: 4px 6px 4px 12px; + border-radius: 12px; /* Pill shape */ + font-size: 13px; + font-family: sans-serif; + line-height: 1; +} + +/* Text spacing inside pill */ +.badge-text { + margin-right: 6px; + white-space: nowrap; +} + +/* Boxed 'x' button inside pill */ +.remove_category-badge { + background-color: #ffffff; /* White box background */ + color: #000000; /* Dark x text */ + border: 1px solid #333333; /* Dark outline */ + border-radius: 2px; /* Slightly rounded box */ + font-size: 13px; + font-weight: bold; + line-height: 1; + padding: 1px 4px; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.remove_category-badge:hover { + background-color: #f0f0f0; + color: #ff0000; +} + .training-layout table tr:first-child td, .training-layout table th { background-color: var(--color-old-brick, #881c1c) !important; @@ -1219,10 +1267,17 @@ figure.table table tr:hover { .flex { display: flex !important; + .h-stretch { + height: stretch; + } } .flex-col { flex-direction: column; } + .flex-row { + flex-direction: row; + } + .items-center { align-items: center; } @@ -1270,10 +1325,13 @@ figure.table table tr:hover { } } + .swiper-dao-tao-02 a { + background-color: var(--color-white); + } + .swiper-slide { - border: solid 1px #efeff0; - border-radius: 15px; padding: 1rem; + height: stretch; .btn { padding: 0.75rem; border-radius: 15px; @@ -1286,4 +1344,92 @@ figure.table table tr:hover { } } +} + +.border-gray-1 { + border: 1px solid rgb(239, 239, 240); + border-radius: 15px; + +} + + +/* CSS cho Component Carousel */ +.course-swiper-component { + position: relative; + padding: 10px 24px; /* Chừa không gian 2 bên cho nút bấm nhô ra */ + width: 100%; +} + +.course-swiper-component .swiper { + width: 100%; + padding-bottom: 10px; +} + +/* Đảm bảo chiều cao các Slide bằng nhau */ +.course-swiper-component .swiper-slide { + height: auto; + display: flex; +} + +/* Custom Nút bấm hình tròn trắng nổi 2 bên mép */ +.course-swiper-component .swiper-button-prev, +.course-swiper-component .swiper-button-next { + width: 42px; + height: 42px; + background-color: #ffffff; + border-radius: 50%; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + color: #333333; + top: 50%; + transform: translateY(-50%); + z-index: 10; +} + +.course-swiper-component .swiper-button-prev { left: 0; } +.course-swiper-component .swiper-button-next { right: 0; } + +.course-swiper-component .swiper-button-prev::after, +.course-swiper-component .swiper-button-next::after { + font-size: 15px; + font-weight: bold; +} + +.course-swiper-component .swiper-button-prev:hover, +.course-swiper-component .swiper-button-next:hover { + background-color: #f8f9fa; + color: #9b1b1b; +} + +/* Ép hiển thị nút tròn < và > */ +.swiper-button-prev, +.swiper-button-next { + width: 44px !important; + height: 44px !important; + background-color: #ffffff !important; /* Nền trắng */ + border-radius: 50% !important; /* Bo tròn */ + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25) !important; /* Đổ bóng */ + color: #111111 !important; /* Màu mũi tên đen */ + + display: flex !important; + align-items: center !important; + justify-content: center !important; + + font-size: 22px !important; /* Kích thước chữ < và > */ + font-weight: bold !important; + z-index: 50 !important; /* Đảm bảo nổi lên trên các card */ + cursor: pointer !important; +} + +/* Xóa icon font mặc định của Swiper để không bị đè chữ */ +.swiper-button-prev::after, +.swiper-button-next::after { + display: none !important; +} + +/* Hiệu ứng khi di chuột vào nút */ +.swiper-button-prev:hover, +.swiper-button-next:hover { + background-color: #f1f1f1 !important; + color: #9b1b1b !important; + transform: scale(1.08); } \ No newline at end of file diff --git a/sisvietnamvn_main/src/main/resources/static/js/main.js b/sisvietnamvn_main/src/main/resources/static/js/main.js new file mode 100644 index 00000000..cd8d9f88 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/static/js/main.js @@ -0,0 +1,63 @@ +/** + * Main JavaScript for SIS Vietnam + * Automatically loads and initializes global components like Swiper sliders + */ + +document.addEventListener('DOMContentLoaded', function () { + initGlobalSwipers(); + + const courseSwiper = new Swiper('.courseSwiper', { + loop: true, + spaceBetween: 20, // Khoảng cách giữa các card + autoplay: { + delay: 3500, // Tự động trượt sau 3.5 giây + disableOnInteraction: false, + pauseOnMouseEnter: true, + }, + navigation: { + nextEl: '.course-swiper-component .swiper-button-next', + prevEl: '.course-swiper-component .swiper-button-prev', + }, + // Breakpoints chuẩn Responsive + breakpoints: { + 320: { slidesPerView: 1 }, // Điện thoại + 640: { slidesPerView: 2 }, // Tablet nhỏ + 1024: { slidesPerView: 3 }, // Laptop + 1280: { slidesPerView: 4 } // Màn hình rộng + } + }); +}); + +function initGlobalSwipers() { + const swipers = document.querySelectorAll('.swiper'); + + swipers.forEach(function(el) { + // Skip if already initialized by another script + if (el.swiper) return; + + // Try to find navigation buttons in the parent container + const parent = el.parentElement; + + // Look for standard swiper buttons or custom ones (e.g. .btn-...-prev) + const prevBtn = parent.querySelector('.swiper-button-prev') || parent.querySelector('[class*="-prev"]'); + const nextBtn = parent.querySelector('.swiper-button-next') || parent.querySelector('[class*="-next"]'); + const pagination = parent.querySelector('.swiper-pagination'); + + new Swiper(el, { + slidesPerView: 'auto', + spaceBetween: 16, + observer: true, + observeParents: true, + watchOverflow: false, + rewind: true, + navigation: { + nextEl: nextBtn, + prevEl: prevBtn, + }, + pagination: pagination ? { + el: pagination, + clickable: true, + } : false + }); + }); +} diff --git a/sisvietnamvn_main/src/main/resources/templates/dao-tao.html b/sisvietnamvn_main/src/main/resources/templates/dao-tao.html index 69b6e19f..b333bf87 100644 --- a/sisvietnamvn_main/src/main/resources/templates/dao-tao.html +++ b/sisvietnamvn_main/src/main/resources/templates/dao-tao.html @@ -45,7 +45,7 @@
-
+
banner (1) banner (1) @@ -61,8 +61,8 @@
-
-
+
+
Với chức năng là cơ sở giảng dạy thực hành, đào tạo là một trong những hoạt động nòng cốt của Bệnh viện Đại học Y Dược TP. Hồ Chí Minh, nhằm tối ưu công tác đào tạo, Bệnh viện đa dạng hóa các loại hình đào tạo để đáp ứng các yêu cầu về đào tạo của nội bộ, của các cơ sở y tế khác và của học viên nước ngoài.Với nguồn nhân lực dồi dào và trình độ chuyên môn cao, các chuyên khoa lâm sàng và cận lâm sàng lớn mạnh, số lượng người bệnh đến khám và chữa bệnh tăng cao, Bệnh viện Đại học Y Dược TP. Hồ Chí Minh còn là nơi thực hành không những của nhân viên y tế Việt Nam, mà còn là sự lựa chọn của rất nhiều sinh viên các nước như: Nhật Bản, Úc, Hoa Kỳ, Thái Lan, CHLB Đức, Pháp, Bỉ, Áo, Đan Mạch, Ấn Độ, …
Ngoài ra, hình thức đào tạo theo hợp đồng/đào tạo theo nhiệm vụ chỉ đạo tuyến/đào tạo chuyển giao kỹ thuật và các khóa đào tạo chuyên môn nghiệp vụ khác của ngành y tế mà không thuộc hệ thống bằng cấp quốc gia cũng là một nhiệm vụ của Bệnh viện Đại học Y Dược TP. Hồ Chí Minh.
@@ -100,20 +100,25 @@
-
-
-
+
+
+

Khóa học Đào tạo

-
-
- -
- -
-
-
+
+

Cấp chứng chỉ

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

Cấp giấy chứng nhận

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

Đào tạo cấp giấy xác nhận quá trình thực hành

-
-
- - -
-
-
-
-
-
- +
+ +
+
+ +
+
-
+ +
-
Đào tạo thực hành sinh viên, học viên Đại học Y Dược TP. Hồ Chí Minh
+
Đào tạo thực hành sinh viên, học viên Bệnh viện Đa Khoa Quốc Tế S.I.S Cần Thơ
-

Với chức năng là cơ sở giảng dạy thực hành, đào tạo là một trong những hoạt động nòng cốt của Bệnh viện Đại học Y Dược TP. Hồ Chí Minh, nhằm tối ưu công tác đào tạo, Bệnh viện đa dạng hóa các loại hình đào tạo để đáp ứng các yêu cầu về đào tạo của nội bộ, của các cơ sở y tế khác và của học viên nước ngoài.

+

Với chức năng là cơ sở giảng dạy thực hành, đào tạo là một trong những hoạt động nòng cốt của Bệnh viện Bệnh viện Đa Khoa Quốc Tế S.I.S Cần Thơ, nhằm tối ưu công tác đào tạo, Bệnh viện đa dạng hóa các loại hình đào tạo để đáp ứng các yêu cầu về đào tạo của nội bộ, của các cơ sở y tế khác và của học viên nước ngoài.

Xem chi tiết
@@ -260,7 +219,7 @@
-
+
Đào tạo - Chuyển giao kỹ thuật
@@ -272,7 +231,7 @@ Xem chi tiết
-
+
training @@ -286,26 +245,22 @@ Xem chi tiết
-
- -
+
- -
- -
- - +
+
@@ -323,85 +278,7 @@
- - - - -
- \ No newline at end of file diff --git a/sisvietnamvn_main/src/main/resources/templates/fragments/layout.html b/sisvietnamvn_main/src/main/resources/templates/fragments/layout.html index 64168b6d..25087477 100644 --- a/sisvietnamvn_main/src/main/resources/templates/fragments/layout.html +++ b/sisvietnamvn_main/src/main/resources/templates/fragments/layout.html @@ -1,52 +1,58 @@ - + - - - - + + + UMass Amherst - - + + + href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400..700;1,400..700&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" + rel="stylesheet" + /> - + + + + - - + + - +
- -
+ +
- -
-
- -
-
+ +
+
+ +
+
- -
+ +
+ + + + - - - \ No newline at end of file + + diff --git a/sisvietnamvn_main/src/main/resources/templates/plugins/swiper-slider/admin-form.html b/sisvietnamvn_main/src/main/resources/templates/plugins/swiper-slider/admin-form.html index 720b840c..138e565a 100644 --- a/sisvietnamvn_main/src/main/resources/templates/plugins/swiper-slider/admin-form.html +++ b/sisvietnamvn_main/src/main/resources/templates/plugins/swiper-slider/admin-form.html @@ -1,110 +1,339 @@ - - + + Swiper Slider Settings - - -
-
+ + +
+
-

Sửa Nhóm Slider

- Quay lại Danh sách +

Sửa Nhóm Slider

+ Quay lại Danh sách
- +
- - - -
-
-
Thông tin cơ bản
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
- - -
-
-
+ + -
-
-
Danh sách Slides
- -
-
-
-
+
+
+
Thông tin cơ bản
- - +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + + + + + +
+
+
+ +
+
+
Danh sách Slides
+ +
+
+
+
+
+ + -
+ +
- -
- + +
+ diff --git a/sisvietnamvn_main/src/main/resources/templates/themes/umass/layout.html b/sisvietnamvn_main/src/main/resources/templates/themes/umass/layout.html index 369c682f..54749e4d 100644 --- a/sisvietnamvn_main/src/main/resources/templates/themes/umass/layout.html +++ b/sisvietnamvn_main/src/main/resources/templates/themes/umass/layout.html @@ -1,49 +1,71 @@ - + - + UMass Amherst Theme - - - - - + + + + + + + + + - + - +
- +
- +
-
-
-