From 302785b60b81a48dfc6ec6900a1f74711bbabbf7 Mon Sep 17 00:00:00 2001 From: Victor Phan Date: Thu, 2 Jul 2026 18:48:00 +0700 Subject: [PATCH] =?UTF-8?q?b=E1=BB=95=20sung=20th=C3=AAm=20c=C3=A1c=20ch?= =?UTF-8?q?=E1=BB=A9c=20n=C4=83ng=20c=C3=B2n=20thi=E1=BA=BFu=20c=E1=BB=A7a?= =?UTF-8?q?=20appearance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/GlobalControllerAdvice.java | 48 +++++++- .../manage/ManageCustomizeController.java | 44 +++++++ .../manage/ManageMenuController.java | 72 +++++++++++ .../manage/ManageThemeController.java | 68 +++++++++++ .../manage/ManageThemeEditorController.java | 66 ++++++++++ .../manage/ManageWidgetController.java | 85 +++++++++++++ .../com/sisvietnamvn/web/domain/Menu.java | 85 +++++++++++++ .../com/sisvietnamvn/web/domain/MenuItem.java | 106 ++++++++++++++++ .../web/repository/MenuItemRepository.java | 12 ++ .../web/repository/MenuRepository.java | 15 +++ .../sisvietnamvn/web/service/MenuService.java | 43 +++++++ .../web/service/dto/WidgetDto.java | 40 ++++++ .../20260702184000_add_menu_entity.xml | 57 +++++++++ .../resources/config/liquibase/master.xml | 1 + .../templates/fragments/manage-layout.html | 4 + .../templates/manage/customize/index.html | 60 +++++++++ .../templates/manage/menus/edit.html | 70 +++++++++++ .../templates/manage/menus/list.html | 85 +++++++++++++ .../templates/manage/themes/editor.html | 62 ++++++++++ .../templates/manage/themes/list.html | 56 ++++++--- .../templates/manage/widgets/list.html | 115 ++++++++++++++++++ .../src/main/resources/templates/page.html | 12 ++ .../templates/themes/default/footer.html | 22 +++- .../templates/themes/default/header.html | 8 +- .../templates/themes/default/layout.html | 15 ++- .../templates/themes/modern/footer.html | 20 ++- .../templates/themes/modern/header.html | 7 +- .../templates/themes/modern/layout.html | 16 ++- 28 files changed, 1265 insertions(+), 29 deletions(-) create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageCustomizeController.java create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageMenuController.java create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageThemeEditorController.java create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageWidgetController.java create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Menu.java create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/MenuItem.java create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/MenuItemRepository.java create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/MenuRepository.java create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/MenuService.java create mode 100644 sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/dto/WidgetDto.java create mode 100644 sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260702184000_add_menu_entity.xml create mode 100644 sisvietnamvn_main/src/main/resources/templates/manage/customize/index.html create mode 100644 sisvietnamvn_main/src/main/resources/templates/manage/menus/edit.html create mode 100644 sisvietnamvn_main/src/main/resources/templates/manage/menus/list.html create mode 100644 sisvietnamvn_main/src/main/resources/templates/manage/themes/editor.html create mode 100644 sisvietnamvn_main/src/main/resources/templates/manage/widgets/list.html diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/GlobalControllerAdvice.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/GlobalControllerAdvice.java index 9025e604..5869334c 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/GlobalControllerAdvice.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/GlobalControllerAdvice.java @@ -15,10 +15,12 @@ import java.util.Map; public class GlobalControllerAdvice { private final SettingService settingService; + private final com.sisvietnamvn.web.service.MenuService menuService; private final PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); - public GlobalControllerAdvice(SettingService settingService) { + public GlobalControllerAdvice(SettingService settingService, com.sisvietnamvn.web.service.MenuService menuService) { this.settingService = settingService; + this.menuService = menuService; } /** @@ -41,4 +43,48 @@ public class GlobalControllerAdvice { return activeTheme; } + + @ModelAttribute("primaryMenu") + public com.sisvietnamvn.web.domain.Menu getPrimaryMenu() { + return menuService.findByLocation("PRIMARY").orElse(null); + } + + @ModelAttribute("footerMenu") + public com.sisvietnamvn.web.domain.Menu getFooterMenu() { + return menuService.findByLocation("FOOTER").orElse(null); + } + + @ModelAttribute("sidebarWidgets") + public java.util.List getSidebarWidgets() { + return getWidgets("sidebar"); + } + + @ModelAttribute("footerWidgets") + public java.util.List getFooterWidgets() { + return getWidgets("footer"); + } + + @ModelAttribute("themeModPrimaryColor") + public String getThemeModPrimaryColor() { + return settingService.getValue("theme_mod_primaryColor", "#007bff"); + } + + @ModelAttribute("themeModFontFamily") + public String getThemeModFontFamily() { + return settingService.getValue("theme_mod_fontFamily", "sans-serif"); + } + + @ModelAttribute("themeModCustomCss") + public String getThemeModCustomCss() { + return settingService.getValue("theme_mod_customCss", ""); + } + + private java.util.List getWidgets(String area) { + String json = settingService.getValue("theme_widgets_" + area, "[]"); + try { + return new com.fasterxml.jackson.databind.ObjectMapper().readValue(json, new com.fasterxml.jackson.core.type.TypeReference>() {}); + } catch (com.fasterxml.jackson.core.JsonProcessingException e) { + return new java.util.ArrayList<>(); + } + } } diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageCustomizeController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageCustomizeController.java new file mode 100644 index 00000000..e6ddf3cf --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageCustomizeController.java @@ -0,0 +1,44 @@ +package com.sisvietnamvn.web.controller.manage; + +import com.sisvietnamvn.web.security.AuthoritiesConstants; +import com.sisvietnamvn.web.service.SettingService; +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 the Theme Customizer in the Admin panel. + */ +@Controller +@RequestMapping("/manage/customize") +@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\")") +public class ManageCustomizeController { + + private final SettingService settingService; + + public ManageCustomizeController(SettingService settingService) { + this.settingService = settingService; + } + + @GetMapping + public String customizerView(Model model) { + // Load current customizer settings + model.addAttribute("primaryColor", settingService.getValue("theme_mod_primaryColor", "#007bff")); + model.addAttribute("fontFamily", settingService.getValue("theme_mod_fontFamily", "sans-serif")); + model.addAttribute("customCss", settingService.getValue("theme_mod_customCss", "")); + + return "manage/customize/index"; + } + + @PostMapping("/save") + public String saveCustomizer(@RequestParam String primaryColor, @RequestParam String fontFamily, @RequestParam String customCss, RedirectAttributes redirectAttributes) { + settingService.setValue("theme_mod_primaryColor", primaryColor); + settingService.setValue("theme_mod_fontFamily", fontFamily); + settingService.setValue("theme_mod_customCss", customCss); + + redirectAttributes.addFlashAttribute("successMessage", "Customizations saved securely."); + return "redirect:/manage/customize"; + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageMenuController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageMenuController.java new file mode 100644 index 00000000..1a0c3a93 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageMenuController.java @@ -0,0 +1,72 @@ +package com.sisvietnamvn.web.controller.manage; + +import com.sisvietnamvn.web.domain.Menu; +import com.sisvietnamvn.web.domain.MenuItem; +import com.sisvietnamvn.web.security.AuthoritiesConstants; +import com.sisvietnamvn.web.service.MenuService; +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 Navigation Menus in the Admin panel. + */ +@Controller +@RequestMapping("/manage/menus") +@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\")") +public class ManageMenuController { + + private final MenuService menuService; + + public ManageMenuController(MenuService menuService) { + this.menuService = menuService; + } + + @GetMapping + public String listMenus(Model model) { + model.addAttribute("menus", menuService.findAll()); + return "manage/menus/list"; + } + + @PostMapping("/create") + public String createMenu(@RequestParam String name, @RequestParam(required = false) String location, RedirectAttributes redirectAttributes) { + Menu menu = new Menu(); + menu.setName(name); + menu.setLocation(location); + menuService.save(menu); + redirectAttributes.addFlashAttribute("successMessage", "Menu created successfully."); + return "redirect:/manage/menus"; + } + + @GetMapping("/{id}/edit") + public String editMenu(@PathVariable Long id, Model model) { + Menu menu = menuService.findById(id).orElseThrow(() -> new IllegalArgumentException("Invalid menu Id")); + model.addAttribute("menu", menu); + return "manage/menus/edit"; + } + + @PostMapping("/{id}/items/add") + public String addMenuItem(@PathVariable Long id, @RequestParam String label, @RequestParam String url, RedirectAttributes redirectAttributes) { + Menu menu = menuService.findById(id).orElseThrow(() -> new IllegalArgumentException("Invalid menu Id")); + MenuItem item = new MenuItem(); + item.setLabel(label); + item.setUrl(url); + item.setMenu(menu); + item.setDisplayOrder(menu.getItems().size()); + + menu.getItems().add(item); + menuService.save(menu); + + redirectAttributes.addFlashAttribute("successMessage", "Menu item added."); + return "redirect:/manage/menus/" + id + "/edit"; + } + + @PostMapping("/{id}/delete") + public String deleteMenu(@PathVariable Long id, RedirectAttributes redirectAttributes) { + menuService.deleteById(id); + redirectAttributes.addFlashAttribute("successMessage", "Menu deleted successfully."); + return "redirect:/manage/menus"; + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageThemeController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageThemeController.java index b96bb16f..f8151823 100644 --- a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageThemeController.java +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageThemeController.java @@ -95,4 +95,72 @@ public class ManageThemeController { redirectAttributes.addFlashAttribute("successMessage", "New theme activated successfully."); return "redirect:/manage/themes"; } + + @PostMapping("/delete") + public String deleteTheme(@RequestParam("themeKey") String themeKey, RedirectAttributes redirectAttributes) { + String activeTheme = settingService.getValue("active_theme", "default"); + if (themeKey.equals(activeTheme)) { + redirectAttributes.addFlashAttribute("errorMessage", "Cannot delete the active theme."); + return "redirect:/manage/themes"; + } + + try { + java.nio.file.Path themePath = java.nio.file.Paths.get("src/main/resources/templates/themes/", themeKey); + if (java.nio.file.Files.exists(themePath)) { + // Delete directory recursively + org.springframework.util.FileSystemUtils.deleteRecursively(themePath); + redirectAttributes.addFlashAttribute("successMessage", "Theme deleted successfully."); + } else { + redirectAttributes.addFlashAttribute("errorMessage", "Theme folder not found."); + } + } catch (java.io.IOException e) { + redirectAttributes.addFlashAttribute("errorMessage", "Error deleting theme: " + e.getMessage()); + } + return "redirect:/manage/themes"; + } + + @PostMapping("/upload") + public String uploadTheme(@RequestParam("file") org.springframework.web.multipart.MultipartFile file, RedirectAttributes redirectAttributes) { + if (file.isEmpty() || !file.getOriginalFilename().endsWith(".zip")) { + redirectAttributes.addFlashAttribute("errorMessage", "Please select a valid .zip file."); + return "redirect:/manage/themes"; + } + + try { + java.nio.file.Path targetDir = java.nio.file.Paths.get("src/main/resources/templates/themes/"); + java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(file.getInputStream()); + java.util.zip.ZipEntry zipEntry = zis.getNextEntry(); + + while (zipEntry != null) { + java.nio.file.Path newPath = zipSlipProtect(zipEntry, targetDir); + if (zipEntry.isDirectory()) { + java.nio.file.Files.createDirectories(newPath); + } else { + if (newPath.getParent() != null) { + if (java.nio.file.Files.notExists(newPath.getParent())) { + java.nio.file.Files.createDirectories(newPath.getParent()); + } + } + java.nio.file.Files.copy(zis, newPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } + zipEntry = zis.getNextEntry(); + } + zis.closeEntry(); + zis.close(); + + redirectAttributes.addFlashAttribute("successMessage", "Theme uploaded and extracted successfully."); + } catch (Exception e) { + redirectAttributes.addFlashAttribute("errorMessage", "Failed to upload theme: " + e.getMessage()); + } + return "redirect:/manage/themes"; + } + + private java.nio.file.Path zipSlipProtect(java.util.zip.ZipEntry zipEntry, java.nio.file.Path targetDir) throws java.io.IOException { + java.nio.file.Path targetDirResolved = targetDir.resolve(zipEntry.getName()); + java.nio.file.Path normalizePath = targetDirResolved.normalize(); + if (!normalizePath.startsWith(targetDir)) { + throw new java.io.IOException("Bad zip entry: " + zipEntry.getName()); + } + return normalizePath; + } } diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageThemeEditorController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageThemeEditorController.java new file mode 100644 index 00000000..11a19f25 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageThemeEditorController.java @@ -0,0 +1,66 @@ +package com.sisvietnamvn.web.controller.manage; + +import com.sisvietnamvn.web.security.AuthoritiesConstants; +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; + +import java.io.IOException; +import java.nio.file.*; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Controller for the Theme File Editor. + */ +@Controller +@RequestMapping("/manage/theme-editor") +@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") +public class ManageThemeEditorController { + + private final Path themesBaseDir = Paths.get("src/main/resources/templates/themes").toAbsolutePath().normalize(); + + @GetMapping + public String editorView(@RequestParam(required = false) String file, Model model) { + try (Stream walk = Files.walk(themesBaseDir)) { + List fileTree = walk.filter(Files::isRegularFile) + .map(path -> themesBaseDir.relativize(path).toString()) + .collect(Collectors.toList()); + model.addAttribute("files", fileTree); + + if (file != null && !file.isEmpty()) { + Path targetPath = themesBaseDir.resolve(file).normalize(); + if (targetPath.startsWith(themesBaseDir) && Files.exists(targetPath)) { + String content = Files.readString(targetPath); + model.addAttribute("selectedFile", file); + model.addAttribute("fileContent", content); + } else { + model.addAttribute("errorMessage", "Invalid file path."); + } + } + } catch (IOException e) { + model.addAttribute("errorMessage", "Error loading theme files: " + e.getMessage()); + } + return "manage/themes/editor"; + } + + @PostMapping("/save") + public String saveFile(@RequestParam("selectedFile") String file, @RequestParam("fileContent") String content, RedirectAttributes redirectAttributes) { + try { + Path targetPath = themesBaseDir.resolve(file).normalize(); + if (targetPath.startsWith(themesBaseDir) && Files.exists(targetPath)) { + Files.writeString(targetPath, content, StandardOpenOption.TRUNCATE_EXISTING); + redirectAttributes.addFlashAttribute("successMessage", "File saved successfully."); + return "redirect:/manage/theme-editor?file=" + file; + } else { + redirectAttributes.addFlashAttribute("errorMessage", "Security violation: Invalid file path."); + } + } catch (IOException e) { + redirectAttributes.addFlashAttribute("errorMessage", "Error saving file: " + e.getMessage()); + } + return "redirect:/manage/theme-editor"; + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageWidgetController.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageWidgetController.java new file mode 100644 index 00000000..86b3b97a --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/controller/manage/ManageWidgetController.java @@ -0,0 +1,85 @@ +package com.sisvietnamvn.web.controller.manage; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sisvietnamvn.web.security.AuthoritiesConstants; +import com.sisvietnamvn.web.service.SettingService; +import com.sisvietnamvn.web.service.dto.WidgetDto; +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; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Controller for managing Theme Widgets in the Admin panel. + */ +@Controller +@RequestMapping("/manage/widgets") +@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\")") +public class ManageWidgetController { + + private final SettingService settingService; + private final ObjectMapper objectMapper; + + public ManageWidgetController(SettingService settingService, ObjectMapper objectMapper) { + this.settingService = settingService; + this.objectMapper = objectMapper; + } + + private List getWidgets(String area) { + String json = settingService.getValue("theme_widgets_" + area, "[]"); + try { + return objectMapper.readValue(json, new TypeReference>() {}); + } catch (JsonProcessingException e) { + return new ArrayList<>(); + } + } + + private void saveWidgets(String area, List widgets) { + try { + String json = objectMapper.writeValueAsString(widgets); + settingService.setValue("theme_widgets_" + area, json); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + + @GetMapping + public String listWidgets(Model model) { + model.addAttribute("sidebarWidgets", getWidgets("sidebar")); + model.addAttribute("footerWidgets", getWidgets("footer")); + return "manage/widgets/list"; + } + + @PostMapping("/add") + public String addWidget(@RequestParam String area, @RequestParam String type, @RequestParam String title, @RequestParam String content, RedirectAttributes redirectAttributes) { + List widgets = getWidgets(area); + WidgetDto widget = new WidgetDto(); + widget.setId(UUID.randomUUID().toString()); + widget.setType(type); + widget.setTitle(title); + widget.setContent(content); + + widgets.add(widget); + saveWidgets(area, widgets); + + redirectAttributes.addFlashAttribute("successMessage", "Widget added to " + area); + return "redirect:/manage/widgets"; + } + + @PostMapping("/delete") + public String deleteWidget(@RequestParam String area, @RequestParam String id, RedirectAttributes redirectAttributes) { + List widgets = getWidgets(area); + widgets.removeIf(w -> w.getId().equals(id)); + saveWidgets(area, widgets); + + redirectAttributes.addFlashAttribute("successMessage", "Widget removed."); + return "redirect:/manage/widgets"; + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Menu.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Menu.java new file mode 100644 index 00000000..665cea85 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/Menu.java @@ -0,0 +1,85 @@ +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.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A Menu entity for managing navigation menus. + * Maps to the "sis_menu" database table. + */ +@Entity +@Table(name = "sis_menu") +public class Menu 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 = "name", length = 255, nullable = false) + private String name; + + @Size(max = 100) + @Column(name = "location", length = 100) + private String location; + + @OneToMany(mappedBy = "menu", cascade = CascadeType.ALL, orphanRemoval = true) + @OrderBy("displayOrder ASC") + private List items = new ArrayList<>(); + + @Override + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Menu menu = (Menu) o; + return Objects.equals(id, menu.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/MenuItem.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/MenuItem.java new file mode 100644 index 00000000..48df82b7 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/domain/MenuItem.java @@ -0,0 +1,106 @@ +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; + +/** + * A MenuItem entity representing a single link in a Menu. + * Maps to the "sis_menu_item" database table. + */ +@Entity +@Table(name = "sis_menu_item") +public class MenuItem extends AbstractAuditingEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") + @SequenceGenerator(name = "sequenceGenerator") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "menu_id", nullable = false) + private Menu menu; + + @NotNull + @Size(max = 255) + @Column(name = "label", length = 255, nullable = false) + private String label; + + @Size(max = 500) + @Column(name = "url", length = 500) + private String url; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private MenuItem parent; + + @Column(name = "display_order") + private Integer displayOrder = 0; + + @Override + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Menu getMenu() { + return menu; + } + + public void setMenu(Menu menu) { + this.menu = menu; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public MenuItem getParent() { + return parent; + } + + public void setParent(MenuItem parent) { + this.parent = parent; + } + + public Integer getDisplayOrder() { + return displayOrder; + } + + public void setDisplayOrder(Integer displayOrder) { + this.displayOrder = displayOrder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MenuItem menuItem = (MenuItem) o; + return Objects.equals(id, menuItem.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/MenuItemRepository.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/MenuItemRepository.java new file mode 100644 index 00000000..790bd97d --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/MenuItemRepository.java @@ -0,0 +1,12 @@ +package com.sisvietnamvn.web.repository; + +import com.sisvietnamvn.web.domain.MenuItem; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * Spring Data JPA repository for the MenuItem entity. + */ +@Repository +public interface MenuItemRepository extends JpaRepository { +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/MenuRepository.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/MenuRepository.java new file mode 100644 index 00000000..2920f93d --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/repository/MenuRepository.java @@ -0,0 +1,15 @@ +package com.sisvietnamvn.web.repository; + +import com.sisvietnamvn.web.domain.Menu; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/** + * Spring Data JPA repository for the Menu entity. + */ +@Repository +public interface MenuRepository extends JpaRepository { + Optional findByLocation(String location); +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/MenuService.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/MenuService.java new file mode 100644 index 00000000..c507c275 --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/MenuService.java @@ -0,0 +1,43 @@ +package com.sisvietnamvn.web.service; + +import com.sisvietnamvn.web.domain.Menu; +import com.sisvietnamvn.web.repository.MenuRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@Transactional +public class MenuService { + + private final MenuRepository menuRepository; + + public MenuService(MenuRepository menuRepository) { + this.menuRepository = menuRepository; + } + + @Transactional(readOnly = true) + public List findAll() { + return menuRepository.findAll(); + } + + @Transactional(readOnly = true) + public Optional findById(Long id) { + return menuRepository.findById(id); + } + + @Transactional(readOnly = true) + public Optional findByLocation(String location) { + return menuRepository.findByLocation(location); + } + + public Menu save(Menu menu) { + return menuRepository.save(menu); + } + + public void deleteById(Long id) { + menuRepository.deleteById(id); + } +} diff --git a/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/dto/WidgetDto.java b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/dto/WidgetDto.java new file mode 100644 index 00000000..cecda9aa --- /dev/null +++ b/sisvietnamvn_main/src/main/java/com/sisvietnamvn/web/service/dto/WidgetDto.java @@ -0,0 +1,40 @@ +package com.sisvietnamvn.web.service.dto; + +public class WidgetDto { + private String id; + private String type; // "HTML", "TEXT" + private String title; + private String content; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260702184000_add_menu_entity.xml b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260702184000_add_menu_entity.xml new file mode 100644 index 00000000..a94c858f --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/config/liquibase/changelog/20260702184000_add_menu_entity.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml b/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml index 635ecc51..484e1a35 100644 --- a/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml +++ b/sisvietnamvn_main/src/main/resources/config/liquibase/master.xml @@ -29,6 +29,7 @@ + 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 4f965060..90753a31 100644 --- a/sisvietnamvn_main/src/main/resources/templates/fragments/manage-layout.html +++ b/sisvietnamvn_main/src/main/resources/templates/fragments/manage-layout.html @@ -124,6 +124,10 @@
Design Management:
Themes + Theme File Editor + Customize + Menus + Widgets
diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/customize/index.html b/sisvietnamvn_main/src/main/resources/templates/manage/customize/index.html new file mode 100644 index 00000000..0ff11746 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/templates/manage/customize/index.html @@ -0,0 +1,60 @@ + + + + + Theme Customizer + + + + + +
+ +
+
+ Close +
Customizer
+
+
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+ + +
+ +
+
+ + diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/menus/edit.html b/sisvietnamvn_main/src/main/resources/templates/manage/menus/edit.html new file mode 100644 index 00000000..7a355030 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/templates/manage/menus/edit.html @@ -0,0 +1,70 @@ + + + + Edit Menu Items + + +
+
+

Edit Menu

+ Back to Menus +
+ + + + +
+ +
+
+
+
Add Custom Link
+
+
+
+
+ + +
+
+ + +
+ +
+
+
+
+ + +
+
+
+
Menu Structure
+
+
+
    +
  • +
    + Link Text
    + URL +
    + + 0 +
  • +
+
+ No items in this menu yet. +
+
+
+
+
+
+ + diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/menus/list.html b/sisvietnamvn_main/src/main/resources/templates/manage/menus/list.html new file mode 100644 index 00000000..c718d652 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/templates/manage/menus/list.html @@ -0,0 +1,85 @@ + + + + Manage Menus + + +
+
+

Menus

+
+ + + + +
+
+
+
+
Create New Menu
+
+
+
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+
+
Existing Menus
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
NameLocationItems CountActions
Menu NameLocation0 + Edit Items +
+ +
+
No menus found.
+
+
+
+
+
+
+ + diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/themes/editor.html b/sisvietnamvn_main/src/main/resources/templates/manage/themes/editor.html new file mode 100644 index 00000000..cffae13a --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/templates/manage/themes/editor.html @@ -0,0 +1,62 @@ + + + + Theme File Editor + + +
+
+

Theme File Editor

+
+ +
+
+ +
+ +
+
+
+
Theme Files
+
+ +
+
+ + +
+
+
+
Editor
+
+
+
+ +

Select a file from the sidebar to start editing.

+
+ +
+ +
+ +
+ +
+
+
+
+
+
+ + diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/themes/list.html b/sisvietnamvn_main/src/main/resources/templates/manage/themes/list.html index 95786547..fb609310 100644 --- a/sisvietnamvn_main/src/main/resources/templates/manage/themes/list.html +++ b/sisvietnamvn_main/src/main/resources/templates/manage/themes/list.html @@ -27,33 +27,53 @@ -
+ +
+
+
Upload New Theme
+
+
+
+ +
+ +
+ +
+ The zip file should contain the theme folder structure (e.g., layout.html, header.html, footer.html). +
+
- -
-
-
-
Theme Name
- Active -
-
-
- -
-

A custom layout file loaded from the templates/themes directory.

+ +
+
+
+
+ +
Theme Name
+ +
+ + Active -
+ + - + +
+ + +
+ + +
-
-
- +
diff --git a/sisvietnamvn_main/src/main/resources/templates/manage/widgets/list.html b/sisvietnamvn_main/src/main/resources/templates/manage/widgets/list.html new file mode 100644 index 00000000..c9717a65 --- /dev/null +++ b/sisvietnamvn_main/src/main/resources/templates/manage/widgets/list.html @@ -0,0 +1,115 @@ + + + + Manage Widgets + + +
+
+

Widgets

+
+ + + + +
+ +
+
+
+
Add New Widget
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ + +
+
+
+
Sidebar Widgets
+
+
+
+
+
+
Title
+
+ + + +
+
+ Type +

Content...

+
+
+ No widgets in Sidebar. +
+
+
+
+
+ + +
+
+
+
Footer Widgets
+
+
+
+
+
+
Title
+
+ + + +
+
+ Type +

Content...

+
+
+ No widgets in Footer. +
+
+
+
+
+
+
+ + diff --git a/sisvietnamvn_main/src/main/resources/templates/page.html b/sisvietnamvn_main/src/main/resources/templates/page.html index 58602d16..f516ec9c 100644 --- a/sisvietnamvn_main/src/main/resources/templates/page.html +++ b/sisvietnamvn_main/src/main/resources/templates/page.html @@ -5,6 +5,18 @@ Page Title + + + diff --git a/sisvietnamvn_main/src/main/resources/templates/themes/default/footer.html b/sisvietnamvn_main/src/main/resources/templates/themes/default/footer.html index 926d083b..048b08df 100644 --- a/sisvietnamvn_main/src/main/resources/templates/themes/default/footer.html +++ b/sisvietnamvn_main/src/main/resources/templates/themes/default/footer.html @@ -4,8 +4,26 @@ -