This commit is contained in:
Sabda Yagra 2025-11-17 13:30:22 +07:00
commit ab2c31654a
9 changed files with 323 additions and 112 deletions

View File

@ -37,32 +37,43 @@ import { Eye, EyeOff } from "lucide-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
const FormSchema = z.object({ const FormSchema = z.object({
name: z.string({ name: z.string().optional(),
required_error: "Required", username: z.string().optional(),
}), password: z.string().optional(),
username: z.string({ phoneNumber: z.string().optional(),
required_error: "Required", email: z.string().optional(),
}), skills: z.string().optional(),
password: z.string({ experiences: z.string().optional(),
required_error: "Required", company: z.string().optional(),
}),
phoneNumber: z.string({
required_error: "Required",
}),
email: z.string({
required_error: "Required",
}),
skills: z.string({
required_error: "Required",
}),
experiences: z.string({
required_error: "Required",
}),
company: z.string({
required_error: "Required",
}),
}); });
// const FormSchema = z.object({
// name: z.string({
// required_error: "Required",
// }),
// username: z.string({
// required_error: "Required",
// }),
// password: z.string({
// required_error: "Required",
// }),
// phoneNumber: z.string({
// required_error: "Required",
// }),
// email: z.string({
// required_error: "Required",
// }),
// skills: z.string({
// required_error: "Required",
// }),
// experiences: z.string({
// required_error: "Required",
// }),
// company: z.string({
// required_error: "Required",
// }),
// });
export type Placements = { export type Placements = {
index: number; index: number;
roleId?: string; roleId?: string;
@ -189,18 +200,35 @@ export default function UpdateExpertForm() {
const dataReq = { const dataReq = {
id: detail?.id, id: detail?.id,
firstName: data.name, firstName: data.name || detail.fullname,
username: data.username, username: data.username || detail.username,
email: data.email, email: data.email || detail.email,
password: data.password, password: data.password || undefined,
address: "", address: "",
roleId: "EXP-ID", roleId: "EXP-ID",
phoneNumber: data.phoneNumber, phoneNumber: data.phoneNumber || detail.phoneNumber,
userCompetencyId: data.skills, userCompetencyId:
userExperienceId: data.experiences, data.skills || detail.userProfilesAdditional?.userCompetency?.id,
companyName: data.company, userExperienceId:
data.experiences || detail.userProfilesAdditional?.userExperienceId,
companyName: data.company || detail.userProfilesAdditional?.companyName,
isAdmin: true,
}; };
// const dataReq = {
// id: detail?.id,
// firstName: data.name,
// username: data.username,
// email: data.email,
// password: data.password,
// address: "",
// roleId: "EXP-ID",
// phoneNumber: data.phoneNumber,
// userCompetencyId: data.skills,
// userExperienceId: data.experiences,
// companyName: data.company,
// };
loading(); loading();
const res = await saveUserInternal(dataReq); const res = await saveUserInternal(dataReq);
const resData = res?.data?.data; const resData = res?.data?.data;
@ -322,10 +350,15 @@ export default function UpdateExpertForm() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Nama Lengkap</FormLabel> <FormLabel>Nama Lengkap</FormLabel>
<Input {/* <Input
defaultValue={detail?.fullname} defaultValue={detail?.fullname}
placeholder="Masukkan Nama Lengkap" placeholder="Masukkan Nama Lengkap"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
defaultValue={detail?.fullname}
placeholder="Masukkan Nama Lengkap"
/> />
<FormMessage /> <FormMessage />
@ -338,13 +371,18 @@ export default function UpdateExpertForm() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>
<Input {/* <Input
type="text" type="text"
defaultValue={detail?.username} defaultValue={detail?.username}
placeholder="Masukkan" placeholder="Masukkan"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
type="text"
defaultValue={detail?.username}
placeholder="Masukkan"
/> />
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@ -355,11 +393,17 @@ export default function UpdateExpertForm() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>No. HP</FormLabel> <FormLabel>No. HP</FormLabel>
<Input {/* <Input
type="number" type="number"
defaultValue={detail?.phoneNumber} defaultValue={detail?.phoneNumber}
placeholder="Masukkan No.Hp" placeholder="Masukkan No.Hp"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
type="number"
defaultValue={detail?.phoneNumber}
placeholder="Masukkan"
/> />
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -371,17 +415,46 @@ export default function UpdateExpertForm() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel>Email</FormLabel>
<Input {/* <Input
type="email" type="email"
defaultValue={detail?.email} defaultValue={detail?.email}
placeholder="Masukkan email" placeholder="Masukkan email"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
type="email"
defaultValue={detail?.email}
placeholder="Masukkan email"
/> />
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password (Opsional)</FormLabel>
<div className="relative">
<Input
{...field}
type={showPassword ? "text" : "password"}
placeholder="Kosongkan jika tidak ingin mengubah password"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2"
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</FormItem>
)}
/>
{/* <FormField
control={form.control} control={form.control}
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
@ -406,7 +479,7 @@ export default function UpdateExpertForm() {
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> /> */}
<FormField <FormField
control={form.control} control={form.control}
name="skills" name="skills"
@ -481,12 +554,21 @@ export default function UpdateExpertForm() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Nama Institusi/Perusahaan</FormLabel> <FormLabel>Nama Institusi/Perusahaan</FormLabel>
<Input {/* <Input
type="text" type="text"
value={detail?.userProfilesAdditional?.companyName || ""} value={detail?.userProfilesAdditional?.companyName || ""}
placeholder="Nama Institusi/Perusahaan" placeholder="Nama Institusi/Perusahaan"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
type="text"
defaultValue={
detail?.userProfilesAdditional?.companyName || ""
}
placeholder="Nama Institusi/Perusahaan"
/> />
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}

View File

@ -152,12 +152,12 @@ const CampaignListTable = () => {
<div className="flex justify-between mb-10 items-center"> <div className="flex justify-between mb-10 items-center">
<p className="text-xl font-medium text-default-900">Daftar Campaign</p> <p className="text-xl font-medium text-default-900">Daftar Campaign</p>
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<Link href="/admin/broadcast/campaign-list/account-list"> {/* <Link href="/admin/broadcast/campaign-list/account-list">
<Button color="primary" size="md" className="text-sm"> <Button color="primary" size="md" className="text-sm">
<UserIcon /> <UserIcon />
Daftar Akun Daftar Akun
</Button> </Button>
</Link> </Link> */}
<Link href="/admin/broadcast/campaign-list/create"> <Link href="/admin/broadcast/campaign-list/create">
<Button color="primary" size="md" className="text-sm"> <Button color="primary" size="md" className="text-sm">
<NewCampaignIcon size={23} /> <NewCampaignIcon size={23} />

View File

@ -49,15 +49,47 @@ const columns: ColumnDef<any>[] = [
header: "Jumlah Amplifikasi", header: "Jumlah Amplifikasi",
cell: ({ row }) => <span>{row.getValue("link")}</span>, cell: ({ row }) => <span>{row.getValue("link")}</span>,
}, },
// {
// accessorKey: "status",
// header: "Status",
// cell: ({ row }) => <span>{row.getValue("status")}</span>,
// },
{ {
accessorKey: "status", accessorKey: "status",
header: "Status", header: () => <div className="text-center">Status</div>,
cell: ({ row }) => <span>{row.getValue("status")}</span>, cell: ({ row }) => {
const raw = row.getValue("status");
let value: string | number = "-";
if (typeof raw === "string" || typeof raw === "number") {
value = raw;
} else if (raw && typeof raw === "object") {
value = JSON.stringify(raw);
}
return <div className="text-center">{value}</div>;
},
}, },
{ {
accessorKey: "date", accessorKey: "createdAt",
header: "Tanggal Penarikan", header: () => <div className="text-center">Tanggal Penarikan</div>,
cell: ({ row }) => <span>{row.getValue("date")}</span>, cell: ({ row }) => {
const raw = row.getValue("createdAt");
if (!raw || typeof raw !== "string")
return <div className="text-center">-</div>;
const date = new Date(raw);
if (isNaN(date.getTime())) return <div className="text-center">-</div>;
const formatted = date.toLocaleDateString("id-ID", {
day: "2-digit",
month: "short",
year: "numeric",
});
return <div className="text-center">{formatted}</div>;
},
}, },
{ {
id: "actions", id: "actions",
@ -78,9 +110,9 @@ const columns: ColumnDef<any>[] = [
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<Link href={`/admin/media-tracking/detail/${row.original.id}`}> <Link href={`/admin/media-tracking/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground items-center rounded-none">
<Eye className="w-4 h-4 me-1.5" /> <Eye className="w-4 h-4 me-1.5" />
View View&nbsp;
{row.original.mediaUpload.fileType.secondaryName && {row.original.mediaUpload.fileType.secondaryName &&
row.original.mediaUpload.fileType.secondaryName.toLowerCase()} row.original.mediaUpload.fileType.secondaryName.toLowerCase()}
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -569,18 +569,19 @@ export default function FilterPage() {
</label> </label>
</li> </li>
))} ))}
<div className="mt-4 flex gap-2 justify-center items-center"> <div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
{/* Tombol Prev */}
<button <button
onClick={() => onClick={() =>
setCategoryPage((prev) => Math.max(prev - 1, 1)) setCategoryPage((prev) => Math.max(prev - 1, 1))
} }
disabled={categoryPage === 1} disabled={categoryPage === 1}
className="px-3 py-1 border rounded disabled:opacity-50" className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="16"
height="24" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@ -590,20 +591,41 @@ export default function FilterPage() {
</svg> </svg>
</button> </button>
{Array.from({ length: categoryTotalPages }, (_, i) => ( {(() => {
<button const maxVisible = 4;
key={i} let startPage = Math.max(
onClick={() => setCategoryPage(i + 1)} 1,
className={`px-3 py-1 border rounded ${ Math.min(
categoryPage === i + 1 categoryPage - Math.floor(maxVisible / 2),
? "bg-[#bb3523] text-white" categoryTotalPages - maxVisible + 1
: "" )
}`} );
> const endPage = Math.min(
{i + 1} categoryTotalPages,
</button> startPage + maxVisible - 1
))} );
const visiblePages = [];
for (let i = startPage; i <= endPage; i++) {
visiblePages.push(i);
}
return visiblePages.map((pageNum) => (
<button
key={pageNum}
onClick={() => setCategoryPage(pageNum)}
className={`px-3 py-1 border rounded text-sm transition-colors ${
categoryPage === pageNum
? "bg-[#bb3523] text-white"
: "bg-white dark:bg-gray-800"
}`}
>
{pageNum}
</button>
));
})()}
{/* Tombol Next */}
<button <button
onClick={() => onClick={() =>
setCategoryPage((prev) => setCategoryPage((prev) =>
@ -611,12 +633,12 @@ export default function FilterPage() {
) )
} }
disabled={categoryPage === categoryTotalPages} disabled={categoryPage === categoryTotalPages}
className="px-3 py-1 border rounded disabled:opacity-50" className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="16"
height="24" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path

View File

@ -1,6 +1,4 @@
import { import {
getCategoryData,
getPublicCategoryData,
getPublicCategoryDataNew, getPublicCategoryDataNew,
} from "@/service/landing/landing"; } from "@/service/landing/landing";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
@ -8,15 +6,7 @@ import { Reveal } from "./Reveal";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import Image from "next/image"; import { Link, useRouter } from "@/i18n/routing";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "../ui/carousel";
import { useRouter } from "@/i18n/routing";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import ImageBlurry from "../ui/image-blurry"; import ImageBlurry from "../ui/image-blurry";
@ -105,12 +95,13 @@ const ContentCategory = (props: { group?: string; type: string }) => {
{(seeAllValue ? categories : categories?.slice(0, 4))?.map( {(seeAllValue ? categories : categories?.slice(0, 4))?.map(
(category: any) => ( (category: any) => (
<div key={category?.id}> <div key={category?.id}>
<div <Link
onClick={() => href={prefixPath + `/all/filter?category=${category?.id}`}
router.push( // onClick={() =>
`${prefixPath}all/filter?category=${category?.id}` // router.push(
) // `${prefixPath}all/filter?category=${category?.id}`
} // )
// }
className="cursor-pointer relative group rounded-md overflow-hidden shadow-md hover:shadow-lg block" className="cursor-pointer relative group rounded-md overflow-hidden shadow-md hover:shadow-lg block"
> >
{/* Gambar */} {/* Gambar */}
@ -142,7 +133,7 @@ const ContentCategory = (props: { group?: string; type: string }) => {
{category?.name} {category?.name}
</h3> </h3>
</div> </div>
</div> </Link>
</div> </div>
) )
)} )}

View File

@ -99,7 +99,7 @@ const Navbar = () => {
? `/polda/${poldaName}` ? `/polda/${poldaName}`
: satkerName : satkerName
? `/satker/${satkerName}` ? `/satker/${satkerName}`
: "/"; : "";
let menu = ""; let menu = "";

View File

@ -100,6 +100,13 @@ export function getMenuList(pathname: string, t: any): Group[] {
}, },
...(!hideForRole14 ...(!hideForRole14
? [ ? [
// {
// href: "/contributor/content/satker",
// label: "satker",
// active: pathname.includes("/content/satker"),
// icon: "heroicons:credit-card",
// children: [],
// },
{ {
href: "/contributor/content/spit", href: "/contributor/content/spit",
label: "spit", label: "spit",
@ -4321,13 +4328,13 @@ export function getMenuList(pathname: string, t: any): Group[] {
icon: "heroicons:arrow-trending-up", icon: "heroicons:arrow-trending-up",
children: [], children: [],
}, },
{ // {
href: "/admin/settings/banner", // href: "/admin/settings/banner",
label: "Banner", // label: "Banner",
active: pathname === "/admin/settings/banner", // active: pathname === "/admin/settings/banner",
icon: "heroicons:arrow-trending-up", // icon: "heroicons:arrow-trending-up",
children: [], // children: [],
}, // },
// { // {
// href: "/admin/settings/feedback", // href: "/admin/settings/feedback",
// label: "Feedback", // label: "Feedback",

View File

@ -1,29 +1,73 @@
import createMiddleware from "next-intl/middleware"; import createMiddleware from "next-intl/middleware";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { locales } from "@/config";
import { routing } from "./i18n/routing"; import { routing } from "./i18n/routing";
// export default async function middleware(request: NextRequest) { const intlMiddleware = createMiddleware(routing);
// // Step 1: Use the incoming request (example)
// const defaultLocale = "in";
// // const defaultLocale = request.headers.get("dashcode-locale") || "in";
// // Step 2: Create and call the next-intl middleware (example) export default function middleware(request: NextRequest) {
// const handleI18nRouting = createMiddleware({ const { pathname } = request.nextUrl;
// locales: ["in", "en"],
// defaultLocale: "in",
// });
// const response = handleI18nRouting(request);
// // Step 3: Alter the response (example) // --- IGNORE STATIC ASSET ---
// response.headers.set("dashcode-locale", defaultLocale); const isStaticAsset =
pathname.startsWith("/api") ||
pathname.startsWith("/_next") ||
pathname.startsWith("/favicon") ||
pathname.startsWith("/assets") ||
pathname.startsWith("/static") ||
pathname.match(/\.(png|jpg|jpeg|gif|webp|svg|ico|css|js)$/);
// return response; if (isStaticAsset) return NextResponse.next();
// }
export default createMiddleware(routing); // --- LOCALES YANG VALID ---
const locales = ["in", "en"];
// Ambil locale utama dari URL
const firstSegment = pathname.split("/")[1];
const isLocaleURL = locales.includes(firstSegment);
// Jika URL sudah mengandung locale → JALANKAN next-intl
if (isLocaleURL) {
return intlMiddleware(request);
}
// Jika URL TIDAK ada locale → redirect ke /in/<path>
const url = request.nextUrl.clone();
url.pathname = `/in${pathname}`;
return NextResponse.redirect(url);
}
// Matcher untuk semua route kecuali static files
export const config = { export const config = {
// Match only internationalized pathnames matcher: ["/((?!_next|api|favicon.ico|assets|static).*)"],
matcher: ["/", "/(in|en)/:path*"],
}; };
// import createMiddleware from "next-intl/middleware";
// import { NextRequest, NextResponse } from "next/server";
// import { locales } from "@/config";
// import { routing } from "./i18n/routing";
// // export default async function middleware(request: NextRequest) {
// // // Step 1: Use the incoming request (example)
// // const defaultLocale = "in";
// // // const defaultLocale = request.headers.get("dashcode-locale") || "in";
// // // Step 2: Create and call the next-intl middleware (example)
// // const handleI18nRouting = createMiddleware({
// // locales: ["in", "en"],
// // defaultLocale: "in",
// // });
// // const response = handleI18nRouting(request);
// // // Step 3: Alter the response (example)
// // response.headers.set("dashcode-locale", defaultLocale);
// // return response;
// // }
// export default createMiddleware(routing);
// export const config = {
// // Match only internationalized pathnames
// matcher: ["/", "/(in|en)/:path*"],
// };

View File

@ -46,6 +46,39 @@ export async function listDataAll(
); );
} }
export async function listDataSatker(
isForSelf: any,
isApproval: any,
page: any,
limit: any,
search: any,
typeId: any,
statusFilter: any,
needApprovalFromLevel: any,
creator: any,
source: any,
startDate: any,
endDate: any,
title: string = ""
) {
return await httpGetInterceptor(
`media/list?enablePage=1&sortBy=createdAt&sort=desc&isAllSatker=true` +
`&size=${limit}` +
`&page=${page}` +
`&isForSelf=${isForSelf}` +
`&isApproval=${isApproval}` +
`&typeId=${typeId || ""}` +
`&statusId=${statusFilter || ""}` +
`&needApprovalFromLevel=${needApprovalFromLevel || ""}` +
`&creator=${encodeURIComponent(creator || "")}` +
`&source=${encodeURIComponent(source || "")}` +
`&startDate=${startDate || ""}` +
`&endDate=${endDate || ""}` +
`&title=${encodeURIComponent(title || search || "")}`
);
}
export async function listDataImage( export async function listDataImage(
size: any = "", size: any = "",
page: any = "", page: any = "",