Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions app2/src/lib/dashboard/components/Achievement.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ type Props = {
isCurrent?: boolean
isNext?: boolean
isCompleted?: boolean
isExpired?: boolean
}

const { achievement, userAchievements, isCurrent = false, isNext = false, isCompleted = false } =
$props()
const {
achievement,
userAchievements,
isCurrent = false,
isNext = false,
isCompleted = false,
isExpired = false,
} = $props()

let showXp = $state(false)

Expand All @@ -40,19 +47,36 @@ function getStatusColor() {
if (completed) {
return "text-accent"
}
if (isExpired && !completed) {
return "text-red-400"
}
if (isNext) {
return "text-zinc-400"
}
return "text-accent"
}

function getContainerClasses() {
let baseClasses =
"flex flex-col gap-3 p-3 rounded-lg transition-all duration-300 cursor-pointer relative w-full text-left"

if (isExpired && !completed) {
baseClasses += " opacity-60 grayscale"
}

if (isCurrent) {
baseClasses += " bg-zinc-800/50"
} else {
baseClasses += " hover:bg-zinc-800/30"
}

return baseClasses
}
</script>

<button
type="button"
class="
flex flex-col gap-3 p-3 rounded-lg transition-all duration-300 cursor-pointer relative w-full text-left
{isCurrent ? 'bg-zinc-800/50' : 'hover:bg-zinc-800/30'}
"
class={getContainerClasses()}
>
{#if !isCompleted && !isNext}
<div class="absolute -top-3 left-1/2 w-0.5 h-3 bg-zinc-700"></div>
Expand All @@ -68,11 +92,18 @@ function getStatusColor() {
{:else if isNext}
<MissionBoxIcon class="size-4 lg:size-5 text-zinc-400" />
{:else}
<MissionBoxIcon class="size-4 lg:size-5 text-accent" />
<MissionBoxIcon class="size-4 lg:size-5 {getStatusColor()}" />
{/if}
<h3 class="text-sm lg:text-base font-medium lg:font-bold group">
{achievement.title}
</h3>
{#if isExpired && !completed}
<span
class="px-1.5 py-0.5 text-[10px] font-semibold bg-red-500/20 text-red-400 border border-red-500/30 rounded-sm ml-2"
>
EXPIRED
</span>
{/if}
</div>
<div class="relative">
<div class="px-1.5 py-0.5 rounded-sm bg-zinc-800/80 border border-zinc-700/50 {showXp ? 'scale-110 border-accent/50' : ''} transition-all duration-300 flex items-center justify-center">
Expand Down
14 changes: 11 additions & 3 deletions app2/src/lib/dashboard/components/AchievementStats.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ let totalXP = $derived(
Option.flatMap(
achievements.available,
(availableAchievements) =>
Option.some(availableAchievements.reduce((sum: number, a: any) => sum + (a.xp || 0), 0)),
Option.some(
availableAchievements.filter(a => !achievements.isAchievementExpired(a)).reduce(
(sum: number, a: any) => sum + (a.xp || 0),
0,
),
),
),
),
)
Expand Down Expand Up @@ -59,14 +64,17 @@ let completedCount = $derived(
),
)

// Calculate total achievements count
// Calculate total achievements count (excluding expired ones)
let totalCount = $derived(
Option.flatMap(
dashboard.achievements,
(achievements) =>
Option.flatMap(
achievements.available,
(availableAchievements) => Option.some(availableAchievements.length),
(availableAchievements) =>
Option.some(
availableAchievements.filter(a => !achievements.isAchievementExpired(a)).length,
),
),
),
)
Expand Down
72 changes: 18 additions & 54 deletions app2/src/lib/dashboard/components/AchievementTabs.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -122,69 +122,24 @@ let achievementChains = $derived(
Option.flatMap(
dashboard.achievements,
(achievements) =>
Option.flatMap(achievements.available, (availableAchievements) => {
let filteredAchievements = availableAchievements
Option.flatMap(achievements.achievementByChain, (chains) => {
let filteredChains = chains

// Filter by category if selected
if (selectedCategory) {
filteredAchievements = filteredAchievements.filter(a =>
a.category?.title === selectedCategory
)
filteredChains = filteredChains.map(chain =>
chain.filter(a => a.category?.title === selectedCategory)
).filter(chain => chain.length > 0)
}

// Filter by subcategory if selected
if (selectedSubcategory) {
filteredAchievements = filteredAchievements.filter(a =>
a.subcategory?.title === selectedSubcategory
)
}

const achievementsMap = new Map(
filteredAchievements.map((achievement: AchievementType) => [achievement.id, achievement]),
)

// Helper function to find the first achievement in a chain
const findChainStart = (achievement: AchievementType): AchievementType => {
const previous = filteredAchievements.find((a: AchievementType) =>
a.next === achievement.id
)
return previous ? findChainStart(previous) : achievement
filteredChains = filteredChains.map(chain =>
chain.filter(a => a.subcategory?.title === selectedSubcategory)
).filter(chain => chain.length > 0)
}

const chains = filteredAchievements.reduce(
(chains: AchievementType[][], achievement: AchievementType) => {
// Skip if achievement is already in any chain
if (
chains.some((chain: AchievementType[]) =>
chain.some((a: AchievementType) => a.id === achievement.id)
)
) {
return chains
}

// Find the actual start of this chain
const chainStart = findChainStart(achievement)

// Build chain from the start
const chain: AchievementType[] = []
let current: AchievementType | null = chainStart

while (current) {
chain.push(current)
const nextId: number | null = current.next
const nextAchievement: AchievementType | null = nextId !== null
? achievementsMap.get(nextId) ?? null
: null
current = nextAchievement
}

chains.push(chain)
return chains
},
[],
)

return Option.some(chains)
return Option.some(filteredChains)
}),
),
)
Expand Down Expand Up @@ -488,6 +443,9 @@ let sortedChains = $derived(
achievement={achievement}
userAchievements={getAchievedAchievements(Option.getOrNull(dashboard.achievements))}
isCompleted={true}
isExpired={Option.getOrNull(dashboard.achievements)?.isAchievementExpired(
achievement,
) ?? false}
/>
{/each}
</div>
Expand All @@ -499,6 +457,9 @@ let sortedChains = $derived(
achievement={organizedChain.current}
userAchievements={getAchievedAchievements(Option.getOrNull(dashboard.achievements))}
isCurrent={true}
isExpired={Option.getOrNull(dashboard.achievements)?.isAchievementExpired(
organizedChain.current,
) ?? false}
/>
</div>
{/if}
Expand All @@ -510,6 +471,9 @@ let sortedChains = $derived(
achievement={achievement}
userAchievements={getAchievedAchievements(Option.getOrNull(dashboard.achievements))}
isNext={true}
isExpired={Option.getOrNull(dashboard.achievements)?.isAchievementExpired(
achievement,
) ?? false}
/>
{/each}
</div>
Expand Down
Loading