This commit is contained in:
Sabda Yagra 2025-10-11 17:07:14 +07:00
parent e21ff675a9
commit e66e77e796
36 changed files with 1724 additions and 890 deletions

View File

@ -9,7 +9,7 @@ import Link from "next/link";
const ReactTableAudioPage = () => { const ReactTableAudioPage = () => {
return ( return (
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
{/* <SiteBreadcrumb /> */} <SiteBreadcrumb />
<div className="p-6"> <div className="p-6">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<Card className="shadow-sm border-0"> <Card className="shadow-sm border-0">

View File

@ -107,7 +107,6 @@ const TableImage = () => {
const [filterByCreator, setFilterByCreator] = React.useState(""); const [filterByCreator, setFilterByCreator] = React.useState("");
const [filterBySource, setFilterBySource] = React.useState(""); const [filterBySource, setFilterBySource] = React.useState("");
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState(""); const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const columns = useTableColumns(); const columns = useTableColumns();
const table = useReactTable({ const table = useReactTable({

View File

@ -9,7 +9,7 @@ import Link from "next/link";
const ReactTableImagePage = () => { const ReactTableImagePage = () => {
return ( return (
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
{/* <SiteBreadcrumb /> */} <SiteBreadcrumb />
<div className="p-6"> <div className="p-6">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<Card className="shadow-sm border-0"> <Card className="shadow-sm border-0">

View File

@ -259,7 +259,7 @@ const useTableColumns = () => {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<Link href={`/admin/content/document/detail/${row.original.id}`}> <Link href={`/admin/content/text/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<Eye className="w-4 h-4 me-1.5" /> <Eye className="w-4 h-4 me-1.5" />
View View
@ -268,7 +268,7 @@ const useTableColumns = () => {
{(Number(row.original.uploadedById) === Number(userId) || {(Number(row.original.uploadedById) === Number(userId) ||
isMabesApprover) && ( isMabesApprover) && (
<Link <Link
href={`/admin/content/document/update/${row.original.id}`} href={`/admin/content/text/update/${row.original.id}`}
> >
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<SquarePen className="w-4 h-4 me-1.5" /> <SquarePen className="w-4 h-4 me-1.5" />

View File

@ -26,7 +26,7 @@ const ReactTableDocumentPage = () => {
</div> </div>
</div> </div>
<div className="flex-none"> <div className="flex-none">
<Link href={"/admin/content/document/create"}> <Link href={"/admin/content/text/create"}>
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow"> <Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow">
<UploadIcon size={18} className="mr-2" /> <UploadIcon size={18} className="mr-2" />
Create Document Create Document

View File

@ -254,7 +254,7 @@ const useTableColumns = () => {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<Link <Link
href={`/admin/content/audio-visual/detail/${row.original.id}`} href={`/admin/content/video/detail/${row.original.id}`}
> >
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<Eye className="w-4 h-4 me-1.5" /> <Eye className="w-4 h-4 me-1.5" />
@ -272,7 +272,7 @@ const useTableColumns = () => {
{/* {(Number(row.original.uploadedById) === Number(userId) || {/* {(Number(row.original.uploadedById) === Number(userId) ||
isMabesApprover) && ( */} isMabesApprover) && ( */}
<Link <Link
href={`/admin/content/audio-visual/update/${row.original.id}`} href={`/admin/content/video/update/${row.original.id}`}
> >
<DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group rounded-none">
<SquarePen className="w-4 h-4 me-1.5" /> <SquarePen className="w-4 h-4 me-1.5" />

View File

@ -1,9 +1,10 @@
import FormVideoDetail from "@/components/form/content/audio-visual/video-detail-form"; import FormVideoDetail from "@/components/form/content/audio-visual/video-detail-form";
import SiteBreadcrumb from "@/components/site-breadcrumb";
const VideoDetailPage = async () => { const VideoDetailPage = async () => {
return ( return (
<div> <div>
{/* <SiteBreadcrumb /> */} <SiteBreadcrumb />
<div className="space-y-4"> <div className="space-y-4">
<FormVideoDetail /> <FormVideoDetail />
</div> </div>

View File

@ -9,7 +9,7 @@ import Link from "next/link";
const ReactTableAudioVisualPage = () => { const ReactTableAudioVisualPage = () => {
return ( return (
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
{/* <SiteBreadcrumb /> */} <SiteBreadcrumb />
<div className="p-6"> <div className="p-6">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<Card className="shadow-sm border-0"> <Card className="shadow-sm border-0">
@ -26,7 +26,7 @@ const ReactTableAudioVisualPage = () => {
</div> </div>
</div> </div>
<div className="flex-none"> <div className="flex-none">
<Link href={"/admin/content/audio-visual/create"}> <Link href={"/admin/content/video/create"}>
<Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow"> <Button color="primary" className="text-white shadow-sm hover:shadow-md transition-shadow">
<UploadIcon size={18} className="mr-2" /> <UploadIcon size={18} className="mr-2" />
Create Audio-Visual Create Audio-Visual

View File

@ -296,7 +296,7 @@ const useTableColumns = (onEdit?: (data: any) => void) => {
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
setOpen(false); // ⬅️ tutup dropdown manual setOpen(false);
onEdit?.(row.original); onEdit?.(row.original);
}} }}
className="p-2 border-b text-default-700 rounded-none cursor-pointer" className="p-2 border-b text-default-700 rounded-none cursor-pointer"
@ -307,7 +307,7 @@ const useTableColumns = (onEdit?: (data: any) => void) => {
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
setOpen(false); // ⬅️ pastikan dropdown tertutup sebelum alert setOpen(false);
handleDeleteMedia(row.original.id); handleDeleteMedia(row.original.id);
}} }}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none" className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-white rounded-none"
@ -338,7 +338,7 @@ const useTableColumns = (onEdit?: (data: any) => void) => {
</p> </p>
<p> <p>
<span className="font-medium">Parent Level:</span>{" "} <span className="font-medium">Parent Level:</span>{" "}
{detailData.parentLevelName || "-"} {detailData.parentLevelId || "-"}
</p> </p>
<p> <p>
<span className="font-medium">Created At:</span>{" "} <span className="font-medium">Created At:</span>{" "}

View File

@ -57,6 +57,7 @@ import useTableColumns from "./columns";
import TenantUpdateForm from "@/components/form/tenant/tenant-update-form"; import TenantUpdateForm from "@/components/form/tenant/tenant-update-form";
import { errorAutoClose, successAutoClose } from "@/lib/swal"; import { errorAutoClose, successAutoClose } from "@/lib/swal";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import DetailTenant from "@/components/form/tenant/tenant-detail-update-form";
function TenantSettingsContentTable() { function TenantSettingsContentTable() {
const [activeTab, setActiveTab] = useState("workflows"); const [activeTab, setActiveTab] = useState("workflows");
@ -217,7 +218,14 @@ function TenantSettingsContentTable() {
</div> </div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full"> <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-2"> <TabsList className="grid w-full grid-cols-3">
<TabsTrigger
value="tenant"
className="flex items-center gap-2 border rounded-lg"
>
<WorkflowIcon className="h-4 w-4" />
Detail Tenant
</TabsTrigger>
<TabsTrigger <TabsTrigger
value="workflows" value="workflows"
className="flex items-center gap-2 border rounded-lg" className="flex items-center gap-2 border rounded-lg"
@ -235,6 +243,9 @@ function TenantSettingsContentTable() {
</TabsList> </TabsList>
{/* Approval Workflows Tab */} {/* Approval Workflows Tab */}
<TabsContent value="tenant" className="space-y-6 border rounded-lg">
<DetailTenant id={10} />
</TabsContent>
<TabsContent value="workflows" className="space-y-6 border rounded-lg"> <TabsContent value="workflows" className="space-y-6 border rounded-lg">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className="text-2xl font-semibold ml-2 mt-4"> <h2 className="text-2xl font-semibold ml-2 mt-4">
@ -718,90 +729,92 @@ function TenantSettingsContentTable() {
<CardHeader className="cursor-pointer hover:bg-gray-50 transition-colors"> <CardHeader className="cursor-pointer hover:bg-gray-50 transition-colors">
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<UsersIcon className="h-5 w-5" /> <UsersIcon className="h-5 w-5" />
User Levels Hierarchy User Levels Hierarchy
</div> </div>
{isHierarchyExpanded ? ( {isHierarchyExpanded ? (
<ChevronUpIcon className="h-4 w-4" /> <ChevronUpIcon className="h-4 w-4" />
) : ( ) : (
<ChevronDownIcon className="h-4 w-4" /> <ChevronDownIcon className="h-4 w-4" />
)} )}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent> <CollapsibleContent>
<CardContent> <CardContent>
<div className="space-y-3"> <div className="space-y-3">
{userLevels {userLevels
.filter((ul) => !ul.parentLevelId) // Root levels .filter((ul) => !ul.parentLevelId) // Root levels
.sort((a, b) => a.levelNumber - b.levelNumber) .sort((a, b) => a.levelNumber - b.levelNumber)
.map((rootLevel) => ( .map((rootLevel) => (
<div key={rootLevel.id} className="space-y-2"> <div key={rootLevel.id} className="space-y-2">
{/* Root Level */} {/* Root Level */}
<div className="flex items-center gap-3 p-3 bg-blue-50 rounded-lg border-l-4 border-blue-500"> <div className="flex items-center gap-3 p-3 bg-blue-50 rounded-lg border-l-4 border-blue-500">
<div className="w-8 h-8 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-sm font-medium"> <div className="w-8 h-8 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-sm font-medium">
{rootLevel.levelNumber} {rootLevel.levelNumber}
</div>
<div className="flex-1">
<div className="font-medium">{rootLevel.name}</div>
<div className="text-sm text-gray-500">
{rootLevel.aliasName} {" "}
{rootLevel.group || "No group"}
</div>
</div>
<div className="flex items-center gap-2">
{rootLevel.isActive && (
<span className="px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">
Active
</span>
)}
{rootLevel.isApprovalActive && (
<span className="px-2 py-1 text-xs bg-purple-100 text-purple-800 rounded-full">
Approval Active
</span>
)}
</div>
</div>
{/* Child Levels */}
{userLevels
.filter((ul) => ul.parentLevelId === rootLevel.id)
.sort((a, b) => a.levelNumber - b.levelNumber)
.map((childLevel) => (
<div
key={childLevel.id}
className="ml-8 flex items-center gap-3 p-3 bg-gray-50 rounded-lg border-l-4 border-gray-300"
>
<div className="w-6 h-6 bg-gray-100 text-gray-600 rounded-full flex items-center justify-center text-xs font-medium">
{childLevel.levelNumber}
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="font-medium text-sm"> <div className="font-medium">
{childLevel.name} {rootLevel.name}
</div> </div>
<div className="text-xs text-gray-500"> <div className="text-sm text-gray-500">
{childLevel.aliasName} {" "} {rootLevel.aliasName} {" "}
{childLevel.group || "No group"} {rootLevel.group || "No group"}
</div> </div>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-2">
{childLevel.isActive && ( {rootLevel.isActive && (
<span className="px-1 py-0.5 text-xs bg-green-100 text-green-800 rounded-full"> <span className="px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">
Active Active
</span> </span>
)} )}
{childLevel.isApprovalActive && ( {rootLevel.isApprovalActive && (
<span className="px-1 py-0.5 text-xs bg-purple-100 text-purple-800 rounded-full"> <span className="px-2 py-1 text-xs bg-purple-100 text-purple-800 rounded-full">
Approval Approval Active
</span> </span>
)} )}
</div> </div>
</div> </div>
))}
</div> {/* Child Levels */}
))} {userLevels
</div> .filter((ul) => ul.parentLevelId === rootLevel.id)
</CardContent> .sort((a, b) => a.levelNumber - b.levelNumber)
.map((childLevel) => (
<div
key={childLevel.id}
className="ml-8 flex items-center gap-3 p-3 bg-gray-50 rounded-lg border-l-4 border-gray-300"
>
<div className="w-6 h-6 bg-gray-100 text-gray-600 rounded-full flex items-center justify-center text-xs font-medium">
{childLevel.levelNumber}
</div>
<div className="flex-1">
<div className="font-medium text-sm">
{childLevel.name}
</div>
<div className="text-xs text-gray-500">
{childLevel.aliasName} {" "}
{childLevel.group || "No group"}
</div>
</div>
<div className="flex items-center gap-1">
{childLevel.isActive && (
<span className="px-1 py-0.5 text-xs bg-green-100 text-green-800 rounded-full">
Active
</span>
)}
{childLevel.isApprovalActive && (
<span className="px-1 py-0.5 text-xs bg-purple-100 text-purple-800 rounded-full">
Approval
</span>
)}
</div>
</div>
))}
</div>
))}
</div>
</CardContent>
</CollapsibleContent> </CollapsibleContent>
</Collapsible> </Collapsible>
</Card> </Card>

View File

@ -8,7 +8,7 @@ export default function Home() {
return ( return (
<div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]"> <div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]">
<div className="relative z-10 bg-white w-full mx-auto"> <div className="relative z-10 bg-white w-full mx-auto">
<Navbar /> {/* <Navbar /> */}
<div className="flex-1"> <div className="flex-1">
<Header /> <Header />
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -152,10 +152,8 @@ export default function FormVideo() {
const options: Option[] = [ const options: Option[] = [
{ id: "all", label: "SEMUA" }, { id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" }, { id: "4", label: "UMUM" },
{ id: "6", label: "JOURNALIS" }, { id: "5", label: "JOURNALIS" },
{ id: "7", label: "POLRI" },
{ id: "8", label: "KSP" },
]; ];
const MAX_FILE_SIZE = 100 * 1024 * 1024; const MAX_FILE_SIZE = 100 * 1024 * 1024;
@ -654,7 +652,7 @@ export default function FormVideo() {
.replace(/[^a-z0-9-]/g, ""), .replace(/[^a-z0-9-]/g, ""),
tags: finalTags, tags: finalTags,
title: finalTitle, title: finalTitle,
typeId: 1, // Image content type typeId: 2,
}; };
// Use new Articles API // Use new Articles API
@ -749,7 +747,7 @@ export default function FormVideo() {
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then(() => {
router.push("/admin/content/audio-visual"); router.push("/admin/content/video");
}); });
Cookies.remove("idCreate"); Cookies.remove("idCreate");
@ -1000,8 +998,8 @@ export default function FormVideo() {
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10 border">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12 m-2">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Video</p> <p className="text-lg font-semibold mb-3">Form Video</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
@ -1453,7 +1451,7 @@ export default function FormVideo() {
</div> </div>
</Card> </Card>
<div className="w-full lg:w-4/12"> <div className="w-full lg:w-4/12 m-2">
<Card className="h-fit"> <Card className="h-fit">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
@ -1594,7 +1592,8 @@ export default function FormVideo() {
<Checkbox <Checkbox
id={option.id} id={option.id}
checked={isChecked} checked={isChecked}
onCheckedChange={handleChange} onCheckedChange={handleChange}
className="border"
/> />
<Label htmlFor={option.id}>{option.label}</Label> <Label htmlFor={option.id}>{option.label}</Label>
</div> </div>

View File

@ -134,7 +134,7 @@ export default function FormAudio() {
polres: false, polres: false,
}); });
let fileTypeId = "3"; let fileTypeId = "4";
let progressInfo: any = []; let progressInfo: any = [];
let counterUpdateProgress = 0; let counterUpdateProgress = 0;
const [progressList, setProgressList] = useState<any>([]); const [progressList, setProgressList] = useState<any>([]);
@ -149,12 +149,10 @@ export default function FormAudio() {
type FileWithPreview = File & { preview: string }; type FileWithPreview = File & { preview: string };
const userId = Cookies.get("userId"); const userId = Cookies.get("userId");
const options: Option[] = [ const options: Option[] = [
{ id: "all", label: "SEMUA" }, { id: "all", label: "SEMUA" },
{ id: "5", label: "UMUM" }, { id: "4", label: "UMUM" },
{ id: "6", label: "JOURNALIS" }, { id: "5", label: "JOURNALIS" },
{ id: "7", label: "POLRI" },
{ id: "8", label: "KSP" },
]; ];
const audioRefs = useRef<HTMLAudioElement[]>([]); const audioRefs = useRef<HTMLAudioElement[]>([]);
@ -651,7 +649,7 @@ export default function FormAudio() {
.replace(/[^a-z0-9-]/g, ""), .replace(/[^a-z0-9-]/g, ""),
tags: finalTags, tags: finalTags,
title: finalTitle, title: finalTitle,
typeId: 1, // Image content type typeId: 4,
}; };
// Use new Articles API // Use new Articles API

View File

@ -132,7 +132,7 @@ export default function FormTeks() {
polres: false, polres: false,
}); });
const userId = Cookies.get("userId"); const userId = Cookies.get("userId");
let fileTypeId = "2"; let fileTypeId = "3";
let progressInfo: any = []; let progressInfo: any = [];
let counterUpdateProgress = 0; let counterUpdateProgress = 0;
const [progressList, setProgressList] = useState<any>([]); const [progressList, setProgressList] = useState<any>([]);

View File

@ -602,7 +602,6 @@ export default function FormImageDetail() {
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full space-y-2"> <div className="py-3 w-full space-y-2">
<Label>Category</Label> <Label>Category</Label>
<Select <Select
disabled disabled
value={String(detail?.categoryId || detail?.category?.id)} value={String(detail?.categoryId || detail?.category?.id)}
@ -779,24 +778,7 @@ export default function FormImageDetail() {
/> />
<Label htmlFor="6">JOURNALIS</Label> <Label htmlFor="6">JOURNALIS</Label>
</div> </div>
<div className="flex gap-2 items-center">
<Checkbox
id="7"
checked={selectedPublishers.includes(7)}
onChange={() => handleCheckboxChange(7)}
className="border"
/>
<Label htmlFor="7">POLRI</Label>
</div>
<div className="flex gap-2 items-center">
<Checkbox
id="8"
checked={selectedPublishers.includes(8)}
onChange={() => handleCheckboxChange(8)}
className="border"
/>
<Label htmlFor="8">KSP</Label>
</div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,305 @@
"use client";
import React, { useState, useEffect } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import {
Card,
CardHeader,
CardTitle,
CardContent,
CardFooter,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Checkbox } from "@/components/ui/checkbox";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
import { close } from "@/config/swal";
import Image from "next/image";
// ✅ Zod Schema Validasi
const companySchema = z.object({
companyName: z.string().trim().min(1, "Nama perusahaan wajib diisi"),
address: z.string().trim().min(1, "Alamat perusahaan wajib diisi"),
phone: z
.string()
.regex(/^[0-9+\-\s]+$/, "Nomor telepon tidak valid")
.min(6, "Nomor telepon minimal 6 karakter"),
website: z
.string()
.url("Masukkan URL website yang valid")
.optional()
.or(z.literal("")),
description: z.string().trim().optional(),
isActive: z.boolean().default(true),
logo: z.any().optional(),
});
type CompanySchema = z.infer<typeof companySchema>;
interface TenantCompanyUpdateFormProps {
id: number;
initialData?: any;
onSuccess?: () => void;
onCancel?: () => void;
}
export default function TenantCompanyUpdateForm({
id,
initialData,
onSuccess,
onCancel,
}: TenantCompanyUpdateFormProps) {
const MySwal = withReactContent(Swal);
const [previewLogo, setPreviewLogo] = useState<string | null>(null);
const [loadingData, setLoadingData] = useState(false);
const {
control,
handleSubmit,
setValue,
formState: { errors },
watch,
} = useForm<CompanySchema>({
resolver: zodResolver(companySchema),
defaultValues: {
companyName: "",
address: "",
phone: "",
website: "",
description: "",
isActive: true,
},
});
// ✅ Load data awal dari server
useEffect(() => {
async function fetchCompanyData() {
setLoadingData(true);
try {
// TODO: ganti dengan service API kamu (misalnya getTenantCompanyDetail)
const response = initialData; // simulasi
if (response) {
setValue("companyName", response.companyName || "");
setValue("address", response.address || "");
setValue("phone", response.phone || "");
setValue("website", response.website || "");
setValue("description", response.description || "");
setValue("isActive", response.isActive ?? true);
setPreviewLogo(response.logoUrl || null);
}
} catch (err) {
console.error("❌ Gagal memuat data perusahaan:", err);
} finally {
setLoadingData(false);
}
}
fetchCompanyData();
}, [initialData, setValue]);
// ✅ Fungsi Upload Logo
const handleLogoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
if (!file.type.startsWith("image/")) {
MySwal.fire({
icon: "error",
title: "Format tidak valid",
text: "File harus berupa gambar (jpg, png, webp, dll)",
});
return;
}
setValue("logo", file);
setPreviewLogo(URL.createObjectURL(file));
};
// ✅ Submit Form
const onSubmit = async (data: CompanySchema) => {
try {
loading();
const formData = new FormData();
formData.append("companyName", data.companyName);
formData.append("address", data.address);
formData.append("phone", data.phone);
formData.append("website", data.website || "");
formData.append("description", data.description || "");
formData.append("isActive", data.isActive ? "true" : "false");
if (data.logo) formData.append("logo", data.logo);
console.log("📦 Payload:", Object.fromEntries(formData.entries()));
// TODO: Ganti dengan service API kamu → misalnya updateTenantCompany(formData)
// const response = await updateTenantCompany(id, formData);
await new Promise((resolve) => setTimeout(resolve, 1000)); // simulate
close();
successAutoClose("Data perusahaan berhasil diperbarui.");
setTimeout(() => {
if (onSuccess) onSuccess();
}, 2000);
} catch (err) {
close();
console.error("❌ Gagal update perusahaan:", err);
errorAutoClose("Terjadi kesalahan saat memperbarui data perusahaan.");
}
};
if (loadingData) {
return (
<Card className="p-6 w-full lg:w-2/3">
<div className="flex items-center justify-center py-8">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-4"></div>
<p>Memuat data perusahaan...</p>
</div>
</div>
</Card>
);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Card className="p-6 w-full lg:w-2/3">
<CardHeader>
<CardTitle className="text-lg font-semibold">
Update Informasi Perusahaan
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Logo Upload */}
<div className="space-y-2">
<Label>Logo Perusahaan</Label>
<div className="flex items-center gap-4">
<Input
type="file"
accept="image/*"
onChange={handleLogoChange}
className="max-w-xs"
/>
{previewLogo && (
<div className="relative w-20 h-20 rounded-lg overflow-hidden border">
<Image
src={previewLogo}
alt="Company Logo"
fill
className="object-cover"
/>
</div>
)}
</div>
</div>
{/* Nama Perusahaan */}
<div>
<Label>Nama Perusahaan</Label>
<Controller
control={control}
name="companyName"
render={({ field }) => (
<Input {...field} placeholder="Masukkan nama perusahaan" />
)}
/>
{errors.companyName && (
<p className="text-red-500 text-sm">
{errors.companyName.message}
</p>
)}
</div>
{/* Alamat */}
<div>
<Label>Alamat</Label>
<Controller
control={control}
name="address"
render={({ field }) => (
<Textarea {...field} placeholder="Masukkan alamat lengkap" />
)}
/>
{errors.address && (
<p className="text-red-500 text-sm">{errors.address.message}</p>
)}
</div>
{/* Nomor Telepon */}
<div>
<Label>Nomor Telepon</Label>
<Controller
control={control}
name="phone"
render={({ field }) => (
<Input {...field} placeholder="Masukkan nomor telepon" />
)}
/>
{errors.phone && (
<p className="text-red-500 text-sm">{errors.phone.message}</p>
)}
</div>
{/* Website */}
<div>
<Label>Website Resmi</Label>
<Controller
control={control}
name="website"
render={({ field }) => (
<Input {...field} placeholder="https://example.com" />
)}
/>
{errors.website && (
<p className="text-red-500 text-sm">{errors.website.message}</p>
)}
</div>
{/* Deskripsi Perusahaan */}
<div>
<Label>Biodata / Deskripsi Perusahaan</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
{...field}
placeholder="Ceritakan tentang perusahaan Anda..."
/>
)}
/>
</div>
{/* Status Aktif */}
<div className="flex items-center space-x-2">
<Controller
control={control}
name="isActive"
render={({ field }) => (
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
)}
/>
<Label>Aktif</Label>
</div>
</CardContent>
<CardFooter className="flex justify-end gap-3">
<Button type="submit">Update</Button>
<Button type="button" variant="outline" onClick={() => onCancel?.()}>
Cancel
</Button>
</CardFooter>
</Card>
</form>
);
}

View File

@ -13,8 +13,18 @@ import withReactContent from "sweetalert2-react-content";
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal"; import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
import { close } from "@/config/swal"; import { close } from "@/config/swal";
import { getUserLevelDetail, updateUserLevel } from "@/service/tenant"; import { getUserLevelDetail, updateUserLevel } from "@/service/tenant";
import { UserLevelsCreateRequest, getUserLevels, getProvinces } from "@/service/approval-workflows"; import {
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; UserLevelsCreateRequest,
getUserLevels,
getProvinces,
} from "@/service/approval-workflows";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
const tenantSchema = z.object({ const tenantSchema = z.object({
@ -48,8 +58,12 @@ export default function TenantUpdateForm({
}: TenantUpdateFormProps) { }: TenantUpdateFormProps) {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const [loadingData, setLoadingData] = useState(false); const [loadingData, setLoadingData] = useState(false);
const [userLevels, setUserLevels] = useState<{id: number; name: string}[]>([]); const [userLevels, setUserLevels] = useState<{ id: number; name: string }[]>(
const [provinces, setProvinces] = useState<{id: number; name: string}[]>([]); []
);
const [provinces, setProvinces] = useState<{ id: number; prov_name: string }[]>(
[]
);
const { const {
control, control,
@ -59,6 +73,7 @@ export default function TenantUpdateForm({
} = useForm<TenantSchema>({ } = useForm<TenantSchema>({
resolver: zodResolver(tenantSchema), resolver: zodResolver(tenantSchema),
defaultValues: { defaultValues: {
aliasName: "", aliasName: "",
levelNumber: 1, levelNumber: 1,
name: "", name: "",
@ -74,14 +89,15 @@ export default function TenantUpdateForm({
async function loadData() { async function loadData() {
setLoadingData(true); setLoadingData(true);
try { try {
const [detailResponse, userLevelsResponse, provincesResponse] = await Promise.all([ const [detailResponse, userLevelsResponse, provincesResponse] =
getUserLevelDetail(id), await Promise.all([
getUserLevels(), getUserLevelDetail(id),
getProvinces(), getUserLevels(),
]); getProvinces(),
]);
if (!detailResponse.error && detailResponse.data) { if (!detailResponse.error && detailResponse.data) {
const detail = detailResponse.data; const detail = detailResponse.data.data;
setValue("aliasName", detail.aliasName ?? ""); setValue("aliasName", detail.aliasName ?? "");
setValue("levelNumber", detail.levelNumber ?? 1); setValue("levelNumber", detail.levelNumber ?? 1);
setValue("name", detail.name ?? ""); setValue("name", detail.name ?? "");
@ -90,6 +106,7 @@ export default function TenantUpdateForm({
setValue("group", detail.group ?? ""); setValue("group", detail.group ?? "");
setValue("isApprovalActive", detail.isApprovalActive ?? true); setValue("isApprovalActive", detail.isApprovalActive ?? true);
setValue("isActive", detail.isActive ?? true); setValue("isActive", detail.isActive ?? true);
console.log("OOOO", detailResponse.data);
} else { } else {
console.error("Gagal mengambil detail:", detailResponse.message); console.error("Gagal mengambil detail:", detailResponse.message);
} }
@ -114,7 +131,8 @@ export default function TenantUpdateForm({
try { try {
loading(); loading();
const payload: UserLevelsCreateRequest = { const payload = {
id: id,
aliasName: data.aliasName, aliasName: data.aliasName,
levelNumber: data.levelNumber, levelNumber: data.levelNumber,
name: data.name, name: data.name,
@ -228,7 +246,14 @@ export default function TenantUpdateForm({
control={control} control={control}
name="parentLevelId" name="parentLevelId"
render={({ field }) => ( render={({ field }) => (
<Select onValueChange={(value) => field.onChange(value && value !== "no-data" ? Number(value) : undefined)} value={field.value?.toString() || ""}> <Select
onValueChange={(value) =>
field.onChange(
value && value !== "no-data" ? Number(value) : undefined
)
}
value={field.value?.toString() || ""}
>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Pilih parent level" /> <SelectValue placeholder="Pilih parent level" />
</SelectTrigger> </SelectTrigger>
@ -249,7 +274,9 @@ export default function TenantUpdateForm({
)} )}
/> />
{errors.parentLevelId && ( {errors.parentLevelId && (
<p className="text-red-500 text-sm">{errors.parentLevelId.message}</p> <p className="text-red-500 text-sm">
{errors.parentLevelId.message}
</p>
)} )}
</div> </div>
@ -260,15 +287,25 @@ export default function TenantUpdateForm({
control={control} control={control}
name="provinceId" name="provinceId"
render={({ field }) => ( render={({ field }) => (
<Select onValueChange={(value) => field.onChange(value && value !== "no-data" ? Number(value) : undefined)} value={field.value?.toString() || ""}> <Select
onValueChange={(value) =>
field.onChange(
value && value !== "no-data" ? Number(value) : undefined
)
}
value={field.value?.toString() || ""}
>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Pilih provinsi" /> <SelectValue placeholder="Pilih provinsi" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{provinces.length > 0 ? ( {provinces.length > 0 ? (
provinces.map((province) => ( provinces.map((province) => (
<SelectItem key={province.id} value={province.id.toString()}> <SelectItem
{province.name} key={province.id}
value={province.id.toString()}
>
{province.prov_name}
</SelectItem> </SelectItem>
)) ))
) : ( ) : (
@ -281,7 +318,9 @@ export default function TenantUpdateForm({
)} )}
/> />
{errors.provinceId && ( {errors.provinceId && (
<p className="text-red-500 text-sm">{errors.provinceId.message}</p> <p className="text-red-500 text-sm">
{errors.provinceId.message}
</p>
)} )}
</div> </div>
@ -314,7 +353,9 @@ export default function TenantUpdateForm({
/> />
<Label>Approval Active</Label> <Label>Approval Active</Label>
{errors.isApprovalActive && ( {errors.isApprovalActive && (
<p className="text-red-500 text-sm">{errors.isApprovalActive.message}</p> <p className="text-red-500 text-sm">
{errors.isApprovalActive.message}
</p>
)} )}
</div> </div>

View File

@ -17,7 +17,7 @@ import { useAutoWorkflowCheck } from "@/hooks/useWorkflowStatusCheck";
const DashCodeHeader = () => { const DashCodeHeader = () => {
// Auto-check workflow status when header mounts // Auto-check workflow status when header mounts
useAutoWorkflowCheck(); // useAutoWorkflowCheck();
return ( return (
<> <>

View File

@ -44,13 +44,10 @@ export const useAuth = (): AuthContextType => {
error: null, error: null,
}); });
// Check if user is authenticated on mount
useEffect(() => { useEffect(() => {
const checkAuth = async () => { const checkAuth = async () => {
try { try {
setState((prev) => ({ ...prev, loading: true })); setState((prev) => ({ ...prev, loading: true }));
// Add logic to check if user is authenticated
// This could check for valid tokens, etc.
} catch (error) { } catch (error) {
setState((prev) => ({ setState((prev) => ({
...prev, ...prev,

View File

@ -84,9 +84,9 @@ export function getMenuList(pathname: string, t: any): Group[] {
children: [], children: [],
}, },
{ {
href: "/admin/content/teks", href: "/admin/content/text",
label: t("text"), label: t("text"),
active: pathname.includes("/content/teks"), active: pathname.includes("/content/text"),
icon: "heroicons:document", icon: "heroicons:document",
children: [], children: [],
}, },