UPDATE: More setting in theorycraft mode
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m4s

This commit is contained in:
2026-01-12 17:18:24 +07:00
parent 2899df4e61
commit f81fa964a5
6 changed files with 182 additions and 36 deletions

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { EyeOff, Eye, Hammer, RefreshCw, ShieldBan, User, Swords, SkipForward, BowArrow, Info } from "lucide-react" import { EyeOff, Eye, Hammer, RefreshCw, ShieldBan, User, Swords, SkipForward, BowArrow, Info, RouteIcon, Search } from "lucide-react"
import useGlobalStore from '@/stores/globalStore' import useGlobalStore from '@/stores/globalStore'
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import useEventStore from "@/stores/eventStore" import useEventStore from "@/stores/eventStore"
@@ -8,6 +8,8 @@ import { getLocaleName, getNameChar } from "@/helper"
import useLocaleStore from "@/stores/localeStore" import useLocaleStore from "@/stores/localeStore"
import useAvatarStore from "@/stores/avatarStore" import useAvatarStore from "@/stores/avatarStore"
import SelectCustomImage from "../select/customSelectImage" import SelectCustomImage from "../select/customSelectImage"
import { useMemo, useState } from "react"
import useMazeStore from "@/stores/mazeStore"
export default function ExtraSettingBar() { export default function ExtraSettingBar() {
const { extraData, setExtraData } = useGlobalStore() const { extraData, setExtraData } = useGlobalStore()
@@ -15,13 +17,39 @@ export default function ExtraSettingBar() {
const { PEAKEvent } = useEventStore() const { PEAKEvent } = useEventStore()
const { listAvatar } = useAvatarStore() const { listAvatar } = useAvatarStore()
const { locale } = useLocaleStore() const { locale } = useLocaleStore()
const [showSearchStage, setShowSearchStage] = useState(false)
const [isChildClick, setIsChildClick] = useState(false)
const [stageSearchTerm, setStageSearchTerm] = useState("")
const [stagePage, setStagePage] = useState(1)
const { Stage } = useMazeStore()
const pageSize = 30
const stageList = useMemo(() => Object.values(Stage).map((stage) => ({
id: stage.stage_id.toString(),
name: `${stage.stage_type} (${stage.stage_id})`,
})), [Stage])
const filteredStages = useMemo(() => stageList.filter((s) =>
s.name.toLowerCase().includes(stageSearchTerm.toLowerCase())
), [stageList, stageSearchTerm])
const paginatedStages = useMemo(() => filteredStages.slice(
(stagePage - 1) * pageSize,
stagePage * pageSize
), [filteredStages, stagePage, pageSize])
const hasMorePages = useMemo(() => stagePage * pageSize < filteredStages.length, [stagePage, filteredStages])
const onChangeSearch = (v: string) => {
setStageSearchTerm(v)
setStagePage(1)
}
return ( return (
<div className="px-4 sm:px-6 py-4 space-y-8"> <div className="px-4 sm:px-6 py-4 space-y-8">
{extraData?.theory_craft && ( {extraData?.theory_craft && (
<div className="space-y-4"> <div className="space-y-4">
<h3 className="text-lg font-bold flex items-center gap-2"> <h3 className="text-lg font-bold flex items-center gap-2">
{transI18n("theoryCraft")} {transI18n("theoryCraft")}
<div className="tooltip tooltip-info tooltip-bottom" data-tip={transI18n("detailTheoryCraft")}> <div className="tooltip tooltip-info tooltip-bottom" data-tip={transI18n("detailTheoryCraft")}>
<Info className="text-primary" size={20} /> <Info className="text-primary" size={20} />
</div> </div>
@@ -39,8 +67,9 @@ export default function ExtraSettingBar() {
...extraData, ...extraData,
theory_craft: { theory_craft: {
hp: extraData?.theory_craft?.hp || {}, hp: extraData?.theory_craft?.hp || {},
cycle_count: extraData?.theory_craft?.cycle_count || 0, cycle_count: extraData?.theory_craft?.cycle_count || 1,
mode: e.target.checked mode: e.target.checked,
stage_id: extraData?.theory_craft?.stage_id || 0
} }
}) })
} }
@@ -52,28 +81,145 @@ export default function ExtraSettingBar() {
</motion.div> </motion.div>
{extraData?.theory_craft?.mode && ( {extraData?.theory_craft?.mode && (
<motion.div className="form-control bg-base-200 p-4 rounded-xl shadow"> <>
<label className="flex flex-wrap items-center justify-start gap-3"> <motion.div className="form-control bg-base-200 p-4 rounded-xl shadow">
<RefreshCw className="text-info" size={20} /> <label className="flex flex-wrap items-center justify-start gap-3">
<span className="label-text font-semibold">{transI18n("cycleCount")}</span> <RefreshCw className="text-info" size={20} />
<input <span className="label-text font-semibold">{transI18n("cycleCount")}</span>
type="number" <input
className="input input-primary" type="number"
value={extraData?.theory_craft?.cycle_count} className="input input-primary"
onChange={(e) => value={extraData?.theory_craft?.cycle_count}
setExtraData({ onChange={(e) =>
...extraData, setExtraData({
theory_craft: { ...extraData,
hp: extraData?.theory_craft?.hp || {}, theory_craft: {
cycle_count: parseInt(e.target.value) || 1, hp: extraData?.theory_craft?.hp || {},
mode: extraData?.theory_craft?.mode || false cycle_count: Number(e.target.value) || 1,
} mode: extraData?.theory_craft?.mode || false,
}) stage_id: extraData?.theory_craft?.stage_id || 0
} }
min="1" })
/> }
</label> min="1"
</motion.div> />
</label>
</motion.div>
<motion.div className="form-control bg-base-200 p-4 rounded-xl shadow">
<label className="flex flex-wrap items-center justify-start gap-3">
<RouteIcon className="text-info" size={20} />
<span className="label-text font-semibold">{transI18n("stage")}</span>
<div className="relative">
<div className="flex items-center justify-center gap-2">
<button
className="btn btn-outline w-full text-left flex items-center gap-2"
onClick={(e) => {
e.stopPropagation()
if (!isChildClick) {
setShowSearchStage(pre => !pre)
}
setIsChildClick(false)
}}
>
<Search className="w-6 h-6" />
<span className="text-left"> {transI18n("stage")}: {stageList.find((s) => s.id === extraData?.theory_craft?.stage_id?.toString())?.name || transI18n("selectStage")}</span>
</button>
</div>
{showSearchStage && (
<div onClick={(e) => e.stopPropagation()} className="absolute top-full mt-2 w-full z-50 border bg-base-200 border-slate-600 rounded-lg p-4 shadow-lg">
<div className="flex items-center gap-2 mb-2">
<label className="input w-full">
<svg className="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g
strokeLinejoin="round"
strokeLinecap="round"
strokeWidth="2.5"
fill="none"
stroke="currentColor"
>
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</g>
</svg>
<input
type="search" className="grow"
placeholder={transI18n("searchStage")}
value={stageSearchTerm}
onChange={(e) => onChangeSearch(e.target.value)}
autoFocus
/>
</label>
</div>
<div className="max-h-60 overflow-y-auto space-y-1">
{paginatedStages.length > 0 ? (
<>
{paginatedStages.map((stage) => (
<div
key={stage.id}
className="p-2 hover:bg-primary/20 rounded cursor-pointer"
onClick={(e) => {
e.stopPropagation()
setIsChildClick(true)
if (extraData?.theory_craft?.stage_id !== Number(stage.id)) {
setExtraData({
...extraData,
theory_craft: {
stage_id: Number(stage.id),
cycle_count: extraData?.theory_craft?.cycle_count || 1,
mode: extraData?.theory_craft?.mode || false,
hp: extraData?.theory_craft?.hp || {}
}
})
}
setShowSearchStage(pre => !pre)
onChangeSearch("")
}}
>
{stage.name}
</div>
))}
</>
) : (
<div className="text-sm text-center py-4">{transI18n("noStageFound")}</div>
)}
</div>
{paginatedStages.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<button
disabled={stagePage === 1}
className="btn btn-sm btn-outline btn-success mt-2"
onClick={(e) => {
e.stopPropagation()
setStagePage(stagePage - 1)
}}
>
{transI18n("previous")}
</button>
<button
disabled={!hasMorePages}
className="btn btn-sm btn-outline btn-success mt-2"
onClick={(e) => {
e.stopPropagation()
setStagePage(stagePage + 1)
}}
>
{transI18n("next")}
</button>
</div>
)}
</div>
)}
</div>
</label>
</motion.div>
</>
)} )}
</div> </div>
)} )}
@@ -100,7 +246,7 @@ export default function ExtraSettingBar() {
excludeSet={[]} excludeSet={[]}
selectedCustomSet={extraData?.multi_path?.main?.toString() || ""} selectedCustomSet={extraData?.multi_path?.main?.toString() || ""}
placeholder={transI18n("selectAMainStat")} placeholder={transI18n("selectAMainStat")}
setSelectedCustomSet={(it) => { setSelectedCustomSet={(it) => {
if (!it) return if (!it) return
setExtraData({ setExtraData({
...extraData, ...extraData,
@@ -130,7 +276,7 @@ export default function ExtraSettingBar() {
excludeSet={[]} excludeSet={[]}
selectedCustomSet={extraData?.multi_path?.march_7?.toString() || ""} selectedCustomSet={extraData?.multi_path?.march_7?.toString() || ""}
placeholder={transI18n("selectAMainStat")} placeholder={transI18n("selectAMainStat")}
setSelectedCustomSet={(it) => { setSelectedCustomSet={(it) => {
if (!it) return if (!it) return
setExtraData({ setExtraData({
...extraData, ...extraData,

View File

@@ -1,6 +1,5 @@
"use client"; "use client";
import { useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { import {
Plus, Plus,
Trash2, Trash2,
@@ -88,7 +87,7 @@ export default function CeBar() {
) )
useEffect(() => { useEffect(() => {
setMonsterPage(1) // reset về trang 1 khi searchTerm thay đổi setMonsterPage(1)
}, [searchTerm]) }, [searchTerm])
const stageList = useMemo(() => Object.values(Stage).map((stage) => ({ const stageList = useMemo(() => Object.values(Stage).map((stage) => ({
@@ -146,12 +145,11 @@ export default function CeBar() {
<div className="mb-4 w-full relative"> <div className="mb-4 w-full relative">
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<button <button
className="btn btn-outline w-full text-left flex items-center gap-2" className="btn btn-outline w-full text-left flex items-center gap-2"
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
setShowSearchStage(true) setShowSearchStage(!showSearchStage)
}} }}
> >
<Search className="w-6 h-6" /> <Search className="w-6 h-6" />

View File

@@ -67,7 +67,7 @@ export default function SelectCustomImage({ customSet, excludeSet, selectedCusto
} }
const formatOptionLabel = (option: SelectOption) => ( const formatOptionLabel = (option: SelectOption) => (
<div className="flex items-center gap-1 w-full h-full z-50"> <div className="flex items-center gap-1 w-full h-full">
<Image src={option.imageUrl} alt="" width={125} height={125} className="w-8 h-8 object-contain bg-warning-content rounded-full" /> <Image src={option.imageUrl} alt="" width={125} height={125} className="w-8 h-8 object-contain bg-warning-content rounded-full" />
<ParseText className='font-bold' text={option.label} locale={locale} /> <ParseText className='font-bold' text={option.label} locale={locale} />
</div> </div>

View File

@@ -67,7 +67,7 @@ export default function SelectCustomText({ customSet, excludeSet, selectedCustom
const formatOptionLabel = (option: SelectOption) => ( const formatOptionLabel = (option: SelectOption) => (
<div className="flex flex-col gap-1 w-full h-full z-50"> <div className="flex flex-col gap-1 w-full h-full">
<div <div
className="font-bold text-lg" className="font-bold text-lg"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{

View File

@@ -3,6 +3,7 @@ export interface ExtraData {
hp: Record<string, number[]> hp: Record<string, number[]>
cycle_count: number cycle_count: number
mode: boolean mode: boolean
stage_id: number
} }
setting?: { setting?: {
censorship: boolean censorship: boolean

View File

@@ -5,7 +5,8 @@ export const extraDataSchema = z.object({
theory_craft: z.object({ theory_craft: z.object({
hp: z.record(z.string(), z.array(z.number())), hp: z.record(z.string(), z.array(z.number())),
cycle_count: z.number(), cycle_count: z.number(),
mode: z.boolean() mode: z.boolean(),
stage_id: z.number()
}), }),
setting: z.object({ setting: z.object({
censorship: z.boolean(), censorship: z.boolean(),