feat(web): add inventory item detail/comparison panel

This commit is contained in:
Bastian Wagner
2026-08-20 17:39:46 +02:00
parent 10dd838465
commit 973d4e3ab4
4 changed files with 370 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import { Component, computed, input, output } from '@angular/core';
import type { EquipmentSlot, InventoryItem } from '../../core/api/game-api.models';
import { RARITY_LABELS } from '../../shared/item-card/item-card.component';
const SLOT_LABELS: Readonly<Record<EquipmentSlot, string>> = {
WEAPON: 'Waffe',
HEAD: 'Kopf',
CHEST: 'Brust',
HANDS: 'Handschuhe',
LEGS: 'Beine',
FEET: 'Stiefel',
AMULET: 'Amulett',
};
interface StatRow {
label: string;
value: number;
diff: number | null;
}
type StatKey = 'weaponDamage' | 'bonusAttack' | 'bonusHp' | 'bonusArmor';
const STAT_LABELS: ReadonlyArray<{ label: string; key: StatKey }> = [
{ label: 'Waffenschaden', key: 'weaponDamage' },
{ label: 'Angriff', key: 'bonusAttack' },
{ label: 'Leben', key: 'bonusHp' },
{ label: 'Rüstung', key: 'bonusArmor' },
];
/** Selected-item details and equip comparison (spec §3237). */
@Component({
selector: 'app-inventory-detail-panel',
templateUrl: './inventory-detail-panel.component.html',
styleUrl: './inventory-detail-panel.component.scss',
})
export class InventoryDetailPanelComponent {
readonly item = input<InventoryItem | null>(null);
readonly equippedItemInSlot = input<InventoryItem | null>(null);
readonly characterLevel = input(1);
readonly busy = input(false);
readonly equip = output<string>();
protected readonly rarityLabel = computed(() => {
const item = this.item();
return item ? RARITY_LABELS[item.item.rarity] : '';
});
protected readonly slotLabel = computed(() => {
const slot = this.item()?.item.equipmentSlot;
return slot ? SLOT_LABELS[slot] : null;
});
protected readonly statRows = computed<StatRow[]>(() => {
const item = this.item();
if (!item) {
return [];
}
const compareTo = this.equippedItemInSlot();
const comparable = compareTo && compareTo.id !== item.id ? compareTo.item : null;
return STAT_LABELS.map(({ label, key }) => ({
label,
value: item.item[key],
diff: comparable ? item.item[key] - comparable[key] : null,
})).filter((row) => row.value > 0 || (row.diff ?? 0) !== 0);
});
protected readonly isEquippable = computed(() => !!this.item()?.item.equipmentSlot);
protected readonly meetsLevelRequirement = computed(() => {
const item = this.item();
return item ? item.item.requiredLevel <= this.characterLevel() : true;
});
protected onEquip(): void {
const item = this.item();
if (item) {
this.equip.emit(item.id);
}
}
}