diff --git a/app/[locale]/(admin)/admin/settings/tenant/component/table-user-level.tsx b/app/[locale]/(admin)/admin/settings/tenant/component/table-user-level.tsx index a9beac7..94dda41 100644 --- a/app/[locale]/(admin)/admin/settings/tenant/component/table-user-level.tsx +++ b/app/[locale]/(admin)/admin/settings/tenant/component/table-user-level.tsx @@ -26,6 +26,7 @@ import { ApprovalWorkflowForm } from "@/components/form/ApprovalWorkflowForm"; import { UserLevelsForm } from "@/components/form/UserLevelsForm"; import { useWorkflowModal } from "@/components/modals/WorkflowModalProvider"; import { useWorkflowStatusCheck } from "@/hooks/useWorkflowStatusCheck"; +import { useLocalStorage } from "@/hooks/use-local-storage"; import { CreateApprovalWorkflowWithClientSettingsRequest, UserLevelsCreateRequest, @@ -60,7 +61,7 @@ import { close, loading } from "@/config/swal"; import DetailTenant from "@/components/form/tenant/tenant-detail-update-form"; function TenantSettingsContentTable() { - const [activeTab, setActiveTab] = useState("workflows"); + const [activeTab, setActiveTab] = useLocalStorage('tenant-settings-active-tab', 'profile'); const [isUserLevelDialogOpen, setIsUserLevelDialogOpen] = useState(false); const [workflow, setWorkflow] = useState(null); @@ -220,7 +221,7 @@ function TenantSettingsContentTable() { @@ -243,7 +244,7 @@ function TenantSettingsContentTable() { {/* Approval Workflows Tab */} - + diff --git a/app/[locale]/content/audio/detail/[id]/page.tsx b/app/[locale]/(public)/content/audio/detail/[id]/page.tsx similarity index 100% rename from app/[locale]/content/audio/detail/[id]/page.tsx rename to app/[locale]/(public)/content/audio/detail/[id]/page.tsx diff --git a/app/[locale]/content/image/detail/[id]/page.tsx b/app/[locale]/(public)/content/image/detail/[id]/page.tsx similarity index 100% rename from app/[locale]/content/image/detail/[id]/page.tsx rename to app/[locale]/(public)/content/image/detail/[id]/page.tsx diff --git a/app/[locale]/content/layout.tsx b/app/[locale]/(public)/content/layout.tsx similarity index 100% rename from app/[locale]/content/layout.tsx rename to app/[locale]/(public)/content/layout.tsx diff --git a/app/[locale]/content/text/detail/[id]/page.tsx b/app/[locale]/(public)/content/text/detail/[id]/page.tsx similarity index 100% rename from app/[locale]/content/text/detail/[id]/page.tsx rename to app/[locale]/(public)/content/text/detail/[id]/page.tsx diff --git a/app/[locale]/content/video/comment/[id]/page.tsx b/app/[locale]/(public)/content/video/comment/[id]/page.tsx similarity index 100% rename from app/[locale]/content/video/comment/[id]/page.tsx rename to app/[locale]/(public)/content/video/comment/[id]/page.tsx diff --git a/app/[locale]/content/video/detail/[id]/page.tsx b/app/[locale]/(public)/content/video/detail/[id]/page.tsx similarity index 100% rename from app/[locale]/content/video/detail/[id]/page.tsx rename to app/[locale]/(public)/content/video/detail/[id]/page.tsx diff --git a/app/[locale]/(public)/(tenant)/tenant/[tenant-name]/layout.tsx b/app/[locale]/(public)/tenant/[tenant-name]/layout.tsx similarity index 100% rename from app/[locale]/(public)/(tenant)/tenant/[tenant-name]/layout.tsx rename to app/[locale]/(public)/tenant/[tenant-name]/layout.tsx diff --git a/app/[locale]/(public)/(tenant)/tenant/[tenant-name]/page.tsx b/app/[locale]/(public)/tenant/[tenant-name]/page.tsx similarity index 100% rename from app/[locale]/(public)/(tenant)/tenant/[tenant-name]/page.tsx rename to app/[locale]/(public)/tenant/[tenant-name]/page.tsx diff --git a/components/form/ApprovalWorkflowForm.tsx b/components/form/ApprovalWorkflowForm.tsx index 23974fd..a2f5eb7 100644 --- a/components/form/ApprovalWorkflowForm.tsx +++ b/components/form/ApprovalWorkflowForm.tsx @@ -687,6 +687,7 @@ export const ApprovalWorkflowForm: React.FC = ({ + )} )} +

+ Format yang didukung: JPG, PNG, WebP. Maksimal 5MB. +

{/* Nama Perusahaan */} @@ -278,7 +340,7 @@ export default function TenantCompanyUpdateForm({ {/* Status Aktif */} -
+ {/*
-
+
*/} - + diff --git a/components/landing-page/category.tsx b/components/landing-page/category.tsx index 563e700..f9d47e5 100644 --- a/components/landing-page/category.tsx +++ b/components/landing-page/category.tsx @@ -1,7 +1,38 @@ "use client"; +import { useState, useEffect } from "react"; +import { getArticleCategories, ArticleCategory } from "@/service/categories/article-categories"; + export default function Category() { - const categories = [ + const [categories, setCategories] = useState([]); + const [loading, setLoading] = useState(true); + + // Fetch article categories + useEffect(() => { + async function fetchCategories() { + try { + const response = await getArticleCategories(); + if (response?.data?.success && response.data.data) { + // Filter hanya kategori yang aktif dan published + const activeCategories = response.data.data.filter( + (category: ArticleCategory) => category.isActive && category.isPublish + ); + setCategories(activeCategories); + } + } catch (error) { + console.error("Error fetching article categories:", error); + // Fallback to static categories if API fails + setCategories([]); + } finally { + setLoading(false); + } + } + + fetchCategories(); + }, []); + + // Fallback categories jika API gagal atau tidak ada data + const fallbackCategories = [ "PON XXI", "OPERASI KETUPAT 2025", "HUT HUMAS KE-74", @@ -14,22 +45,50 @@ export default function Category() { "SEPUTAR PRESTASI", ]; + const displayCategories = categories.length > 0 ? categories : fallbackCategories; + return (

- 10 Kategori Paling Populer + {loading ? "Memuat Kategori..." : `${displayCategories.length} Kategori Paling Populer`}

-
- {categories.map((category, index) => ( - - ))} -
+ + {loading ? ( + // Loading skeleton +
+ {Array.from({ length: 10 }).map((_, index) => ( +
+
+
+ ))} +
+ ) : ( +
+ {displayCategories.map((category, index) => { + // Handle both API data and fallback data + const categoryTitle = typeof category === 'string' ? category : category.title; + const categorySlug = typeof category === 'string' ? category.toLowerCase().replace(/\s+/g, '-') : category.slug; + + return ( + + ); + })} +
+ )}
); diff --git a/components/landing-page/footer.tsx b/components/landing-page/footer.tsx index 02cc520..41939be 100644 --- a/components/landing-page/footer.tsx +++ b/components/landing-page/footer.tsx @@ -1,8 +1,52 @@ "use client"; -import { Instagram, ChevronLeft, ChevronRight } from "lucide-react"; +import { Instagram } from "lucide-react"; import Image from "next/image"; -import { useRef } from "react"; +import { useState, useEffect } from "react"; +import { getPublicClients, PublicClient } from "@/service/client/public-clients"; +import { Swiper, SwiperSlide } from "swiper/react"; +import { Navigation, Autoplay } from "swiper/modules"; +import "swiper/css"; +import "swiper/css/navigation"; + +// Custom styles for Swiper +const swiperStyles = ` + .client-swiper .swiper-button-next, + .client-swiper .swiper-button-prev { + background: white; + border-radius: 50%; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + width: 32px; + height: 32px; + margin-top: 0; + } + + .client-swiper .swiper-button-next:after, + .client-swiper .swiper-button-prev:after { + font-size: 14px; + font-weight: bold; + } + + .client-swiper .swiper-button-disabled { + opacity: 0.3; + } + + .client-swiper.swiper-centered .swiper-button-next, + .client-swiper.swiper-centered .swiper-button-prev { + display: none; + } + + .client-swiper.swiper-centered .swiper-wrapper { + justify-content: center; + } + + @media (max-width: 768px) { + .client-swiper .swiper-button-next, + .client-swiper .swiper-button-prev { + display: none; + } + } +`; // const logos = [ // { src: "/mabes.png", href: "/in/public/publication/kl" }, @@ -27,63 +71,120 @@ const logos = [ ]; export default function Footer() { - const scrollRef = useRef(null); + const [clients, setClients] = useState([]); + const [loading, setLoading] = useState(true); - const scroll = (direction: "left" | "right") => { - if (scrollRef.current) { - scrollRef.current.scrollBy({ - left: direction === "left" ? -200 : 200, - behavior: "smooth", - }); + // Fetch public clients + useEffect(() => { + async function fetchClients() { + try { + const response = await getPublicClients(); + if (response?.data?.success && response.data.data) { + setClients(response.data.data); + } + } catch (error) { + console.error("Error fetching public clients:", error); + // Fallback to static logos if API fails + setClients([]); + } finally { + setLoading(false); + } } - }; + + fetchClients(); + }, []); return (