From b76a4a4e9c91fd45829044eb8396db380d7315f4 Mon Sep 17 00:00:00 2001 From: AzenKain Date: Sun, 5 Oct 2025 17:01:07 +0700 Subject: [PATCH] UPDATE: add more ui for showcase card, fix some bug --- src/components/avatarBar/index.tsx | 20 ++- src/components/importBar/copy.tsx | 39 +++--- src/components/lightconeBar/index.tsx | 21 ++- src/components/quickView/index.tsx | 127 ++++++++++-------- src/components/showcaseCard/index.tsx | 85 +++--------- src/components/showcaseCard/relicShowcase.tsx | 99 ++++++++++++++ src/stores/avatarStore.ts | 8 ++ src/stores/copyProfile.ts | 12 ++ src/stores/lightconeStore.ts | 9 ++ src/types/index.ts | 1 + src/types/showcase.tsx | 26 ++++ 11 files changed, 301 insertions(+), 146 deletions(-) create mode 100644 src/components/showcaseCard/relicShowcase.tsx create mode 100644 src/types/showcase.tsx diff --git a/src/components/avatarBar/index.tsx b/src/components/avatarBar/index.tsx index 90b555c..2954133 100644 --- a/src/components/avatarBar/index.tsx +++ b/src/components/avatarBar/index.tsx @@ -1,6 +1,6 @@ "use client" import Image from "next/image" -import { useEffect, useState } from "react" +import { useEffect } from "react" import CharacterCard from "../card/characterCard" import useLocaleStore from "@/stores/localeStore" import useAvatarStore from "@/stores/avatarStore" @@ -8,9 +8,17 @@ import { useTranslations } from "next-intl" export default function AvatarBar({ onClose }: { onClose?: () => void }) { - const [listElement, setListElement] = useState>({ "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false }) - const [listPath, setListPath] = useState>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false }) - const { listAvatar, setAvatarSelected, setSkillSelected, setFilter, filter } = useAvatarStore() + const { + listAvatar, + setAvatarSelected, + setSkillSelected, + setFilter, + filter, + listElement, + listPath, + setListElement, + setListPath + } = useAvatarStore() const transI18n = useTranslations("DataPage") const { locale } = useLocaleStore() @@ -40,7 +48,7 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
{ - setListElement((prev) => ({ ...prev, [key]: !prev[key] })) + setListElement({ ...listElement, [key]: !listElement[key] }) }} className="hover:bg-gray-600 grid items-center justify-items-center cursor-pointer rounded-md shadow-lg" style={{ @@ -60,7 +68,7 @@ export default function AvatarBar({ onClose }: { onClose?: () => void }) {
{ - setListPath((prev) => ({ ...prev, [key]: !prev[key] })) + setListPath({ ...listPath, [key]: !listPath[key] }) }} className="hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-lg cursor-pointer" style={{ diff --git a/src/components/importBar/copy.tsx b/src/components/importBar/copy.tsx index 66e0f48..cfcfac8 100644 --- a/src/components/importBar/copy.tsx +++ b/src/components/importBar/copy.tsx @@ -13,7 +13,7 @@ import SelectCustomImage from "../select/customSelectImage"; export default function CopyImport() { const { avatars, setAvatar } = useUserDataStore(); - const {avatarSelected} = useListAvatarStore() + const { avatarSelected } = useListAvatarStore() const { locale } = useLocaleStore() const { selectedProfiles, @@ -22,12 +22,16 @@ export default function CopyImport() { setSelectedProfiles, filterCopy, setFilterCopy, - setAvatarCopySelected + setAvatarCopySelected, + listElement, + listPath, + listRank, + setListElement, + setListPath, + setListRank } = useCopyProfileStore() const transI18n = useTranslations("DataPage") - const [listPath, setListPath] = useState>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false }) - const [listElement, setListElement] = useState>({ "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false }) - const [listRank, setListRank] = useState>({ "4": false, "5": false }) + const [message, setMessage] = useState({ type: "", text: "" @@ -49,8 +53,9 @@ export default function CopyImport() { element: Object.keys(listElement).filter((key) => listElement[key]), rarity: Object.keys(listRank).filter((key) => listRank[key]) }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [listPath, listRank, locale]) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [listPath, listRank, listElement, locale, setFilterCopy]) const clearSelection = () => { setSelectedProfiles([]); @@ -142,7 +147,7 @@ export default function CopyImport() {
{ - setListPath((prev) => ({ ...prev, [key]: !prev[key] })) + setListPath({ ...listPath, [key]: !listPath[key] }) }} className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer" style={{ @@ -167,7 +172,7 @@ export default function CopyImport() {
{ - setListElement((prev) => ({ ...prev, [key]: !prev[key] })) + setListElement({ ...listElement, [key]: !listElement[key] }) }} className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer" style={{ @@ -192,7 +197,7 @@ export default function CopyImport() {
{ - setListRank((prev) => ({ ...prev, [key]: !prev[key] })) + setListRank({ ...listRank, [key]: !listRank[key] }) }} className="w-[50px] h-[50px] hover:bg-gray-600 grid items-center justify-items-center rounded-md shadow-md cursor-pointer" style={{ @@ -207,9 +212,10 @@ export default function CopyImport() {
-
-
{transI18n("characterName")}
- {listCopyAvatar.length > 0 && ( + + {listCopyAvatar.length > 0 && ( +
+
{transI18n("characterName")}
({ value: avatar.id.toString(), @@ -221,8 +227,9 @@ export default function CopyImport() { placeholder="Character Select" setSelectedCustomSet={(value) => setAvatarCopySelected(listCopyAvatar.find((avatar) => avatar.id.toString() === value) || null)} /> - )} -
+
+ )} +
@@ -240,7 +247,7 @@ export default function CopyImport() { {transI18n("clearAll")} diff --git a/src/components/lightconeBar/index.tsx b/src/components/lightconeBar/index.tsx index 0db72ee..aeef447 100644 --- a/src/components/lightconeBar/index.tsx +++ b/src/components/lightconeBar/index.tsx @@ -1,6 +1,6 @@ "use client" -import { useEffect, useState } from "react" +import { useEffect } from "react" import Image from "next/image"; import useLocaleStore from "@/stores/localeStore" import useLightconeStore from "@/stores/lightconeStore"; @@ -11,10 +11,17 @@ import useModelStore from "@/stores/modelStore"; import { useTranslations } from "next-intl"; export default function LightconeBar() { - const [listPath, setListPath] = useState>({ "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false }) - const [listRank, setListRank] = useState>({ "3": false, "4": false, "5": false }) const { locale } = useLocaleStore() - const { listLightcone, filter, setFilter, defaultFilter } = useLightconeStore() + const { + listLightcone, + filter, + setFilter, + defaultFilter, + listPath, + listRank, + setListPath, + setListRank + } = useLightconeStore() const { setAvatar, avatars } = useUserDataStore() const { avatarSelected } = useAvatarStore() const { setIsOpenLightcone } = useModelStore() @@ -35,7 +42,7 @@ export default function LightconeBar() { } setListPath(newListPath) setListRank(newListRank) - }, [defaultFilter]) + }, [defaultFilter, setListPath, setListRank]) useEffect(() => { setFilter({ @@ -72,7 +79,7 @@ export default function LightconeBar() {
{ - setListPath((prev) => ({ ...prev, [key]: !prev[key] })) + setListPath({ ...listPath, [key]: !listPath[key] }) }} className="h-[38px] w-[38px] md:h-[50px] md:w-[50px] hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer" style={{ @@ -96,7 +103,7 @@ export default function LightconeBar() {
{ - setListRank((prev) => ({ ...prev, [key]: !prev[key] })) + setListRank({ ...listRank, [key]: !listRank[key] }) }} className="h-[38px] w-[38px] md:h-[50px] md:w-[50px] hover:bg-gray-600 grid place-items-center rounded-md shadow-lg cursor-pointer" style={{ diff --git a/src/components/quickView/index.tsx b/src/components/quickView/index.tsx index e3726e8..626aec5 100644 --- a/src/components/quickView/index.tsx +++ b/src/components/quickView/index.tsx @@ -9,6 +9,7 @@ import { useMemo } from "react"; import useAvatarStore from "@/stores/avatarStore"; import { calcAffixBonus, calcBaseStatRaw, calcBonusStatRaw, calcMainAffixBonus, calcMainAffixBonusRaw, calcPromotion, calcSubAffixBonusRaw, replaceByParam } from "@/helper"; import { mappingStats } from "@/constant/constant"; +import RelicShowcase from "../showcaseCard/relicShowcase"; export default function QuickView() { const { avatarSelected, mapAvatarInfo } = useAvatarStore() const { mapLightconeInfo } = useLightconeStore() @@ -80,7 +81,9 @@ export default function QuickView() { return { property: subAffixMap?.[subValue?.sub_affix_id]?.property, valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count), - detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property] + detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property], + step: subValue?.step, + count: subValue?.count } }) } @@ -398,7 +401,7 @@ export default function QuickView() {
- {stat.name} +
{stat.name}
{ @@ -439,63 +442,79 @@ export default function QuickView() {
- {relicStats?.map((relic, index) => { + {relicStats?.map((relic, index) => { if (!relic) return null return ( -
-
- +
+ {/* Subtle glow overlay */} +
-
- ✦✦✦✦✦ -
-
-
- - {relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit} - +{relic?.mainAffix?.level} -
-
-
- {relic?.subAffix?.map((subAffix, index) => { - if (!subAffix) return null - return ( -
-
- {subAffix?.detail?.icon ? ( - - ) : ( -
- ? -
- )} - +{subAffix?.valueAffix + subAffix?.detail?.unit} -
-
- ) - })} -
+
+
+ + +
+ ✦✦✦✦✦ +
+ +
+
+
+ +
+ + {relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit} + + + +{relic?.mainAffix?.level} + +
+ +
+ +
+ {relic?.subAffix?.map((subAffix, index) => { + if (!subAffix) return null + return ( + + ) + })} +
+
) - })} - {(!relicStats || !relicStats?.length) &&
- {transI18n("noRelicEquipped")} -
} + })} + + {(!relicStats || !relicStats?.length) && ( +
+
+ {transI18n("noRelicEquipped")} +
+
+ )}
) diff --git a/src/components/showcaseCard/index.tsx b/src/components/showcaseCard/index.tsx index ea9386d..80a1201 100644 --- a/src/components/showcaseCard/index.tsx +++ b/src/components/showcaseCard/index.tsx @@ -16,6 +16,8 @@ import { useTranslations } from 'next-intl'; import useAffixStore from '@/stores/affixStore'; import useRelicStore from '@/stores/relicStore'; import { toast } from 'react-toastify'; +import RelicShowcase from './relicShowcase'; + export default function ShowCaseInfo() { const { avatarSelected, mapAvatarInfo } = useAvatarStore() @@ -37,9 +39,9 @@ export default function ShowCaseInfo() { import("html2canvas-pro") .then(({ default: html2canvas }) => - html2canvas(cardRef.current!, { - scale: 2, - backgroundColor: "#000000", + html2canvas(cardRef.current!, { + scale: 2, + backgroundColor: "#000000", logging: false, proxy: '/api/proxy/', imageTimeout: 30000, @@ -162,7 +164,9 @@ export default function ShowCaseInfo() { return { property: subAffixMap?.[subValue?.sub_affix_id]?.property, valueAffix: calcAffixBonus(subAffixMap?.[subValue?.sub_affix_id], subValue?.step, subValue?.count), - detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property] + detail: mappingStats?.[subAffixMap?.[subValue?.sub_affix_id]?.property], + step: subValue?.step, + count: subValue?.count } }) } @@ -495,7 +499,7 @@ export default function ShowCaseInfo() { }, [avatarSelected]) return ( -
+
@@ -503,7 +507,7 @@ export default function ShowCaseInfo() {
{ - return (
{item.map((btn, idx) => { @@ -803,7 +806,6 @@ export default function ShowCaseInfo() {
- {/* Chỉ số */}
@@ -872,7 +874,7 @@ export default function ShowCaseInfo() { }} />
- {setEffect.count} + {setEffect.count}
) @@ -881,65 +883,22 @@ export default function ShowCaseInfo() {
-
+
{relicStats?.map((relic, index) => { if (!relic) return null return ( -
-
- - -
- ✦✦✦✦✦ -
-
-
- - {relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit} - +{relic?.mainAffix?.level} -
-
-
- {relic?.subAffix?.map((subAffix, index) => { - if (!subAffix) return null - return ( -
-
- {subAffix?.detail?.icon ? ( - - ) : ( -
- ? -
- )} - +{subAffix?.valueAffix + subAffix?.detail?.unit} -
-
- ) - })} -
-
+ ) })} - {(!relicStats || !relicStats?.length) &&
- {transI18n("noRelicEquipped")} -
} + + {(!relicStats || !relicStats?.length) && ( +
+
+ {transI18n("noRelicEquipped")} +
+
+ )}
diff --git a/src/components/showcaseCard/relicShowcase.tsx b/src/components/showcaseCard/relicShowcase.tsx new file mode 100644 index 0000000..d6a9302 --- /dev/null +++ b/src/components/showcaseCard/relicShowcase.tsx @@ -0,0 +1,99 @@ + +"use client" + +import NextImage from "next/image" +import { RelicShowcaseType } from "@/types"; + +export default function RelicShowcase({ + relic, +}: { + relic: RelicShowcaseType; +}) { + return ( + <> +
+ {/* Subtle glow overlay */} +
+ +
+
+ + +
+ ✦✦✦✦✦ +
+
+ +
+
+
+ +
+ + {relic?.mainAffix?.valueAffix + relic?.mainAffix?.detail?.unit} + + + +{relic?.mainAffix?.level} + +
+ +
+ +
+ {relic?.subAffix?.map((subAffix, index) => { + if (!subAffix) return null + return ( +
+
+ {subAffix?.detail?.icon ? ( + + ) : ( +
+ ? +
+ )} + + +{subAffix?.valueAffix + subAffix?.detail?.unit} + + {subAffix.step > 1 && ( + + {subAffix?.step} + + )} +
+
+ ) + })} +
+
+ + ) +} \ No newline at end of file diff --git a/src/stores/avatarStore.ts b/src/stores/avatarStore.ts index ff2460a..60aea77 100644 --- a/src/stores/avatarStore.ts +++ b/src/stores/avatarStore.ts @@ -9,6 +9,10 @@ interface AvatarState { avatarSelected: CharacterBasic | null; mapAvatarInfo: Record; skillSelected: string | null; + listElement: Record; + listPath: Record; + setListElement: (newListElement: Record) => void; + setListPath: (newListPath: Record) => void; setListAvatar: (newListAvatar: CharacterBasic[]) => void; setAvatarSelected: (newAvatarSelected: CharacterBasic) => void; setFilter: (newFilter: FilterAvatarType) => void; @@ -30,6 +34,10 @@ const useAvatarStore = create((set, get) => ({ avatarSelected: null, skillSelected: null, mapAvatarInfo: {}, + listElement: { "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false }, + listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false }, + setListElement: (newListElement: Record) => set({ listElement: newListElement }), + setListPath: (newListPath: Record) => set({ listPath: newListPath }), setSkillSelected: (newSkillSelected: string | null) => set({ skillSelected: newSkillSelected }), setListAvatar: (newListAvatar: CharacterBasic[]) => set({ listAvatar: newListAvatar, listRawAvatar: newListAvatar }), setAvatarSelected: (newAvatarSelected: CharacterBasic) => set({ avatarSelected: newAvatarSelected }), diff --git a/src/stores/copyProfile.ts b/src/stores/copyProfile.ts index f890506..c7f471b 100644 --- a/src/stores/copyProfile.ts +++ b/src/stores/copyProfile.ts @@ -7,6 +7,12 @@ interface CopyProfileState { listRawCopyAvatar: CharacterBasic[]; filterCopy: FilterAvatarType; avatarCopySelected: CharacterBasic | null; + listElement: Record; + listPath: Record; + listRank: Record; + setListElement: (newListElement: Record) => void; + setListPath: (newListPath: Record) => void; + setListRank: (newListRank: Record) => void; setSelectedProfiles: (newListAvatar: AvatarProfileCardType[]) => void; setAvatarCopySelected: (newAvatarSelected: CharacterBasic | null) => void; setFilterCopy: (newFilter: FilterAvatarType) => void; @@ -25,6 +31,12 @@ const useCopyProfileStore = create((set, get) => ({ locale: "", }, avatarCopySelected: null, + listElement: { "fire": false, "ice": false, "imaginary": false, "physical": false, "quantum": false, "thunder": false, "wind": false }, + listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false }, + listRank: { "4": false, "5": false }, + setListElement: (newListElement: Record) => set({ listElement: newListElement }), + setListPath: (newListPath: Record) => set({ listPath: newListPath }), + setListRank: (newListRank: Record) => set({ listRank: newListRank }), setListCopyAvatar: (newListAvatar: CharacterBasic[]) => set({ listCopyAvatar: newListAvatar, listRawCopyAvatar: newListAvatar }), setAvatarCopySelected: (newAvatarSelected: CharacterBasic | null) => set({ avatarCopySelected: newAvatarSelected }), setFilterCopy: (newFilter: FilterAvatarType) => { diff --git a/src/stores/lightconeStore.ts b/src/stores/lightconeStore.ts index dc9fa08..76d2bc4 100644 --- a/src/stores/lightconeStore.ts +++ b/src/stores/lightconeStore.ts @@ -4,9 +4,13 @@ import { create } from 'zustand' interface LightconeState { listLightcone: LightConeBasic[]; listRawLightcone: LightConeBasic[]; + listPath: Record; + listRank: Record; filter: FilterLightconeType; defaultFilter: { path: string[], rarity: string[] }; mapLightconeInfo: Record; + setListPath: (newListPath: Record) => void; + setListRank: (newListRank: Record) => void; setDefaultFilter: (newDefaultFilter: { path: string[], rarity: string[] }) => void; setListLightcone: (newListLightcone: LightConeBasic[]) => void; setFilter: (newFilter: FilterLightconeType) => void; @@ -18,6 +22,7 @@ const useLightconeStore = create((set, get) => ({ listLightcone: [], listRawLightcone: [], mapLightconeInfo: {}, + filter: { name: "", path: [], @@ -25,6 +30,10 @@ const useLightconeStore = create((set, get) => ({ rarity: [], }, defaultFilter: { path: [], rarity: [] }, + listPath: { "knight": false, "mage": false, "priest": false, "rogue": false, "shaman": false, "warlock": false, "warrior": false, "memory": false }, + listRank: { "3": false, "4": false, "5": false }, + setListPath: (newListPath: Record) => set({ listPath: newListPath }), + setListRank: (newListRank: Record) => set({ listRank: newListRank }), setDefaultFilter: (newDefaultFilter: { path: string[], rarity: string[] }) => set({ defaultFilter: newDefaultFilter }), setListLightcone: (newListLightcone: LightConeBasic[]) => set({ listLightcone: newListLightcone, listRawLightcone: newListLightcone }), setFilter: (newFilter: FilterLightconeType) => { diff --git a/src/types/index.ts b/src/types/index.ts index 450cc04..3d7b56e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -20,3 +20,4 @@ export * from "./monsterValue" export * from "./peakDetail" export * from "./monsterDetail" export * from "./extraData" +export * from "./showcase" diff --git a/src/types/showcase.tsx b/src/types/showcase.tsx new file mode 100644 index 0000000..c3b0837 --- /dev/null +++ b/src/types/showcase.tsx @@ -0,0 +1,26 @@ +export type RelicShowcaseType = { + img: string; + mainAffix: { + property: string; + level: number; + valueAffix: string; + detail: { + name: string; + icon: string; + unit: string; + baseStat: string; + }; + }; + subAffix: { + property: string; + valueAffix: string; + detail: { + name: string; + icon: string; + unit: string; + baseStat: string; + }; + step: number; + count: number; + }[] +} \ No newline at end of file