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'
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 { useTranslations } from "next-intl"
import useEventStore from "@/stores/eventStore"
@@ -8,6 +8,8 @@ import { getLocaleName, getNameChar } from "@/helper"
import useLocaleStore from "@/stores/localeStore"
import useAvatarStore from "@/stores/avatarStore"
import SelectCustomImage from "../select/customSelectImage"
import { useMemo, useState } from "react"
import useMazeStore from "@/stores/mazeStore"
export default function ExtraSettingBar() {
const { extraData, setExtraData } = useGlobalStore()
@@ -15,13 +17,39 @@ export default function ExtraSettingBar() {
const { PEAKEvent } = useEventStore()
const { listAvatar } = useAvatarStore()
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 (
<div className="px-4 sm:px-6 py-4 space-y-8">
{extraData?.theory_craft && (
<div className="space-y-4">
<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")}>
<Info className="text-primary" size={20} />
</div>
@@ -39,8 +67,9 @@ export default function ExtraSettingBar() {
...extraData,
theory_craft: {
hp: extraData?.theory_craft?.hp || {},
cycle_count: extraData?.theory_craft?.cycle_count || 0,
mode: e.target.checked
cycle_count: extraData?.theory_craft?.cycle_count || 1,
mode: e.target.checked,
stage_id: extraData?.theory_craft?.stage_id || 0
}
})
}
@@ -52,28 +81,145 @@ export default function ExtraSettingBar() {
</motion.div>
{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">
<RefreshCw className="text-info" size={20} />
<span className="label-text font-semibold">{transI18n("cycleCount")}</span>
<input
type="number"
className="input input-primary"
value={extraData?.theory_craft?.cycle_count}
onChange={(e) =>
setExtraData({
...extraData,
theory_craft: {
hp: extraData?.theory_craft?.hp || {},
cycle_count: parseInt(e.target.value) || 1,
mode: extraData?.theory_craft?.mode || false
}
})
}
min="1"
/>
</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">
<RefreshCw className="text-info" size={20} />
<span className="label-text font-semibold">{transI18n("cycleCount")}</span>
<input
type="number"
className="input input-primary"
value={extraData?.theory_craft?.cycle_count}
onChange={(e) =>
setExtraData({
...extraData,
theory_craft: {
hp: extraData?.theory_craft?.hp || {},
cycle_count: Number(e.target.value) || 1,
mode: extraData?.theory_craft?.mode || false,
stage_id: extraData?.theory_craft?.stage_id || 0
}
})
}
min="1"
/>
</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>
)}
@@ -100,7 +246,7 @@ export default function ExtraSettingBar() {
excludeSet={[]}
selectedCustomSet={extraData?.multi_path?.main?.toString() || ""}
placeholder={transI18n("selectAMainStat")}
setSelectedCustomSet={(it) => {
setSelectedCustomSet={(it) => {
if (!it) return
setExtraData({
...extraData,
@@ -130,7 +276,7 @@ export default function ExtraSettingBar() {
excludeSet={[]}
selectedCustomSet={extraData?.multi_path?.march_7?.toString() || ""}
placeholder={transI18n("selectAMainStat")}
setSelectedCustomSet={(it) => {
setSelectedCustomSet={(it) => {
if (!it) return
setExtraData({
...extraData,

View File

@@ -1,6 +1,5 @@
"use client";
import React, { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import {
Plus,
Trash2,
@@ -88,7 +87,7 @@ export default function CeBar() {
)
useEffect(() => {
setMonsterPage(1) // reset về trang 1 khi searchTerm thay đổi
setMonsterPage(1)
}, [searchTerm])
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="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()
setShowSearchStage(true)
setShowSearchStage(!showSearchStage)
}}
>
<Search className="w-6 h-6" />

View File

@@ -67,7 +67,7 @@ export default function SelectCustomImage({ customSet, excludeSet, selectedCusto
}
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" />
<ParseText className='font-bold' text={option.label} locale={locale} />
</div>

View File

@@ -67,7 +67,7 @@ export default function SelectCustomText({ customSet, excludeSet, selectedCustom
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
className="font-bold text-lg"
dangerouslySetInnerHTML={{

View File

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

View File

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