fixing
This commit is contained in:
parent
e21ff675a9
commit
e66e77e796
|
|
@ -9,7 +9,7 @@ import Link from "next/link";
|
|||
const ReactTableAudioPage = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* <SiteBreadcrumb /> */}
|
||||
<SiteBreadcrumb />
|
||||
<div className="p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<Card className="shadow-sm border-0">
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ const TableImage = () => {
|
|||
const [filterByCreator, setFilterByCreator] = React.useState("");
|
||||
const [filterBySource, setFilterBySource] = React.useState("");
|
||||
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
|
||||
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const columns = useTableColumns();
|
||||
const table = useReactTable({
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import Link from "next/link";
|
|||
const ReactTableImagePage = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* <SiteBreadcrumb /> */}
|
||||
<SiteBreadcrumb />
|
||||
<div className="p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<Card className="shadow-sm border-0">
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ const useTableColumns = () => {
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<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">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
View
|
||||
|
|
@ -268,7 +268,7 @@ const useTableColumns = () => {
|
|||
{(Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover) && (
|
||||
<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">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
|
|
@ -26,7 +26,7 @@ const ReactTableDocumentPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
Create Document
|
||||
|
|
@ -254,7 +254,7 @@ const useTableColumns = () => {
|
|||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<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">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
|
|
@ -272,7 +272,7 @@ const useTableColumns = () => {
|
|||
{/* {(Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover) && ( */}
|
||||
<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">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import FormVideoDetail from "@/components/form/content/audio-visual/video-detail-form";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
|
||||
const VideoDetailPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
{/* <SiteBreadcrumb /> */}
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormVideoDetail />
|
||||
</div>
|
||||
|
|
@ -9,7 +9,7 @@ import Link from "next/link";
|
|||
const ReactTableAudioVisualPage = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* <SiteBreadcrumb /> */}
|
||||
<SiteBreadcrumb />
|
||||
<div className="p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<Card className="shadow-sm border-0">
|
||||
|
|
@ -26,7 +26,7 @@ const ReactTableAudioVisualPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
Create Audio-Visual
|
||||
|
|
@ -296,7 +296,7 @@ const useTableColumns = (onEdit?: (data: any) => void) => {
|
|||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setOpen(false); // ⬅️ tutup dropdown manual
|
||||
setOpen(false);
|
||||
onEdit?.(row.original);
|
||||
}}
|
||||
className="p-2 border-b text-default-700 rounded-none cursor-pointer"
|
||||
|
|
@ -307,7 +307,7 @@ const useTableColumns = (onEdit?: (data: any) => void) => {
|
|||
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setOpen(false); // ⬅️ pastikan dropdown tertutup sebelum alert
|
||||
setOpen(false);
|
||||
handleDeleteMedia(row.original.id);
|
||||
}}
|
||||
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>
|
||||
<span className="font-medium">Parent Level:</span>{" "}
|
||||
{detailData.parentLevelName || "-"}
|
||||
{detailData.parentLevelId || "-"}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-medium">Created At:</span>{" "}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import useTableColumns from "./columns";
|
|||
import TenantUpdateForm from "@/components/form/tenant/tenant-update-form";
|
||||
import { errorAutoClose, successAutoClose } from "@/lib/swal";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import DetailTenant from "@/components/form/tenant/tenant-detail-update-form";
|
||||
|
||||
function TenantSettingsContentTable() {
|
||||
const [activeTab, setActiveTab] = useState("workflows");
|
||||
|
|
@ -217,7 +218,14 @@ function TenantSettingsContentTable() {
|
|||
</div>
|
||||
|
||||
<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
|
||||
value="workflows"
|
||||
className="flex items-center gap-2 border rounded-lg"
|
||||
|
|
@ -235,6 +243,9 @@ function TenantSettingsContentTable() {
|
|||
</TabsList>
|
||||
|
||||
{/* 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">
|
||||
<div className="flex items-center justify-between">
|
||||
<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">
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<UsersIcon className="h-5 w-5" />
|
||||
User Levels Hierarchy
|
||||
<UsersIcon className="h-5 w-5" />
|
||||
User Levels Hierarchy
|
||||
</div>
|
||||
{isHierarchyExpanded ? (
|
||||
<ChevronUpIcon className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{userLevels
|
||||
.filter((ul) => !ul.parentLevelId) // Root levels
|
||||
.sort((a, b) => a.levelNumber - b.levelNumber)
|
||||
.map((rootLevel) => (
|
||||
<div key={rootLevel.id} className="space-y-2">
|
||||
{/* Root Level */}
|
||||
<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">
|
||||
{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}
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{userLevels
|
||||
.filter((ul) => !ul.parentLevelId) // Root levels
|
||||
.sort((a, b) => a.levelNumber - b.levelNumber)
|
||||
.map((rootLevel) => (
|
||||
<div key={rootLevel.id} className="space-y-2">
|
||||
{/* Root Level */}
|
||||
<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">
|
||||
{rootLevel.levelNumber}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">
|
||||
{childLevel.name}
|
||||
<div className="font-medium">
|
||||
{rootLevel.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{childLevel.aliasName} •{" "}
|
||||
{childLevel.group || "No group"}
|
||||
<div className="text-sm text-gray-500">
|
||||
{rootLevel.aliasName} •{" "}
|
||||
{rootLevel.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">
|
||||
<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>
|
||||
)}
|
||||
{childLevel.isApprovalActive && (
|
||||
<span className="px-1 py-0.5 text-xs bg-purple-100 text-purple-800 rounded-full">
|
||||
Approval
|
||||
{rootLevel.isApprovalActive && (
|
||||
<span className="px-2 py-1 text-xs bg-purple-100 text-purple-800 rounded-full">
|
||||
Approval Active
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
{/* 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 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>
|
||||
</Collapsible>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function Home() {
|
|||
return (
|
||||
<div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]">
|
||||
<div className="relative z-10 bg-white w-full mx-auto">
|
||||
<Navbar />
|
||||
{/* <Navbar /> */}
|
||||
<div className="flex-1">
|
||||
<Header />
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -152,10 +152,8 @@ export default function FormVideo() {
|
|||
|
||||
const options: Option[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
{ id: "5", label: "UMUM" },
|
||||
{ id: "6", label: "JOURNALIS" },
|
||||
{ id: "7", label: "POLRI" },
|
||||
{ id: "8", label: "KSP" },
|
||||
{ id: "4", label: "UMUM" },
|
||||
{ id: "5", label: "JOURNALIS" },
|
||||
];
|
||||
|
||||
const MAX_FILE_SIZE = 100 * 1024 * 1024;
|
||||
|
|
@ -654,7 +652,7 @@ export default function FormVideo() {
|
|||
.replace(/[^a-z0-9-]/g, ""),
|
||||
tags: finalTags,
|
||||
title: finalTitle,
|
||||
typeId: 1, // Image content type
|
||||
typeId: 2,
|
||||
};
|
||||
|
||||
// Use new Articles API
|
||||
|
|
@ -749,7 +747,7 @@ export default function FormVideo() {
|
|||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/admin/content/audio-visual");
|
||||
router.push("/admin/content/video");
|
||||
});
|
||||
|
||||
Cookies.remove("idCreate");
|
||||
|
|
@ -1000,8 +998,8 @@ export default function FormVideo() {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<div className="flex flex-col lg:flex-row gap-10 border">
|
||||
<Card className="w-full lg:w-8/12 m-2">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Form Video</p>
|
||||
<div className="gap-5 mb-5">
|
||||
|
|
@ -1453,7 +1451,7 @@ export default function FormVideo() {
|
|||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="w-full lg:w-4/12">
|
||||
<div className="w-full lg:w-4/12 m-2">
|
||||
<Card className="h-fit">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
|
|
@ -1594,7 +1592,8 @@ export default function FormVideo() {
|
|||
<Checkbox
|
||||
id={option.id}
|
||||
checked={isChecked}
|
||||
onCheckedChange={handleChange}
|
||||
onCheckedChange={handleChange}
|
||||
className="border"
|
||||
/>
|
||||
<Label htmlFor={option.id}>{option.label}</Label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export default function FormAudio() {
|
|||
polres: false,
|
||||
});
|
||||
|
||||
let fileTypeId = "3";
|
||||
let fileTypeId = "4";
|
||||
let progressInfo: any = [];
|
||||
let counterUpdateProgress = 0;
|
||||
const [progressList, setProgressList] = useState<any>([]);
|
||||
|
|
@ -149,12 +149,10 @@ export default function FormAudio() {
|
|||
type FileWithPreview = File & { preview: string };
|
||||
const userId = Cookies.get("userId");
|
||||
|
||||
const options: Option[] = [
|
||||
const options: Option[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
{ id: "5", label: "UMUM" },
|
||||
{ id: "6", label: "JOURNALIS" },
|
||||
{ id: "7", label: "POLRI" },
|
||||
{ id: "8", label: "KSP" },
|
||||
{ id: "4", label: "UMUM" },
|
||||
{ id: "5", label: "JOURNALIS" },
|
||||
];
|
||||
|
||||
const audioRefs = useRef<HTMLAudioElement[]>([]);
|
||||
|
|
@ -651,7 +649,7 @@ export default function FormAudio() {
|
|||
.replace(/[^a-z0-9-]/g, ""),
|
||||
tags: finalTags,
|
||||
title: finalTitle,
|
||||
typeId: 1, // Image content type
|
||||
typeId: 4,
|
||||
};
|
||||
|
||||
// Use new Articles API
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ export default function FormTeks() {
|
|||
polres: false,
|
||||
});
|
||||
const userId = Cookies.get("userId");
|
||||
let fileTypeId = "2";
|
||||
let fileTypeId = "3";
|
||||
let progressInfo: any = [];
|
||||
let counterUpdateProgress = 0;
|
||||
const [progressList, setProgressList] = useState<any>([]);
|
||||
|
|
|
|||
|
|
@ -602,7 +602,6 @@ export default function FormImageDetail() {
|
|||
<div className="flex items-center">
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>Category</Label>
|
||||
|
||||
<Select
|
||||
disabled
|
||||
value={String(detail?.categoryId || detail?.category?.id)}
|
||||
|
|
@ -779,24 +778,7 @@ export default function FormImageDetail() {
|
|||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -13,8 +13,18 @@ import withReactContent from "sweetalert2-react-content";
|
|||
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
|
||||
import { close } from "@/config/swal";
|
||||
import { getUserLevelDetail, updateUserLevel } from "@/service/tenant";
|
||||
import { UserLevelsCreateRequest, getUserLevels, getProvinces } from "@/service/approval-workflows";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import {
|
||||
UserLevelsCreateRequest,
|
||||
getUserLevels,
|
||||
getProvinces,
|
||||
} from "@/service/approval-workflows";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
const tenantSchema = z.object({
|
||||
|
|
@ -48,8 +58,12 @@ export default function TenantUpdateForm({
|
|||
}: TenantUpdateFormProps) {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const [loadingData, setLoadingData] = useState(false);
|
||||
const [userLevels, setUserLevels] = useState<{id: number; name: string}[]>([]);
|
||||
const [provinces, setProvinces] = useState<{id: number; name: string}[]>([]);
|
||||
const [userLevels, setUserLevels] = useState<{ id: number; name: string }[]>(
|
||||
[]
|
||||
);
|
||||
const [provinces, setProvinces] = useState<{ id: number; prov_name: string }[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const {
|
||||
control,
|
||||
|
|
@ -59,6 +73,7 @@ export default function TenantUpdateForm({
|
|||
} = useForm<TenantSchema>({
|
||||
resolver: zodResolver(tenantSchema),
|
||||
defaultValues: {
|
||||
|
||||
aliasName: "",
|
||||
levelNumber: 1,
|
||||
name: "",
|
||||
|
|
@ -74,14 +89,15 @@ export default function TenantUpdateForm({
|
|||
async function loadData() {
|
||||
setLoadingData(true);
|
||||
try {
|
||||
const [detailResponse, userLevelsResponse, provincesResponse] = await Promise.all([
|
||||
getUserLevelDetail(id),
|
||||
getUserLevels(),
|
||||
getProvinces(),
|
||||
]);
|
||||
const [detailResponse, userLevelsResponse, provincesResponse] =
|
||||
await Promise.all([
|
||||
getUserLevelDetail(id),
|
||||
getUserLevels(),
|
||||
getProvinces(),
|
||||
]);
|
||||
|
||||
if (!detailResponse.error && detailResponse.data) {
|
||||
const detail = detailResponse.data;
|
||||
const detail = detailResponse.data.data;
|
||||
setValue("aliasName", detail.aliasName ?? "");
|
||||
setValue("levelNumber", detail.levelNumber ?? 1);
|
||||
setValue("name", detail.name ?? "");
|
||||
|
|
@ -90,6 +106,7 @@ export default function TenantUpdateForm({
|
|||
setValue("group", detail.group ?? "");
|
||||
setValue("isApprovalActive", detail.isApprovalActive ?? true);
|
||||
setValue("isActive", detail.isActive ?? true);
|
||||
console.log("OOOO", detailResponse.data);
|
||||
} else {
|
||||
console.error("Gagal mengambil detail:", detailResponse.message);
|
||||
}
|
||||
|
|
@ -114,7 +131,8 @@ export default function TenantUpdateForm({
|
|||
try {
|
||||
loading();
|
||||
|
||||
const payload: UserLevelsCreateRequest = {
|
||||
const payload = {
|
||||
id: id,
|
||||
aliasName: data.aliasName,
|
||||
levelNumber: data.levelNumber,
|
||||
name: data.name,
|
||||
|
|
@ -228,7 +246,14 @@ export default function TenantUpdateForm({
|
|||
control={control}
|
||||
name="parentLevelId"
|
||||
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>
|
||||
<SelectValue placeholder="Pilih parent level" />
|
||||
</SelectTrigger>
|
||||
|
|
@ -249,7 +274,9 @@ export default function TenantUpdateForm({
|
|||
)}
|
||||
/>
|
||||
{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>
|
||||
|
||||
|
|
@ -260,15 +287,25 @@ export default function TenantUpdateForm({
|
|||
control={control}
|
||||
name="provinceId"
|
||||
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>
|
||||
<SelectValue placeholder="Pilih provinsi" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{provinces.length > 0 ? (
|
||||
provinces.map((province) => (
|
||||
<SelectItem key={province.id} value={province.id.toString()}>
|
||||
{province.name}
|
||||
<SelectItem
|
||||
key={province.id}
|
||||
value={province.id.toString()}
|
||||
>
|
||||
{province.prov_name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
|
|
@ -281,7 +318,9 @@ export default function TenantUpdateForm({
|
|||
)}
|
||||
/>
|
||||
{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>
|
||||
|
||||
|
|
@ -314,7 +353,9 @@ export default function TenantUpdateForm({
|
|||
/>
|
||||
<Label>Approval Active</Label>
|
||||
{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>
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { useAutoWorkflowCheck } from "@/hooks/useWorkflowStatusCheck";
|
|||
|
||||
const DashCodeHeader = () => {
|
||||
// Auto-check workflow status when header mounts
|
||||
useAutoWorkflowCheck();
|
||||
// useAutoWorkflowCheck();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -44,13 +44,10 @@ export const useAuth = (): AuthContextType => {
|
|||
error: null,
|
||||
});
|
||||
|
||||
// Check if user is authenticated on mount
|
||||
useEffect(() => {
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
setState((prev) => ({ ...prev, loading: true }));
|
||||
// Add logic to check if user is authenticated
|
||||
// This could check for valid tokens, etc.
|
||||
} catch (error) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
|
|
|
|||
|
|
@ -84,9 +84,9 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
|||
children: [],
|
||||
},
|
||||
{
|
||||
href: "/admin/content/teks",
|
||||
href: "/admin/content/text",
|
||||
label: t("text"),
|
||||
active: pathname.includes("/content/teks"),
|
||||
active: pathname.includes("/content/text"),
|
||||
icon: "heroicons:document",
|
||||
children: [],
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue