add orphaned item indicators

shows warning badge + dimmed styling when collection items get deleted
party banner warns if any items are orphaned
This commit is contained in:
Justin Edmund 2025-12-23 22:39:39 -08:00
parent d0592935be
commit daa104cd99
5 changed files with 148 additions and 2 deletions

View file

@ -316,6 +316,14 @@
// Check if syncing is in progress
const isSyncingAll = $derived(syncAllItems.isPending)
// Check if any items in the party are orphaned (linked collection item was deleted)
const hasOrphanedItems = $derived.by(() => {
const hasOrphanedWeapons = (party?.weapons ?? []).some((w) => w?.orphaned)
const hasOrphanedCharacters = (party?.characters ?? []).some((c) => c?.orphaned)
const hasOrphanedSummons = (party?.summons ?? []).some((s) => s?.orphaned)
return hasOrphanedWeapons || hasOrphanedCharacters || hasOrphanedSummons
})
function handleTabChange(tab: GridType) {
activeTab = tab // Instant UI update
@ -941,6 +949,13 @@
{/snippet}
</PartyInfoGrid>
{#if hasOrphanedItems}
<div class="orphan-warning" role="alert">
<Icon name="alertTriangle" size={16} />
<span>Some items in this party are no longer in your collection and may have outdated data.</span>
</div>
{/if}
<PartySegmentedControl
selectedTab={activeTab}
onTabChange={handleTabChange}
@ -1149,4 +1164,21 @@
background: color.adjust($error, $lightness: -10%);
}
}
.orphan-warning {
display: flex;
align-items: center;
gap: $unit;
padding: $unit $unit-2x;
background: rgba(209, 137, 58, 0.15);
border: 1px solid rgba(209, 137, 58, 0.4);
border-radius: $unit-half;
color: #c47a1a;
font-size: $font-small;
:global(svg) {
flex-shrink: 0;
color: #c47a1a;
}
}
</style>

View file

@ -176,7 +176,7 @@
}
</script>
<div class="unit {elementClass}" class:empty={!item} class:is-active={isActive}>
<div class="unit {elementClass}" class:empty={!item} class:is-active={isActive} class:orphaned={item?.orphaned}>
{#if item}
<UnitMenuContainer showGearButton={true}>
{#snippet trigger()}
@ -218,6 +218,11 @@
title="Perpetuity Ring"
/>
{/if}
{#if item?.orphaned}
<div class="orphaned-badge" title="This item is no longer in your collection">
<Icon name="alertTriangle" size={16} />
</div>
{/if}
{#if imageUrl}
<img
class="image {elementClass}"
@ -617,4 +622,34 @@
color: colors.$grey-40;
}
}
.orphaned-badge {
position: absolute;
top: 4px;
right: 4px;
width: 24px;
height: 24px;
background: #d13a3a;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
z-index: 10;
pointer-events: auto;
cursor: help;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
// Orphaned state
.unit.orphaned {
.frame {
opacity: 0.7;
border: 2px solid #d13a3a;
}
.name {
color: #d13a3a;
}
}
</style>

View file

@ -114,7 +114,7 @@
</script>
<div class="unit {elementClass}" class:empty={!item} class:is-active={isActive}>
<div class="unit {elementClass}" class:empty={!item} class:is-active={isActive} class:orphaned={item?.orphaned}>
{#if item}
<UnitMenuContainer showGearButton={true}>
{#snippet trigger()}
@ -129,6 +129,11 @@
class:is-active={isActive}
onclick={() => viewDetails()}
>
{#if item?.orphaned}
<div class="orphaned-badge" title="This item is no longer in your collection">
<Icon name="alertTriangle" size={16} />
</div>
{/if}
<img
class="image {elementClass}"
class:placeholder={!item?.summon?.granblueId}
@ -346,6 +351,36 @@
color: var(--text-secondary);
}
.orphaned-badge {
position: absolute;
top: 4px;
right: 4px;
width: 24px;
height: 24px;
background: #d13a3a;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
z-index: 10;
pointer-events: auto;
cursor: help;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
// Orphaned state
.unit.orphaned {
.frame {
opacity: 0.7;
border: 2px solid #d13a3a;
}
.name {
color: #d13a3a;
}
}
// Pulsing focus ring animation
@keyframes pulse-focus-ring {
0%, 100% {

View file

@ -152,6 +152,7 @@
class:empty={!item}
class:extra={position >= 9}
class:is-active={isActive}
class:orphaned={item?.orphaned}
>
{#if item}
<UnitMenuContainer showGearButton={true}>
@ -173,6 +174,11 @@
onclick={() => viewDetails()}
>
<div class="modifiers">
{#if item?.orphaned}
<div class="orphaned-badge" title="This item is no longer in your collection">
<Icon name="alertTriangle" size={16} />
</div>
{/if}
{#if awakeningImage}
<img
class="awakening"
@ -448,6 +454,24 @@
z-index: 3;
pointer-events: none;
.orphaned-badge {
position: absolute;
top: 4px;
right: 4px;
width: 24px;
height: 24px;
background: #d13a3a;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
z-index: 10;
pointer-events: auto;
cursor: help;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.awakening {
position: absolute;
width: 30%;
@ -467,6 +491,18 @@
}
}
// Orphaned state
.unit.orphaned {
.frame {
opacity: 0.7;
border: 2px solid #d13a3a;
}
.name {
color: #d13a3a;
}
}
// Position modifiers for grid weapons
.frame.weapon.cell {
.awakening {

View file

@ -38,6 +38,8 @@ export interface GridWeapon {
collectionWeaponId?: string
/** Whether the grid item is out of sync with its collection source */
outOfSync?: boolean
/** Whether the linked collection item has been deleted (item is orphaned) */
orphaned?: boolean
}
// GridCharacter from GridCharacterBlueprint
@ -60,6 +62,8 @@ export interface GridCharacter {
collectionCharacterId?: string
/** Whether the grid item is out of sync with its collection source */
outOfSync?: boolean
/** Whether the linked collection item has been deleted (item is orphaned) */
orphaned?: boolean
}
// GridSummon from GridSummonBlueprint
@ -76,6 +80,8 @@ export interface GridSummon {
collectionSummonId?: string
/** Whether the grid item is out of sync with its collection source */
outOfSync?: boolean
/** Whether the linked collection item has been deleted (item is orphaned) */
orphaned?: boolean
}
// JobSkillList for party job skills
@ -115,6 +121,8 @@ export interface Party {
extra?: boolean
remix?: boolean
editKey?: string
/** Whether the party contains any orphaned grid items */
hasOrphanedItems?: boolean
// Relationships
weapons: GridWeapon[]