feat:fix SPIT, penugasan,agenda setting
This commit is contained in:
parent
ade8b1b296
commit
079d238d1f
|
|
@ -21,7 +21,13 @@ import {
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Loader2, CalendarIcon, ChevronUp, ChevronDown } from "lucide-react";
|
import {
|
||||||
|
Loader2,
|
||||||
|
CalendarIcon,
|
||||||
|
ChevronUp,
|
||||||
|
ChevronDown,
|
||||||
|
Music,
|
||||||
|
} from "lucide-react";
|
||||||
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
||||||
import { CalendarCategory } from "./data";
|
import { CalendarCategory } from "./data";
|
||||||
import {
|
import {
|
||||||
|
|
@ -338,11 +344,17 @@ const EventModal = ({
|
||||||
await uploadResumableFile(index, String(id), item, "3", "0");
|
await uploadResumableFile(index, String(id), item, "3", "0");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (audioFiles?.length === 0) {
|
if (audioFiles?.length == 0) {
|
||||||
setIsAudioUploadFinish(true);
|
setIsAudioUploadFinish(true);
|
||||||
}
|
}
|
||||||
audioFiles?.map(async (item: any, index: number) => {
|
audioFiles.map(async (item: FileWithPreview, index: number) => {
|
||||||
await uploadResumableFile(index, String(id), item, "4", "0");
|
await uploadResumableFile(
|
||||||
|
index,
|
||||||
|
String(id),
|
||||||
|
item, // Use .file to access the actual File object
|
||||||
|
"4",
|
||||||
|
"0" // Optional: Replace with actual duration if available
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -429,9 +441,15 @@ const EventModal = ({
|
||||||
audio.controls = true;
|
audio.controls = true;
|
||||||
document.body.appendChild(audio);
|
document.body.appendChild(audio);
|
||||||
|
|
||||||
// Convert Blob to File
|
// Convert Blob to File and add preview
|
||||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
const fileWithPreview: FileWithPreview = Object.assign(
|
||||||
setAudioFile(file);
|
new File([blob], "voiceNote.webm", { type: "audio/webm" }),
|
||||||
|
{ preview: url }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add to state
|
||||||
|
setAudioFile(fileWithPreview);
|
||||||
|
setAudioFiles((prev) => [...prev, fileWithPreview]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteAudio = () => {
|
const handleDeleteAudio = () => {
|
||||||
|
|
@ -993,8 +1011,7 @@ const EventModal = ({
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Voice Note</Label>
|
<Label>Audio</Label>
|
||||||
|
|
||||||
<AudioRecorder
|
<AudioRecorder
|
||||||
onRecordingComplete={addAudioElement}
|
onRecordingComplete={addAudioElement}
|
||||||
audioTrackConstraints={{
|
audioTrackConstraints={{
|
||||||
|
|
@ -1011,7 +1028,9 @@ const EventModal = ({
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .mp3 atau .wav."
|
label="Upload file dengan format .mp3 atau .wav."
|
||||||
onDrop={(files) => setAudioFiles(files)}
|
onDrop={(files) =>
|
||||||
|
setAudioFiles((prev) => [...prev, ...files])
|
||||||
|
}
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/>
|
||||||
{audioUploadedFiles?.map((file: any, index: number) => (
|
{audioUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
|
@ -1054,7 +1073,7 @@ const EventModal = ({
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
{renderFilePreview(file.url)}
|
<Music />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className=" text-sm text-card-foreground">
|
<div className=" text-sm text-card-foreground">
|
||||||
|
|
@ -1078,7 +1097,10 @@ const EventModal = ({
|
||||||
</div>
|
</div>
|
||||||
{audioFile && (
|
{audioFile && (
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
<div className="flex items-center mr-1">
|
||||||
|
{" "}
|
||||||
|
<Music /> <p>Voice Note</p>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDeleteAudio}
|
onClick={handleDeleteAudio}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselNext,
|
||||||
|
CarouselPrevious,
|
||||||
|
} from "@/components/ui/carousel";
|
||||||
|
import { Link } from "@/components/navigation";
|
||||||
|
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { locale } from "dayjs";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getListContent } from "@/service/landing/landing";
|
||||||
|
import ContestTable from "../../../../contest/components/contest-table";
|
||||||
|
import AudioSliderPage from "../../audio/audio";
|
||||||
|
import TeksSliderPage from "../../document/teks";
|
||||||
|
import ImageSliderPage from "../../image/image";
|
||||||
|
import VideoSliderPage from "../../video/audio-visual";
|
||||||
|
|
||||||
|
const AudioAllPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="my-3">
|
||||||
|
<Tabs defaultValue="giat-routine" className="w-full">
|
||||||
|
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||||
|
<p className="text-lg font-semibold ml-2">Konten Audio</p>
|
||||||
|
</Card>
|
||||||
|
<TabsContent value="giat-routine">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Audio</Label>
|
||||||
|
</div>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<AudioSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AudioAllPage;
|
||||||
|
|
@ -66,7 +66,7 @@ const AudioSliderPage = () => {
|
||||||
<Link
|
<Link
|
||||||
href={`/shared/curated-content//giat-routine/audio/detail/${audio.id}`}
|
href={`/shared/curated-content//giat-routine/audio/detail/${audio.id}`}
|
||||||
key={audio?.id}
|
key={audio?.id}
|
||||||
className="flex flex-col sm:flex-row items-center hover:scale-110 transition-transform duration-300 bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
className="flex flex-col sm:flex-row items-center hover:scale-100 transition-transform duration-300 bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-16">
|
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-16">
|
||||||
<svg
|
<svg
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselNext,
|
||||||
|
CarouselPrevious,
|
||||||
|
} from "@/components/ui/carousel";
|
||||||
|
import { Link } from "@/components/navigation";
|
||||||
|
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { locale } from "dayjs";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getListContent } from "@/service/landing/landing";
|
||||||
|
import ContestTable from "../../../../contest/components/contest-table";
|
||||||
|
import AudioSliderPage from "../../audio/audio";
|
||||||
|
import TeksSliderPage from "../../document/teks";
|
||||||
|
import ImageSliderPage from "../../image/image";
|
||||||
|
import VideoSliderPage from "../../video/audio-visual";
|
||||||
|
|
||||||
|
const DocumentAllPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="my-3">
|
||||||
|
<Tabs defaultValue="giat-routine" className="w-full">
|
||||||
|
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||||
|
<p className="text-lg font-semibold ml-2">Konten Teks</p>
|
||||||
|
</Card>
|
||||||
|
<TabsContent value="giat-routine">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Teks</Label>
|
||||||
|
</div>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<TeksSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="giat-penugasan">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<Label>Audio Visual</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<VideoSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Audio</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<AudioSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Foto</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<ImageSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Teks</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<TeksSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="contest">
|
||||||
|
<Card>
|
||||||
|
<div className="py-3">
|
||||||
|
<ContestTable />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocumentAllPage;
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselNext,
|
||||||
|
CarouselPrevious,
|
||||||
|
} from "@/components/ui/carousel";
|
||||||
|
import { Link } from "@/components/navigation";
|
||||||
|
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { locale } from "dayjs";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getListContent } from "@/service/landing/landing";
|
||||||
|
import ContestTable from "../../../../contest/components/contest-table";
|
||||||
|
import AudioSliderPage from "../../audio/audio";
|
||||||
|
import TeksSliderPage from "../../document/teks";
|
||||||
|
import ImageSliderPage from "../../image/image";
|
||||||
|
import VideoSliderPage from "../../video/audio-visual";
|
||||||
|
|
||||||
|
const ImageAllPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="my-3">
|
||||||
|
<Tabs defaultValue="giat-routine" className="w-full">
|
||||||
|
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||||
|
<p className="text-lg font-semibold ml-2">Konten Image</p>
|
||||||
|
</Card>
|
||||||
|
<TabsContent value="giat-routine">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Image</Label>
|
||||||
|
</div>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<ImageSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageAllPage;
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Carousel,
|
||||||
|
CarouselContent,
|
||||||
|
CarouselItem,
|
||||||
|
CarouselNext,
|
||||||
|
CarouselPrevious,
|
||||||
|
} from "@/components/ui/carousel";
|
||||||
|
import { Link } from "@/components/navigation";
|
||||||
|
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { locale } from "dayjs";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getListContent } from "@/service/landing/landing";
|
||||||
|
import ContestTable from "../../../../contest/components/contest-table";
|
||||||
|
import AudioSliderPage from "../../audio/audio";
|
||||||
|
import TeksSliderPage from "../../document/teks";
|
||||||
|
import ImageSliderPage from "../../image/image";
|
||||||
|
import VideoSliderPage from "../audio-visual";
|
||||||
|
|
||||||
|
const VideoAllPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="my-3">
|
||||||
|
<Tabs defaultValue="giat-routine" className="w-full">
|
||||||
|
<Card className="py-3 px-2 my-4 h-20 flex items-center">
|
||||||
|
<p className="text-lg font-semibold ml-2">Konten Video</p>
|
||||||
|
</Card>
|
||||||
|
<TabsContent value="giat-routine">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Audio Visual</Label>
|
||||||
|
</div>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<VideoSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="giat-penugasan">
|
||||||
|
<div className="grid grid-cols-12 gap-5">
|
||||||
|
<div className="lg:col-span-12 col-span-12">
|
||||||
|
<Card>
|
||||||
|
<div className="flex justify-between items-center py-4 px-5">
|
||||||
|
<div>
|
||||||
|
<InputGroup merged>
|
||||||
|
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||||
|
<Search className=" h-4 w-4 dark:text-white" />
|
||||||
|
</InputGroupText>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search Judul..."
|
||||||
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-5 pb-3">
|
||||||
|
<Label>Audio Visual</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<VideoSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Audio</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<AudioSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Foto</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<ImageSliderPage />
|
||||||
|
</div>
|
||||||
|
<Label>Teks</Label>
|
||||||
|
<div className="px-5 my-5">
|
||||||
|
<TeksSliderPage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="contest">
|
||||||
|
<Card>
|
||||||
|
<div className="py-3">
|
||||||
|
<ContestTable />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoAllPage;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Search, UploadIcon } from "lucide-react";
|
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
@ -73,19 +73,69 @@ const CuratedContentPage = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-5 pb-3">
|
<div className="ml-5 pb-3">
|
||||||
<Label>Audio Visual</Label>
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Audio Visual</Label>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
{" "}
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
"/shared/curated-content/giat-routine/video/all"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
<div className="px-5 my-5">
|
<div className="px-5 my-5">
|
||||||
<VideoSliderPage />
|
<VideoSliderPage />
|
||||||
</div>
|
</div>
|
||||||
<Label>Audio</Label>
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Audio</Label>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
{" "}
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
"/shared/curated-content/giat-routine/audio/all"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
<div className="px-5 my-5">
|
<div className="px-5 my-5">
|
||||||
<AudioSliderPage />
|
<AudioSliderPage />
|
||||||
</div>
|
</div>
|
||||||
<Label>Foto</Label>
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Foto</Label>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
{" "}
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
"/shared/curated-content/giat-routine/image/all"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
<div className="px-5 my-5">
|
<div className="px-5 my-5">
|
||||||
<ImageSliderPage />
|
<ImageSliderPage />
|
||||||
</div>
|
</div>
|
||||||
<Label>Teks</Label>
|
<div className="flex justify-between items-center mx-3">
|
||||||
|
<Label className="text-base">Teks</Label>
|
||||||
|
<Label>
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
"/shared/curated-content/giat-routine/document/all"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
<div className="px-5 my-5">
|
<div className="px-5 my-5">
|
||||||
<TeksSliderPage />
|
<TeksSliderPage />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,13 @@ interface PlacementData {
|
||||||
placements: string;
|
placements: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FileType = {
|
||||||
|
contentId: number;
|
||||||
|
contentFile: string;
|
||||||
|
thumbnailFileUrl: string;
|
||||||
|
fileName: string;
|
||||||
|
};
|
||||||
|
|
||||||
const CustomEditor = dynamic(
|
const CustomEditor = dynamic(
|
||||||
() => {
|
() => {
|
||||||
return import("@/components/editor/custom-editor");
|
return import("@/components/editor/custom-editor");
|
||||||
|
|
@ -122,7 +129,7 @@ export default function FormConvertSPIT() {
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [tags, setTags] = useState<any[]>([]);
|
const [tags, setTags] = useState<any[]>([]);
|
||||||
const [detail, setDetail] = useState<Detail>();
|
const [detail, setDetail] = useState<any>();
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||||
|
|
@ -150,7 +157,9 @@ export default function FormConvertSPIT() {
|
||||||
polres: false,
|
polres: false,
|
||||||
});
|
});
|
||||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
const [placementLength, setPlacementLength] = useState([]);
|
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||||
|
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||||
|
const [files, setFiles] = useState<FileType[]>([]);
|
||||||
|
|
||||||
const options: Option[] = [
|
const options: Option[] = [
|
||||||
{ id: "all", label: "SEMUA" },
|
{ id: "all", label: "SEMUA" },
|
||||||
|
|
@ -201,6 +210,17 @@ export default function FormConvertSPIT() {
|
||||||
initState();
|
initState();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
userLevelId != undefined &&
|
||||||
|
roleId != undefined &&
|
||||||
|
userLevelId == "216" &&
|
||||||
|
roleId == "3"
|
||||||
|
) {
|
||||||
|
setIsUserMabesApprover(true);
|
||||||
|
}
|
||||||
|
}, [userLevelId, roleId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
userLevelId != undefined &&
|
userLevelId != undefined &&
|
||||||
|
|
@ -237,6 +257,47 @@ export default function FormConvertSPIT() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setupPlacement = (
|
||||||
|
index: number,
|
||||||
|
placement: string,
|
||||||
|
checked: boolean
|
||||||
|
) => {
|
||||||
|
let temp = [...filePlacements];
|
||||||
|
if (checked) {
|
||||||
|
if (placement === "all") {
|
||||||
|
temp[index] = ["all", "mabes", "polda", "international"];
|
||||||
|
} else {
|
||||||
|
const now = temp[index];
|
||||||
|
now.push(placement);
|
||||||
|
if (now.length === 3 && !now.includes("all")) {
|
||||||
|
now.push("all");
|
||||||
|
}
|
||||||
|
temp[index] = now;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (placement === "all") {
|
||||||
|
temp[index] = [];
|
||||||
|
} else {
|
||||||
|
const now = temp[index].filter((a) => a !== placement);
|
||||||
|
console.log("now", now);
|
||||||
|
temp[index] = now;
|
||||||
|
if (now.length === 3 && now.includes("all")) {
|
||||||
|
const newData = now.filter((b) => b !== "all");
|
||||||
|
temp[index] = newData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFilePlacements(temp);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupPlacementCheck = (length: number) => {
|
||||||
|
const temp = [];
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
temp.push([]);
|
||||||
|
}
|
||||||
|
setFilePlacements(temp);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
|
@ -244,6 +305,8 @@ export default function FormConvertSPIT() {
|
||||||
const details = response?.data?.data;
|
const details = response?.data?.data;
|
||||||
|
|
||||||
setDetail(details);
|
setDetail(details);
|
||||||
|
setFiles(details?.contentList);
|
||||||
|
setupPlacementCheck(details?.contentList?.length);
|
||||||
|
|
||||||
const filesData = details.contentList || [];
|
const filesData = details.contentList || [];
|
||||||
const fileUrls = filesData.map((file: { contentFile: string }) =>
|
const fileUrls = filesData.map((file: { contentFile: string }) =>
|
||||||
|
|
@ -272,49 +335,6 @@ export default function FormConvertSPIT() {
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
const getPlacement = (): PlacementData[] => {
|
|
||||||
return tempFile
|
|
||||||
.filter((file: FileData) => (file.placement || []).length > 0) // Gunakan default array
|
|
||||||
.map((file: FileData) => ({
|
|
||||||
mediaFileId: Number(file.contentId),
|
|
||||||
placements: (file.placement || []).join(","), // Gunakan default array
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const setupPlacement = (value: string, id: number): void => {
|
|
||||||
const updatedFiles = tempFile.map((file: FileData) => {
|
|
||||||
if (file.contentId === id) {
|
|
||||||
const currentPlacement = file.placement || [];
|
|
||||||
if (currentPlacement.includes(value)) {
|
|
||||||
// Remove the placement value
|
|
||||||
file.placement = currentPlacement.filter((val) => val !== value);
|
|
||||||
} else {
|
|
||||||
// Add the placement value
|
|
||||||
file.placement =
|
|
||||||
value === "all"
|
|
||||||
? ["all", "mabes", "polda", "international"]
|
|
||||||
: [...currentPlacement, value];
|
|
||||||
if (file.placement.includes("all") && value !== "all") {
|
|
||||||
file.placement = file.placement.filter((val) => val !== "all");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
});
|
|
||||||
|
|
||||||
const placementLength = updatedFiles.reduce(
|
|
||||||
(acc: any, file: any) => acc + (file.placement?.length || 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
setTempFile(
|
|
||||||
updatedFiles.sort((a: any, b: any) => a.contentId - b.contentId)
|
|
||||||
);
|
|
||||||
setPlacementLength(placementLength);
|
|
||||||
|
|
||||||
console.log("Updated Files:", updatedFiles);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheckboxChangeFile = (contentId: number, value: string) => {
|
const handleCheckboxChangeFile = (contentId: number, value: string) => {
|
||||||
setTempFile((prevTempFile: any) => {
|
setTempFile((prevTempFile: any) => {
|
||||||
return prevTempFile.map((file: any) => {
|
return prevTempFile.map((file: any) => {
|
||||||
|
|
@ -359,12 +379,32 @@ export default function FormConvertSPIT() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPlacement = () => {
|
||||||
|
console.log("getPlaa", filePlacements);
|
||||||
|
const temp = [];
|
||||||
|
for (let i = 0; i < filePlacements?.length; i++) {
|
||||||
|
if (filePlacements[i].length !== 0) {
|
||||||
|
const now = filePlacements[i].filter((a) => a !== "all");
|
||||||
|
const data = {
|
||||||
|
mediaFileId: files[i].contentId,
|
||||||
|
placement: now.join(","),
|
||||||
|
};
|
||||||
|
temp.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
};
|
||||||
|
|
||||||
const save = async (data: {
|
const save = async (data: {
|
||||||
contentTitle: string;
|
contentTitle: string;
|
||||||
contentDescription: string;
|
contentDescription: string;
|
||||||
contentRewriteDescription: string;
|
contentRewriteDescription: string;
|
||||||
contentCreator: string;
|
contentCreator: string;
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
|
const temp = [];
|
||||||
|
for (const element of detail.contentList) {
|
||||||
|
temp.push([]);
|
||||||
|
}
|
||||||
const description =
|
const description =
|
||||||
selectedFileType === "original"
|
selectedFileType === "original"
|
||||||
? data.contentDescription
|
? data.contentDescription
|
||||||
|
|
@ -376,15 +416,16 @@ export default function FormConvertSPIT() {
|
||||||
description,
|
description,
|
||||||
htmlDescription: description,
|
htmlDescription: description,
|
||||||
tags: "siap",
|
tags: "siap",
|
||||||
categoryId: selectedCategoryId,
|
categoryId: 1,
|
||||||
publishedFor: publishedFor.join(","),
|
publishedFor: publishedFor.join(","),
|
||||||
creator: data.contentCreator,
|
creator: data.contentCreator,
|
||||||
files: getPlacement(), // Include placement data
|
files: isUserMabesApprover ? getPlacement() : [], // Include placement data
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await convertSPIT(requestData);
|
const response = await convertSPIT(requestData);
|
||||||
console.log("Form Data Submitted:", response);
|
console.log("Form Data Submitted:", response);
|
||||||
|
setFilePlacements(temp);
|
||||||
|
setFiles(detail.files);
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Sukses",
|
title: "Sukses",
|
||||||
text: "Data berhasil disimpan.",
|
text: "Data berhasil disimpan.",
|
||||||
|
|
@ -699,62 +740,96 @@ export default function FormConvertSPIT() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isMabesApprover ? (
|
{files?.map((file, index) => (
|
||||||
<div className="mt-5">
|
<div
|
||||||
<Label className="text-xl text-black">
|
key={file.contentId}
|
||||||
Penempatan File
|
className="flex flex-row gap-2 items-center my-3"
|
||||||
</Label>
|
>
|
||||||
{detailThumb.map((data: any) => (
|
<img
|
||||||
<div
|
src={file.contentFile}
|
||||||
key={data.contentId}
|
className="w-[150px] rounded-md"
|
||||||
className="flex items-center gap-3 mt-2"
|
/>
|
||||||
>
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<img
|
<div className="flex justify-between text-sm">
|
||||||
className="object-cover w-36 h-32"
|
{file.fileName}
|
||||||
src={data}
|
{/* <a
|
||||||
alt={`Thumbnail ${data.contentId}`}
|
onClick={() =>
|
||||||
/>
|
handleDeleteFileApproval(file.id)
|
||||||
<div className="flex flex-row gap-3 items-center">
|
}
|
||||||
{[
|
>
|
||||||
"all",
|
<Icon icon="humbleicons:times" color="red" />
|
||||||
"mabes",
|
</a> */}
|
||||||
"polda",
|
</div>
|
||||||
"satker",
|
|
||||||
"internasional",
|
<div className="flex flex-row gap-2">
|
||||||
].map((value) => (
|
<div className="flex items-center space-x-2">
|
||||||
<label
|
<Checkbox
|
||||||
key={value}
|
id="terms"
|
||||||
className="text-blue-500 cursor-pointer flex items-center gap-1"
|
value="all"
|
||||||
>
|
checked={filePlacements[index]?.includes("all")}
|
||||||
<input
|
onCheckedChange={(e) =>
|
||||||
type="checkbox"
|
setupPlacement(index, "all", Boolean(e))
|
||||||
name="placement"
|
}
|
||||||
value={value}
|
/>
|
||||||
onChange={() =>
|
<label
|
||||||
handleCheckboxChangeFile(
|
htmlFor="terms"
|
||||||
data.contentId,
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
value
|
>
|
||||||
)
|
Semua
|
||||||
}
|
</label>
|
||||||
checked={
|
</div>
|
||||||
tempFile
|
<div className="flex items-center space-x-2">
|
||||||
.find(
|
<Checkbox
|
||||||
(file: FileData) =>
|
id="terms"
|
||||||
file.contentId === data.contentId
|
checked={filePlacements[index]?.includes("mabes")}
|
||||||
)
|
onCheckedChange={(e) =>
|
||||||
?.placement?.includes(value) || false
|
setupPlacement(index, "mabes", Boolean(e))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{value.charAt(0).toUpperCase() + value.slice(1)}
|
<label
|
||||||
</label>
|
htmlFor="terms"
|
||||||
))}
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Nasional
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="terms"
|
||||||
|
checked={filePlacements[index]?.includes("polda")}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
setupPlacement(index, "polda", Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="terms"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Wilayah
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="terms"
|
||||||
|
checked={filePlacements[index]?.includes(
|
||||||
|
"international"
|
||||||
|
)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
setupPlacement(index, "international", Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="terms"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Internasional
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
))}
|
||||||
""
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -797,14 +872,16 @@ export default function FormConvertSPIT() {
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Tag</Label>
|
<Label>Tag</Label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{detail?.contentTag?.split(",").map((tag, index) => (
|
{detail?.contentTag
|
||||||
<Badge
|
?.split(",")
|
||||||
key={index}
|
.map((tag: any, index: any) => (
|
||||||
className="border rounded-md px-2 py-2"
|
<Badge
|
||||||
>
|
key={index}
|
||||||
{tag.trim()}
|
className="border rounded-md px-2 py-2"
|
||||||
</Badge>
|
>
|
||||||
))}
|
{tag.trim()}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,16 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { ChevronDown, ChevronUp, DotSquare, TrashIcon } from "lucide-react";
|
import {
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
Dock,
|
||||||
|
DotSquare,
|
||||||
|
ImageIcon,
|
||||||
|
Music,
|
||||||
|
TrashIcon,
|
||||||
|
VideoIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { Link } from "@/components/navigation";
|
import { Link } from "@/components/navigation";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
@ -1064,7 +1073,7 @@ export default function FormTaskDetail() {
|
||||||
onClick={() => setSelectedVideo(file.url)}
|
onClick={() => setSelectedVideo(file.url)}
|
||||||
>
|
>
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
{renderFilePreview(file.url)}
|
<VideoIcon />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-card-foreground">
|
<div className="text-sm text-card-foreground">
|
||||||
|
|
@ -1109,7 +1118,7 @@ export default function FormTaskDetail() {
|
||||||
onClick={() => setSelectedImage(file.url)}
|
onClick={() => setSelectedImage(file.url)}
|
||||||
>
|
>
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
{renderFilePreview(file.url)}
|
<ImageIcon />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-card-foreground">
|
<div className="text-sm text-card-foreground">
|
||||||
|
|
@ -1157,7 +1166,7 @@ export default function FormTaskDetail() {
|
||||||
onClick={() => setSelectedText(file.url)}
|
onClick={() => setSelectedText(file.url)}
|
||||||
>
|
>
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
{renderFilePreview(file.url)}
|
<Dock />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-card-foreground">
|
<div className="text-sm text-card-foreground">
|
||||||
|
|
@ -1228,7 +1237,7 @@ export default function FormTaskDetail() {
|
||||||
onClick={() => setSelectedAudio(file.url)}
|
onClick={() => setSelectedAudio(file.url)}
|
||||||
>
|
>
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
{renderFilePreview(file.url)}
|
<Music />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-card-foreground">
|
<div className="text-sm text-card-foreground">
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,14 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import {
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
Dock,
|
||||||
|
ImageIcon,
|
||||||
|
Music,
|
||||||
|
VideoIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import FileUploader from "../shared/file-uploader";
|
import FileUploader from "../shared/file-uploader";
|
||||||
import { AudioRecorder } from "react-audio-voice-recorder";
|
import { AudioRecorder } from "react-audio-voice-recorder";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
@ -376,8 +383,14 @@ export default function FormTaskEdit() {
|
||||||
if (audioFiles?.length == 0) {
|
if (audioFiles?.length == 0) {
|
||||||
setIsAudioUploadFinish(true);
|
setIsAudioUploadFinish(true);
|
||||||
}
|
}
|
||||||
audioFiles?.map(async (item: any, index: number) => {
|
audioFiles.map(async (item: FileWithPreview, index: number) => {
|
||||||
await uploadResumableFile(index, String(id), item, "4", "0");
|
await uploadResumableFile(
|
||||||
|
index,
|
||||||
|
String(id),
|
||||||
|
item, // Use .file to access the actual File object
|
||||||
|
"4",
|
||||||
|
"0" // Optional: Replace with actual duration if available
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// MySwal.fire({
|
// MySwal.fire({
|
||||||
|
|
@ -446,10 +459,17 @@ export default function FormTaskEdit() {
|
||||||
audio.controls = true;
|
audio.controls = true;
|
||||||
document.body.appendChild(audio);
|
document.body.appendChild(audio);
|
||||||
|
|
||||||
// Convert Blob to File
|
// Convert Blob to File and add preview
|
||||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
const fileWithPreview: FileWithPreview = Object.assign(
|
||||||
setAudioFile(file);
|
new File([blob], "voiceNote.webm", { type: "audio/webm" }),
|
||||||
|
{ preview: url }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add to state
|
||||||
|
setAudioFile(fileWithPreview);
|
||||||
|
setAudioFiles((prev) => [...prev, fileWithPreview]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteAudio = () => {
|
const handleDeleteAudio = () => {
|
||||||
// Remove the audio file by setting state to null
|
// Remove the audio file by setting state to null
|
||||||
setAudioFile(null);
|
setAudioFile(null);
|
||||||
|
|
@ -835,7 +855,7 @@ export default function FormTaskEdit() {
|
||||||
<Label htmlFor="attachments">Lampiran</Label>
|
<Label htmlFor="attachments">Lampiran</Label>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
{videoUploadedFiles?.length > 0 && <Label>Video</Label>}
|
<Label>Video</Label>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"mp4/*": [],
|
"mp4/*": [],
|
||||||
|
|
@ -843,7 +863,7 @@ export default function FormTaskEdit() {
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .mp4 atau .mov."
|
label="Upload file dengan format .mp4 atau .mov."
|
||||||
onDrop={(files) => setImageFiles(files)}
|
onDrop={(files) => setVideoFiles(files)}
|
||||||
/>
|
/>
|
||||||
{videoUploadedFiles?.map((file: any, index: number) => (
|
{videoUploadedFiles?.map((file: any, index: number) => (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -853,7 +873,7 @@ export default function FormTaskEdit() {
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
{renderFilePreview(file.url)}
|
<VideoIcon />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className=" text-sm text-card-foreground">
|
<div className=" text-sm text-card-foreground">
|
||||||
|
|
@ -876,7 +896,7 @@ export default function FormTaskEdit() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{imageUploadedFiles?.length > 0 && <Label>Foto</Label>}
|
<Label>Foto</Label>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"image/*": [],
|
"image/*": [],
|
||||||
|
|
@ -893,7 +913,7 @@ export default function FormTaskEdit() {
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
{renderFilePreview(file.url)}
|
<ImageIcon />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className=" text-sm text-card-foreground">
|
<div className=" text-sm text-card-foreground">
|
||||||
|
|
@ -916,7 +936,7 @@ export default function FormTaskEdit() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{textUploadedFiles?.length > 0 && <Label>Teks</Label>}
|
<Label>Teks</Label>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
accept={{
|
accept={{
|
||||||
"pdf/*": [],
|
"pdf/*": [],
|
||||||
|
|
@ -933,7 +953,7 @@ export default function FormTaskEdit() {
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
{renderFilePreview(file.url)}
|
<Dock />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className=" text-sm text-card-foreground">
|
<div className=" text-sm text-card-foreground">
|
||||||
|
|
@ -956,7 +976,7 @@ export default function FormTaskEdit() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{audioUploadedFiles?.length > 0 && <Label>Audio</Label>}
|
<Label>Audio</Label>
|
||||||
<AudioRecorder
|
<AudioRecorder
|
||||||
onRecordingComplete={addAudioElement}
|
onRecordingComplete={addAudioElement}
|
||||||
audioTrackConstraints={{
|
audioTrackConstraints={{
|
||||||
|
|
@ -973,7 +993,9 @@ export default function FormTaskEdit() {
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .mp3 atau .wav."
|
label="Upload file dengan format .mp3 atau .wav."
|
||||||
onDrop={(files) => setAudioFiles(files)}
|
onDrop={(files) =>
|
||||||
|
setAudioFiles((prev) => [...prev, ...files])
|
||||||
|
}
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/>
|
||||||
{audioUploadedFiles?.map((file: any, index: number) => (
|
{audioUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
|
@ -984,7 +1006,7 @@ export default function FormTaskEdit() {
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
{renderFilePreview(file.url)}
|
<Music />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className=" text-sm text-card-foreground">
|
<div className=" text-sm text-card-foreground">
|
||||||
|
|
@ -1008,7 +1030,7 @@ export default function FormTaskEdit() {
|
||||||
</div>
|
</div>
|
||||||
{audioFile && (
|
{audioFile && (
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
<p>Voice Note</p>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDeleteAudio}
|
onClick={handleDeleteAudio}
|
||||||
|
|
|
||||||
|
|
@ -275,8 +275,14 @@ export default function FormTask() {
|
||||||
if (audioFiles?.length == 0) {
|
if (audioFiles?.length == 0) {
|
||||||
setIsAudioUploadFinish(true);
|
setIsAudioUploadFinish(true);
|
||||||
}
|
}
|
||||||
audioFiles?.map(async (item: any, index: number) => {
|
audioFiles.map(async (item: FileWithPreview, index: number) => {
|
||||||
await uploadResumableFile(index, String(id), item, "4", "0");
|
await uploadResumableFile(
|
||||||
|
index,
|
||||||
|
String(id),
|
||||||
|
item, // Use .file to access the actual File object
|
||||||
|
"4",
|
||||||
|
"0" // Optional: Replace with actual duration if available
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -335,15 +341,19 @@ export default function FormTask() {
|
||||||
audio.controls = true;
|
audio.controls = true;
|
||||||
document.body.appendChild(audio);
|
document.body.appendChild(audio);
|
||||||
|
|
||||||
// Convert Blob to File
|
// Convert Blob to File and add preview
|
||||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
const fileWithPreview: FileWithPreview = Object.assign(
|
||||||
setAudioFile(file);
|
new File([blob], "voiceNote.webm", { type: "audio/webm" }),
|
||||||
|
{ preview: url }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add to state
|
||||||
|
setAudioFile(fileWithPreview);
|
||||||
|
setAudioFiles((prev) => [...prev, fileWithPreview]);
|
||||||
};
|
};
|
||||||
const handleDeleteAudio = () => {
|
|
||||||
// Remove the audio file by setting state to null
|
const handleDeleteAudio = (index: number) => {
|
||||||
setAudioFile(null);
|
setAudioFiles((prev) => prev.filter((_, idx) => idx !== index));
|
||||||
const audioElements = document.querySelectorAll("audio");
|
|
||||||
audioElements.forEach((audio) => audio.remove());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function uploadResumableFile(
|
async function uploadResumableFile(
|
||||||
|
|
@ -752,7 +762,7 @@ export default function FormTask() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Voice Note</Label>
|
<Label>Audio</Label>
|
||||||
<AudioRecorder
|
<AudioRecorder
|
||||||
onRecordingComplete={addAudioElement}
|
onRecordingComplete={addAudioElement}
|
||||||
audioTrackConstraints={{
|
audioTrackConstraints={{
|
||||||
|
|
@ -769,23 +779,28 @@ export default function FormTask() {
|
||||||
}}
|
}}
|
||||||
maxSize={100}
|
maxSize={100}
|
||||||
label="Upload file dengan format .mp3 atau .wav."
|
label="Upload file dengan format .mp3 atau .wav."
|
||||||
onDrop={(files) => setAudioFiles(files)}
|
onDrop={(files) =>
|
||||||
|
setAudioFiles((prev) => [...prev, ...files])
|
||||||
|
}
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{audioFile && (
|
{audioFiles?.map((audio: any, idx: any) => (
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div
|
||||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
key={idx}
|
||||||
|
className="flex flex-row justify-between items-center"
|
||||||
|
>
|
||||||
|
<p>Voice Note</p>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDeleteAudio}
|
onClick={() => handleDeleteAudio(idx)}
|
||||||
size="sm"
|
size="sm"
|
||||||
color="destructive"
|
color="destructive"
|
||||||
>
|
>
|
||||||
X
|
X
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||||
{/* Display remaining time */}
|
{/* Display remaining time */}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue