diff options
| author | Factiven <[email protected]> | 2023-09-26 23:35:35 +0700 |
|---|---|---|
| committer | Factiven <[email protected]> | 2023-09-26 23:35:35 +0700 |
| commit | 20b8a7267827e3a07c1eef668c3b9c22fda43765 (patch) | |
| tree | 2fec9006dfac5737d8b227bf5ccce73880800cc2 /components/admin | |
| parent | Update release.md (diff) | |
| download | moopa-20b8a7267827e3a07c1eef668c3b9c22fda43765.tar.xz moopa-20b8a7267827e3a07c1eef668c3b9c22fda43765.zip | |
Update v4.1.2v4.1.2
Diffstat (limited to 'components/admin')
| -rw-r--r-- | components/admin/dashboard/index.js | 134 | ||||
| -rw-r--r-- | components/admin/layout.js | 75 | ||||
| -rw-r--r-- | components/admin/meta/AppendMeta.js | 252 |
3 files changed, 461 insertions, 0 deletions
diff --git a/components/admin/dashboard/index.js b/components/admin/dashboard/index.js new file mode 100644 index 0000000..64a1d6f --- /dev/null +++ b/components/admin/dashboard/index.js @@ -0,0 +1,134 @@ +import React, { useState } from "react"; + +export default function AdminDashboard({ + animeCount, + infoCount, + metaCount, + report, +}) { + const [message, setMessage] = useState(""); + const [selectedTime, setSelectedTime] = useState(""); + const [unixTimestamp, setUnixTimestamp] = useState(null); + + const handleSubmit = (e) => { + e.preventDefault(); + + if (selectedTime) { + const unixTime = Math.floor(new Date(selectedTime).getTime() / 1000); + setUnixTimestamp(unixTime); + } + }; + return ( + <div className="flex flex-col gap-5 px-5 py-10 h-full"> + <div className="flex flex-col gap-2"> + <p className="font-semibold">Stats</p> + <div className="grid grid-cols-3 gap-5"> + <div className="flex-center flex-col bg-secondary rounded p-5"> + <p className="font-karla text-4xl">{animeCount}</p> + <p className="font-karla text-xl">Anime</p> + </div> + <div className="flex-center flex-col bg-secondary rounded p-5"> + <p className="font-karla text-4xl">{infoCount}</p> + <p className="font-karla text-xl">detail info</p> + </div> + <div className="flex-center flex-col bg-secondary rounded p-5"> + <p className="font-karla text-4xl">{metaCount}</p> + <p className="font-karla text-xl">Metadata</p> + </div> + </div> + </div> + <div className="grid grid-cols-2 gap-5 h-full"> + <div className="flex flex-col gap-2"> + <p className="font-semibold">Broadcast</p> + <div className="flex flex-col justify-between bg-secondary rounded p-5 h-full"> + <form onSubmit={handleSubmit}> + <div className="mb-4"> + <label + htmlFor="message" + className="block text-txt font-light mb-2" + > + Message + </label> + <input + type="text" + id="message" + value={message} + onChange={(e) => setMessage(e.target.value)} + required + className="w-full px-3 py-2 border rounded-md focus:outline-none text-black" + /> + </div> + <div className="mb-4"> + <label + htmlFor="selectedTime" + className="block text-txt font-light mb-2" + > + Select Time + </label> + <input + type="datetime-local" + id="selectedTime" + value={selectedTime} + onChange={(e) => setSelectedTime(e.target.value)} + required + className="w-full px-3 py-2 border rounded-md focus:outline-none text-black" + /> + </div> + <button + type="submit" + className="bg-image text-white py-2 px-4 rounded-md hover:bg-opacity-80 transition duration-300" + > + Submit + </button> + </form> + {unixTimestamp && ( + <p> + Unix Timestamp: <strong>{unixTimestamp}</strong> + </p> + )} + </div> + </div> + <div className="flex flex-col gap-2"> + <p className="font-semibold">Recent Reports</p> + <div className="bg-secondary rounded p-5 h-full"> + <div className="rounded overflow-hidden w-full h-full"> + {report?.map((i, index) => ( + <div + key={index} + className="odd:bg-primary/80 even:bg-primary/40 p-2 flex justify-between items-center" + > + {i.desc}{" "} + {i.severity === "Low" && ( + <span className="relative w-5 h-5 flex-center shrink-0"> + {/* <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-rose-500 opacity-75"></span> */} + <span className="relative inline-flex rounded-full h-3 w-3 bg-green-500"></span> + </span> + )} + {i.severity === "Medium" && ( + <span className="relative w-5 h-5 flex-center shrink-0"> + {/* <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-rose-500 opacity-75"></span> */} + <span className="relative inline-flex rounded-full h-3 w-3 bg-amber-500"></span> + </span> + )} + {i.severity === "High" && ( + <span className="relative w-5 h-5 flex-center shrink-0"> + {/* <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-rose-500 opacity-75"></span> */} + <span className="relative animate-pulse inline-flex rounded-full h-3 w-3 bg-rose-500"></span> + </span> + )} + {i.severity === "Critical" && ( + <span className="relative w-5 h-5 flex-center shrink-0"> + <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-900 opacity-75"></span> + <span className="relative inline-flex rounded-full h-3 w-3 bg-red-900"></span> + </span> + )} + </div> + ))} + </div> + </div> + </div> + </div> + <div className="w-full h-full">a</div> + </div> + ); +} diff --git a/components/admin/layout.js b/components/admin/layout.js new file mode 100644 index 0000000..3209dcf --- /dev/null +++ b/components/admin/layout.js @@ -0,0 +1,75 @@ +import { + CloudArrowUpIcon, + Cog6ToothIcon, + HomeIcon, + UserIcon, +} from "@heroicons/react/24/outline"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import React from "react"; + +const Navigation = [ + { + name: "Dashboard", + page: 1, + icon: <HomeIcon />, + current: false, + }, + { + name: "Metadata", + page: 2, + icon: <CloudArrowUpIcon />, + current: false, + }, + { + name: "Users", + page: 3, + icon: <UserIcon />, + current: false, + }, + { + name: "Settings", + page: 4, + icon: <Cog6ToothIcon />, + current: false, + }, +]; + +export default function AdminLayout({ children, page, setPage }) { + return ( + <div className="relative w-screen h-screen"> + <div className="absolute flex flex-col gap-5 top-0 left-0 py-2 bg-secondary w-[14rem] h-full"> + <div className="flex flex-col px-3"> + <p className="text-sm font-light text-action font-outfit">moopa</p> + <h1 className="text-2xl font-bold text-white"> + Admin <br /> + Dashboard + </h1> + </div> + <div className="flex flex-col px-1"> + {Navigation.map((item, index) => ( + <button + key={item.name} + onClick={() => { + setPage(item.page); + }} + className={`flex items-center gap-2 p-2 group ${ + page == item.page ? "bg-image/50" : "text-txt" + } hover:bg-image rounded transition-colors duration-200 ease-in-out`} + > + <div + className={`w-5 h-5 ${ + page == item.page ? "text-action" : "text-txt" + } group-hover:text-action`} + > + {item.icon} + </div> + <p>{item.name}</p> + </button> + ))} + </div> + </div> + <div className="ml-[14rem] overflow-x-hidden h-full">{children}</div> + </div> + ); +} diff --git a/components/admin/meta/AppendMeta.js b/components/admin/meta/AppendMeta.js new file mode 100644 index 0000000..1707ed2 --- /dev/null +++ b/components/admin/meta/AppendMeta.js @@ -0,0 +1,252 @@ +import Loading from "@/components/shared/loading"; +import Image from "next/image"; +import { useState } from "react"; +import { toast } from "react-toastify"; + +// Define a function to convert the data +function convertData(episodes) { + const convertedData = episodes.map((episode) => ({ + episode: episode.episode, + title: episode?.title, + description: episode?.description || null, + img: episode?.img?.hd || episode?.img?.mobile || null, // Use hd if available, otherwise use mobile + })); + + return convertedData; +} + +export default function AppendMeta({ api }) { + const [id, setId] = useState(); + const [resultData, setResultData] = useState(null); + + const [query, setQuery] = useState(""); + const [tmdbId, setTmdbId] = useState(); + const [hasilQuery, setHasilQuery] = useState([]); + const [season, setSeason] = useState(); + + const [override, setOverride] = useState(); + + const [loading, setLoading] = useState(false); + + const handleSearch = async () => { + try { + setLoading(true); + setResultData(null); + const res = await fetch(`${api}/meta/tmdb/${query}`); + const json = await res.json(); + const data = json.results.filter((i) => i.type === "TV Series"); + setHasilQuery(data); + setLoading(false); + } catch (err) { + console.log(err); + } + }; + + const handleDetail = async () => { + try { + setLoading(true); + const res = await fetch(`${api}/meta/tmdb/info/${tmdbId}?type=TV%20Series +`); + const json = await res.json(); + const data = json.seasons; + setHasilQuery(data); + setLoading(false); + } catch (err) { + console.log(err); + } + }; + + const handleStore = async () => { + try { + setLoading(true); + if (!resultData && !id) { + console.log("No data to store"); + setLoading(false); + return; + } + const data = await fetch("/api/v2/admin/meta", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + id: id, + data: resultData, + }), + }); + if (data.status === 200) { + const json = await data.json(); + toast.success(json.message); + setLoading(false); + } + } catch (err) { + console.log(err); + } + }; + + const handleOverride = async () => { + setResultData(JSON.parse(override)); + }; + + return ( + <> + <div className="container mx-auto p-4 scrol"> + <h1 className="text-3xl font-semibold mb-4">Append Data Page</h1> + <div> + <div className="space-y-3 mb-4"> + <label>Search Anime:</label> + <input + type="text" + className="w-full px-3 py-2 border rounded-md text-black" + value={query} + onChange={(e) => setQuery(e.target.value)} + /> + <button + type="button" + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + onClick={handleSearch} + > + Find Anime{" "} + </button> + </div> + <div className="space-y-3 mb-4"> + <label>Get Episodes:</label> + <input + type="number" + placeholder="TMDB ID" + className="w-full px-3 py-2 border rounded-md text-black" + value={tmdbId} + onChange={(e) => setTmdbId(e.target.value)} + /> + <button + type="button" + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + onClick={handleDetail} + > + Get Details + </button> + </div> + + <div className="space-y-3 mb-4"> + <label>Override Result:</label> + <textarea + rows="5" + className="w-full px-3 py-2 border rounded-md text-black" + value={override} + onChange={(e) => setOverride(e.target.value)} + /> + <button + type="button" + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + onClick={handleOverride} + > + Override{" "} + </button> + </div> + + <div className="space-y-3 mb-4"> + <label className="block text-sm font-medium text-gray-300"> + Anime ID: + </label> + <input + type="number" + placeholder="AniList ID" + className="w-full px-3 py-2 border rounded-md text-black" + value={id} + onChange={(e) => setId(e.target.value)} + /> + </div> + <div className="mb-4"> + <button + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + onClick={handleStore} + > + Store Data {season && `Season ${season}`} + </button> + </div> + + {!loading && hasilQuery?.some((i) => i?.season) && ( + <div className="border rounded-md p-4 mt-4"> + <h2 className="text-lg font-semibold mb-2"> + Which season do you want to format? + </h2> + <div className="w-full flex gap-2"> + {hasilQuery?.map((season, index) => ( + <button + type="button" + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + key={index} + onClick={() => { + setLoading(true); + const data = hasilQuery[index].episodes; + const convertedData = convertData(data); + setSeason(index + 1); + setResultData(convertedData); + console.log(convertedData); + setLoading(false); + }} + > + <p>{season.season} </p> + </button> + ))} + </div> + </div> + )} + + {!loading && resultData && ( + <div className="border rounded-md p-4 mt-4"> + <h2 className="text-lg font-semibold mb-2">Season {season}</h2> + <pre>{JSON.stringify(resultData, null, 2)}</pre> + </div> + )} + {!loading && hasilQuery && ( + <div className="border rounded-md p-4 mt-4"> + {/* <h2 className="text-lg font-semibold mb-2"> + Result Data,{" "} + {hasilQuery.length > 0 && `${hasilQuery.length} Seasons`}: + </h2> */} + <div className="flex flex-wrap gap-10"> + {!hasilQuery.every((i) => i?.episodes) && + hasilQuery?.map((i, index) => ( + <div + key={i.id} + className="flex flex-col items-center gap-2" + > + <p className="font-karla font-semibold"> + {i.releaseDate} + </p> + <Image + src={i.image} + width={500} + height={500} + className="w-[160px] h-[210px] object-cover" + /> + <button + className="bg-blue-500 text-white w-[160px] py-1 rounded-md hover:bg-blue-600 text-sm" + onClick={() => { + setTmdbId(i.id); + }} + > + <p className="line-clamp-1 px-1">{i.title}</p> + </button> + </div> + ))} + </div> + <pre>{JSON.stringify(hasilQuery, null, 2)}</pre> + </div> + )} + + {loading && <Loading />} + </div> + <div> + {/* {resultData && ( + <div className="border rounded-md p-4 mt-4"> + <h2 className="text-lg font-semibold mb-2">Result Data:</h2> + <pre>{JSON.stringify(resultData, null, 2)}</pre> + </div> + )} */} + </div> + </div> + </> + ); +} |