FIX: Monster bar
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m38s

This commit is contained in:
2025-07-25 11:30:19 +07:00
parent 13d27bb014
commit a649ec14ac
19 changed files with 159 additions and 38 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 KiB

View File

@@ -21,7 +21,34 @@ const geistMono = Geist_Mono({
export const metadata: Metadata = {
title: "Firefly Analytics",
description: "Analytics tool for Veritas",
};
icons: {
icon: "/ff-sranalysis.png",
shortcut: "/ff-sranalysis.ico",
apple: "/ff-sranalysis.png",
},
openGraph: {
title: "Firefly Analytics",
description: "Analytics tool for Veritas",
url: "https://sranalysis.kain.id.vn",
siteName: "Firefly Analytics",
images: [
{
url: "https://sranalysis.kain.id.vn/ff-sranalysis.png",
width: 1200,
height: 630,
alt: "Firefly Analytics Logo",
},
],
locale: "en_US",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Firefly Analytics",
description: "Analytics tool for Veritas",
images: ["https://sranalysis.kain.id.vn/ff-sranalysis.png"],
},
};
export default async function RootLayout({
children,

View File

@@ -3,7 +3,7 @@ import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import ActionBar from "@/components/actionbar";
import useAvatarDataStore from "@/stores/avatarDataStore";
import { getCharacterListApi } from "@/lib/api";
import { getCharacterListApi, getEnemyListApi } from "@/lib/api";
import LineupBar from "@/components/lineupbar";
import useBattleDataStore from "@/stores/battleDataStore";
import DamagePerAvatarForAll from "@/components/chart/damagePerAvatarForAll";
@@ -14,7 +14,7 @@ import EnemyBar from "@/components/enemybar";
export default function Home() {
const transI18n = useTranslations("DataAnalysisPage");
const { setListAvatar } = useAvatarDataStore();
const { setListAvatar, setListEnemy } = useAvatarDataStore();
const {
totalAV,
totalDamage,
@@ -36,9 +36,11 @@ export default function Home() {
const fetchData = async () => {
const data = await getCharacterListApi();
setListAvatar(data);
const enemyData = await getEnemyListApi();
setListEnemy(enemyData);
};
fetchData();
}, [setListAvatar]);
}, [setListAvatar, setListEnemy]);
useEffect(() => {
window.dispatchEvent(new Event('resize'));

View File

@@ -2,15 +2,16 @@
import useBattleDataStore from "@/stores/battleDataStore";
import Image from "next/image";
import useAvatarDataStore from "@/stores/avatarDataStore";
import { getNameEnemy } from "@/helper/getNameChar";
function formatEnemyIdForURL(id?: number): string {
const n = id ?? 0;
const adjusted = n.toString().length === 9 ? n / 100 : n;
return adjusted.toFixed(0);
}
import useLocaleStore from "@/stores/localeStore";
import NameAvatar from "../nameAvatar";
export default function EnemyBar() {
const { enemyDetail } = useBattleDataStore()
const { listEnemy } = useAvatarDataStore()
const { locale } = useLocaleStore()
return (
<div className="p-3 w-full">
@@ -21,16 +22,18 @@ export default function EnemyBar() {
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Image
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/Monster_${formatEnemyIdForURL(enemy.id)}.webp`}
src={`https://api.hakush.in/hsr/UI/monstermiddleicon/${listEnemy.find((monster) => monster.child.includes(enemy.id))?.icon?.split("/")?.pop()?.replace(".png", "")}.webp`}
alt={enemy.name}
width={40}
height={40}
className="object-cover w-10 h-10 rounded-lg"
/>
<div className="flex-1 min-w-0">
<h3 className="text-base font-semibold leading-tight truncate overflow-hidden" title={enemy.name}>
{enemy.name}
</h3>
<NameAvatar
text={getNameEnemy(locale, listEnemy.find((monster) => monster.child.includes(enemy.id)))}
locale={locale}
className="text-base font-semibold leading-tight truncate overflow-hidden"
/>
<p className="text-base-content/70 text-xs">Level {enemy.level || 1}</p>
</div>
</div>

View File

@@ -12,6 +12,7 @@ import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import Image from "next/image";
const themes = [
{ label: "Winter" },
@@ -211,23 +212,28 @@ export default function Header() {
{/* Logo */}
<a className="hidden sm:grid sm:grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
<h1 className="text-xl font-bold">
<span className="text-emerald-500">Firefly Analy</span>
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
sis
</span>
</h1>
<p className="text-sm text-gray-500">For Veritas</p>
<div className="flex items-center justify-center">
<Image src="/ff-sranalysis.png" alt="Logo" width={50} height={50} />
<div className="flex flex-col justify-center items-start">
<h1 className="text-xl font-bold">
<span className="text-emerald-500">Firefly Analy</span>
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
sis
</span>
</h1>
<p className="text-sm text-gray-500">For Veritas</p>
</div>
</div>
</a>
{version && (
<div className="px-2">
<div className="inline-flex items-center space-x-2 px-3 py-1.5 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full shadow-md hover:shadow-lg transition-all duration-200">
<div className="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></div>
<div className="text-xs font-semibold text-white">
{version}
</div>
</div>
</div>
<div className="px-2">
<div className="inline-flex items-center space-x-2 px-3 py-1.5 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full shadow-md hover:shadow-lg transition-all duration-200">
<div className="w-1.5 h-1.5 bg-white rounded-full animate-pulse"></div>
<div className="text-xs font-semibold text-white">
{version}
</div>
</div>
</div>
)}
</div>

View File

@@ -1,5 +1,5 @@
import { listCurrentLanguage } from "@/lib/constant";
import { AvatarHakushiType } from "@/types";
import { AvatarHakushiType, EnemyHakushiType } from "@/types";
export function getNameChar(locale: string, data: AvatarHakushiType | undefined): string {
@@ -22,6 +22,21 @@ export function getNameChar(locale: string, data: AvatarHakushiType | undefined)
return text
}
export function getNameEnemy(locale: string, data: EnemyHakushiType | undefined): string {
if (!data) {
return ""
}
if (!listCurrentLanguage.hasOwnProperty(locale)) {
return ""
}
let text = data.lang.get(listCurrentLanguage[locale as keyof typeof listCurrentLanguage].toLowerCase()) ?? "";
if (!text) {
text = data.lang.get("en") ?? "";
}
return text
}
export function parseRuby(text: string): string {
const rubyRegex = /\{RUBY_B#(.*?)\}(.*?)\{RUBY_E#\}/gs;
return text.replace(rubyRegex, (_match, furigana, kanji) => {

View File

@@ -1,4 +1,5 @@
import useSocketStore from "@/stores/socketSettingStore";
import { EnemyHakushiRawType, EnemyHakushiType } from "@/types";
import { AvatarHakushiType, AvatarHakushiRawType } from "@/types/avatar";
import axios from 'axios';
@@ -46,6 +47,29 @@ export async function getCharacterListApi(): Promise<AvatarHakushiType[]> {
}
}
export async function getEnemyListApi(): Promise<EnemyHakushiType[]> {
try {
const res = await axios.get<Record<string, EnemyHakushiRawType>>(
'https://api.hakush.in/hsr/data/monster.json',
{
headers: {
'Content-Type': 'application/json',
},
}
);
const data = new Map(Object.entries(res.data));
return Array.from(data.entries()).map(([id, it]) => convertMonster(id, it));
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
console.log(`Error: ${error.response?.status} - ${error.message}`);
} else {
console.log(`Unexpected error: ${String(error)}`);
}
return [];
}
}
function convertAvatar(id: string, item: AvatarHakushiRawType): AvatarHakushiType {
const lang = new Map<string, string>([
@@ -66,5 +90,26 @@ function convertAvatar(id: string, item: AvatarHakushiRawType): AvatarHakushiTyp
id: id
};
return result;
}
export function convertMonster(id: string, item: EnemyHakushiRawType): EnemyHakushiType {
const lang = new Map<string, string>([
['en', item.en],
['kr', item.kr],
['cn', item.cn],
['jp', item.jp]
]);
const result: EnemyHakushiType = {
id: id,
rank: item.rank,
camp: item.camp,
icon: item.icon,
child: item.child,
weak: item.weak,
desc: item.desc,
lang: lang
};
return result;
}

View File

@@ -1,16 +1,19 @@
import { AvatarHakushiType } from '@/types';
import { AvatarHakushiType, EnemyHakushiType } from '@/types';
import { create } from 'zustand'
interface AvatarDataState {
listAvatar: AvatarHakushiType[];
listEnemy: EnemyHakushiType[];
setListAvatar: (list: AvatarHakushiType[]) => void;
setListEnemy: (list: EnemyHakushiType[]) => void;
}
const useAvatarDataStore = create<AvatarDataState>((set) => ({
listAvatar: [],
listEnemy: [],
setListAvatar: (list: AvatarHakushiType[]) => set({ listAvatar: list }),
setListEnemy: (list: EnemyHakushiType[]) => set({ listEnemy: list }),
}));
export default useAvatarDataStore;

View File

@@ -1,4 +1,4 @@
import { DamageType, AvatarAnalysisJson, UseSkillType, BattleBeginType, BattleEndType, DamageDetailType, EntityDefeatedType, SetBattleLineupType, TurnBeginType, TurnEndType, UpdateCycleType, UpdateWaveType, VersionType, StatType, StatChangeType, UpdateTeamFormationType } from '@/types';
import { DamageType, AvatarAnalysisJson, UseSkillType, BattleBeginType, BattleEndType, DamageDetailType, EntityDefeatedType, SetBattleLineupType, TurnBeginType, TurnEndType, UpdateCycleType, UpdateWaveType, VersionType, StatChangeType, UpdateTeamFormationType } from '@/types';
import { InitializeEnemyType } from '@/types/enemy';
import { AvatarBattleInfo, AvatarInfo, BattleDataStateJson, EnemyInfo, SkillBattleInfo, TurnBattleInfo } from '@/types/mics';
import { create } from 'zustand'

View File

@@ -9,4 +9,28 @@ export interface EnemyType {
export interface InitializeEnemyType {
enemy: EnemyType
}
}
export interface EnemyHakushiRawType {
rank: string;
camp: string | null;
icon: string;
child: number[];
weak: string[];
en: string;
desc: string;
kr: string;
cn: string;
jp: string;
}
export interface EnemyHakushiType {
id: string;
rank: string;
camp: string | null;
icon: string;
child: number[];
weak: string[];
desc: string;
lang: Map<string, string>;
}

View File

@@ -9,3 +9,4 @@ export * from "./srtools"
export * from "./version"
export * from "./entity"
export * from "./stat"
export * from "./enemy"