177 lines
5.1 KiB
Vue
177 lines
5.1 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed } from "vue"
|
|
import ProfileLayout from "@/Components/FlightsGoneBy/ProfileLayout.vue"
|
|
import ProfileViewSwitcher from "@/Components/FlightsGoneBy/ProfileViewSwitcher.vue"
|
|
import AchievementCard from "@/Components/FlightsGoneBy/AchievementCard.vue"
|
|
import {Achievement, SharedProps, User, UserAchievement} from "@/Types/types"
|
|
import {Head, usePage} from "@inertiajs/vue3";
|
|
import MainLayout from "@/Layouts/MainLayout.vue";
|
|
import Panel from "@/Components/FlightsGoneBy/Panels/Panel.vue";
|
|
import PanelHeader from "@/Components/FlightsGoneBy/Panels/PanelHeader.vue";
|
|
|
|
defineOptions({ layout: MainLayout })
|
|
|
|
const props = defineProps<{
|
|
user: User
|
|
canEdit: boolean
|
|
isFollowing: boolean
|
|
achievements: Record<string, Achievement[]>
|
|
userAchievements: Record<number, UserAchievement>
|
|
unlockedCount: number
|
|
totalAchievements: number
|
|
unlockedByCategory: Record<string, number>
|
|
}>()
|
|
|
|
const page = usePage<SharedProps>().props
|
|
|
|
const hideImpossible = ref(false)
|
|
|
|
const filteredAchievements = computed(() => {
|
|
if (!hideImpossible.value) return props.achievements
|
|
|
|
return Object.fromEntries(
|
|
Object.entries(props.achievements).map(([category, achievements]) => [
|
|
category,
|
|
achievements.filter(a => a.difficulty?.internal_name !== 'impossible')
|
|
])
|
|
)
|
|
})
|
|
|
|
const filteredUnlockedByCategory = computed(() =>
|
|
Object.fromEntries(
|
|
Object.entries(filteredAchievements.value).map(([category, achievements]) => [
|
|
category,
|
|
achievements.filter(a => props.userAchievements[a.id]?.unlocked).length
|
|
])
|
|
)
|
|
)
|
|
|
|
const filteredTotal = computed(() =>
|
|
Object.values(filteredAchievements.value).reduce((sum, group) => sum + group.length, 0)
|
|
)
|
|
|
|
const filteredUnlockedCount = computed(() =>
|
|
Object.values(filteredUnlockedByCategory.value).reduce((sum, count) => sum + count, 0)
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<Head :title="`${user.name}'s Achievements`" />
|
|
<ProfileLayout
|
|
:user="user"
|
|
:achievementCount="unlockedCount"
|
|
:is-following="isFollowing"
|
|
:loading="false"
|
|
>
|
|
<ProfileViewSwitcher active-view="achievements" :user="user" />
|
|
|
|
<div class="achievements-page">
|
|
<div class="achievements-summary">
|
|
<div class="summary-controls">
|
|
<span class="summary-text">
|
|
{{ filteredUnlockedCount }} / {{ filteredTotal }} achievements unlocked
|
|
</span>
|
|
<v-checkbox
|
|
v-model="hideImpossible"
|
|
label="Hide Impossible Achievements"
|
|
density="compact"
|
|
hide-details
|
|
color="amber"
|
|
/>
|
|
</div>
|
|
<v-progress-linear
|
|
:model-value="Math.round((filteredUnlockedCount / filteredTotal) * 100)"
|
|
color="amber"
|
|
rounded
|
|
height="6"
|
|
bg-color="rgba(255,255,255,0.1)"
|
|
/>
|
|
</div>
|
|
|
|
<Panel
|
|
v-for="(categoryAchievements, categoryName) in filteredAchievements"
|
|
:key="categoryName"
|
|
class="achievement-category"
|
|
>
|
|
<PanelHeader class="category-header">{{ categoryName }}
|
|
<span class="category-count">
|
|
{{ filteredUnlockedByCategory[categoryName] ?? 0 }}
|
|
/ {{ categoryAchievements.length }}
|
|
</span>
|
|
</PanelHeader>
|
|
|
|
<div class="achievement-grid">
|
|
<AchievementCard
|
|
:user="user"
|
|
v-for="achievement in categoryAchievements"
|
|
:key="achievement.id"
|
|
:achievement="achievement"
|
|
:user-achievement="userAchievements[achievement.id]"
|
|
:distance-unit="page.auth?.user?.resolved_settings?.distance_unit"
|
|
/>
|
|
</div>
|
|
</Panel>
|
|
</div>
|
|
</ProfileLayout>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.achievements-page {
|
|
width: 100%;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 1.5rem 1rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2.5rem;
|
|
}
|
|
|
|
.achievements-summary {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.summary-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.summary-text {
|
|
font-size: 0.9rem;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.achievement-category {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.category-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
.category-count {
|
|
font-size: 0.85rem;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.achievement-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.achievement-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|