mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
fix(manacore): improve dashboard layout polish
- Remove unnecessary wrapper div in WidgetContainer - Increase grid gap from gap-4 to gap-5 for breathing room - Add auto-rows-fr for equal row heights - Add min-h on widget content so empty widgets aren't tiny - Change default layout to 3 equal columns (small) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
02215dfb12
commit
61c23d5e79
45 changed files with 185 additions and 100 deletions
|
|
@ -94,9 +94,7 @@ export class AchievementService implements OnModuleInit {
|
|||
}
|
||||
|
||||
async getStats(userId: string): Promise<{ total: number; unlocked: number }> {
|
||||
const [totalResult] = await this.db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(achievements);
|
||||
const [totalResult] = await this.db.select({ count: sql<number>`count(*)` }).from(achievements);
|
||||
|
||||
const [unlockedResult] = await this.db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
|
|
@ -113,7 +111,10 @@ export class AchievementService implements OnModuleInit {
|
|||
* Check all achievements for a user and unlock any newly earned ones.
|
||||
* Called after XP gain, skill creation, activity logging, etc.
|
||||
*/
|
||||
async checkAndUnlock(userId: string, context?: { activityXp?: number }): Promise<AchievementUnlockResult[]> {
|
||||
async checkAndUnlock(
|
||||
userId: string,
|
||||
context?: { activityXp?: number }
|
||||
): Promise<AchievementUnlockResult[]> {
|
||||
const allAchievements = await this.db.select().from(achievements);
|
||||
const unlocked = await this.db
|
||||
.select()
|
||||
|
|
|
|||
|
|
@ -22,11 +22,15 @@ function calculateLevel(xp: number): number {
|
|||
export class SkillService {
|
||||
constructor(
|
||||
@Inject(DATABASE_TOKEN) private db: Database,
|
||||
private readonly achievementService: AchievementService,
|
||||
private readonly achievementService: AchievementService
|
||||
) {}
|
||||
|
||||
async findAll(userId: string): Promise<Skill[]> {
|
||||
return this.db.select().from(skills).where(eq(skills.userId, userId)).orderBy(desc(skills.totalXp));
|
||||
return this.db
|
||||
.select()
|
||||
.from(skills)
|
||||
.where(eq(skills.userId, userId))
|
||||
.orderBy(desc(skills.totalXp));
|
||||
}
|
||||
|
||||
async findByBranch(userId: string, branch: string): Promise<Skill[]> {
|
||||
|
|
@ -109,7 +113,12 @@ export class SkillService {
|
|||
id: string,
|
||||
userId: string,
|
||||
dto: AddXpDto
|
||||
): Promise<{ skill: Skill; leveledUp: boolean; newLevel: number; newAchievements: AchievementUnlockResult[] }> {
|
||||
): Promise<{
|
||||
skill: Skill;
|
||||
leveledUp: boolean;
|
||||
newLevel: number;
|
||||
newAchievements: AchievementUnlockResult[];
|
||||
}> {
|
||||
const skill = await this.findByIdOrThrow(id, userId);
|
||||
|
||||
const newTotalXp = skill.totalXp + dto.xp;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ COPY packages/shared-utils ./packages/shared-utils
|
|||
COPY packages/shared-error-tracking ./packages/shared-error-tracking
|
||||
COPY packages/shared-vite-config ./packages/shared-vite-config
|
||||
COPY packages/shared-api-client ./packages/shared-api-client
|
||||
COPY packages/shared-app-onboarding ./packages/shared-app-onboarding
|
||||
|
||||
# Copy skilltree web
|
||||
COPY apps/skilltree/apps/web ./apps/skilltree/apps/web
|
||||
|
|
|
|||
|
|
@ -74,7 +74,11 @@
|
|||
|
||||
<!-- XP reward + unlock date -->
|
||||
<div class="mt-2 flex items-center gap-3 text-xs">
|
||||
<span class="flex items-center gap-1 {achievement.unlocked ? 'text-yellow-400' : 'text-gray-500'}">
|
||||
<span
|
||||
class="flex items-center gap-1 {achievement.unlocked
|
||||
? 'text-yellow-400'
|
||||
: 'text-gray-500'}"
|
||||
>
|
||||
<Star class="h-3 w-3" />
|
||||
+{achievement.xpReward} XP
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@
|
|||
</div>
|
||||
|
||||
<!-- Achievement unlocked text -->
|
||||
<h2 class="mb-1 text-2xl font-bold text-yellow-400 achievement-text">Achievement freigeschaltet!</h2>
|
||||
<h2 class="mb-1 text-2xl font-bold text-yellow-400 achievement-text">
|
||||
Achievement freigeschaltet!
|
||||
</h2>
|
||||
|
||||
<!-- Achievement name -->
|
||||
<p class="mb-2 text-xl font-semibold text-white">{result.achievement.name}</p>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,10 @@
|
|||
</div>
|
||||
|
||||
<!-- Achievements -->
|
||||
<a href="/achievements" class="rounded-xl border border-gray-700 bg-gray-800/50 p-4 transition-colors hover:border-yellow-600/50 hover:bg-yellow-900/10">
|
||||
<a
|
||||
href="/achievements"
|
||||
class="rounded-xl border border-gray-700 bg-gray-800/50 p-4 transition-colors hover:border-yellow-600/50 hover:bg-yellow-900/10"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-yellow-500/20">
|
||||
<Medal class="h-6 w-6 text-yellow-500" />
|
||||
|
|
@ -74,7 +77,9 @@
|
|||
<div>
|
||||
<p class="text-sm text-gray-400">Achievements</p>
|
||||
<p class="text-2xl font-bold text-white">
|
||||
{achievementStore.stats().unlocked}<span class="text-sm font-normal text-gray-500">/{achievementStore.stats().total}</span>
|
||||
{achievementStore.stats().unlocked}<span class="text-sm font-normal text-gray-500"
|
||||
>/{achievementStore.stats().total}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -257,7 +257,9 @@ export async function saveAchievement(achievement: AchievementWithStatus): Promi
|
|||
await db.put('achievements', achievement);
|
||||
}
|
||||
|
||||
export async function saveAllAchievements(achievementsList: AchievementWithStatus[]): Promise<void> {
|
||||
export async function saveAllAchievements(
|
||||
achievementsList: AchievementWithStatus[]
|
||||
): Promise<void> {
|
||||
const db = await getDB();
|
||||
const tx = db.transaction('achievements', 'readwrite');
|
||||
await tx.objectStore('achievements').clear();
|
||||
|
|
|
|||
|
|
@ -199,22 +199,14 @@ async function checkLocal(context: {
|
|||
unlockedAt: new Date().toISOString(),
|
||||
progress: condition.threshold,
|
||||
};
|
||||
achievements = [
|
||||
...achievements.slice(0, i),
|
||||
unlocked,
|
||||
...achievements.slice(i + 1),
|
||||
];
|
||||
achievements = [...achievements.slice(0, i), unlocked, ...achievements.slice(i + 1)];
|
||||
await storage.saveAchievement(unlocked);
|
||||
newlyUnlocked.push({ achievement: a, xpReward: a.xpReward });
|
||||
} else {
|
||||
// Update progress
|
||||
const updated = { ...a, progress: Math.min(current, condition.threshold) };
|
||||
if (updated.progress !== a.progress) {
|
||||
achievements = [
|
||||
...achievements.slice(0, i),
|
||||
updated,
|
||||
...achievements.slice(i + 1),
|
||||
];
|
||||
achievements = [...achievements.slice(0, i), updated, ...achievements.slice(i + 1)];
|
||||
await storage.saveAchievement(updated);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,9 @@
|
|||
>
|
||||
<Trophy class="h-5 w-5" />
|
||||
{#if achievementStore.stats().unlocked > 0}
|
||||
<span class="absolute -right-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-yellow-500 text-[10px] font-bold text-gray-900">
|
||||
<span
|
||||
class="absolute -right-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-yellow-500 text-[10px] font-bold text-gray-900"
|
||||
>
|
||||
{achievementStore.stats().unlocked}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
@ -359,8 +361,5 @@
|
|||
{/if}
|
||||
|
||||
{#if showAchievementCelebration && currentAchievementUnlock}
|
||||
<AchievementCelebration
|
||||
result={currentAchievementUnlock}
|
||||
onClose={showNextAchievement}
|
||||
/>
|
||||
<AchievementCelebration result={currentAchievementUnlock} onClose={showNextAchievement} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,7 @@
|
|||
import { ACHIEVEMENT_CATEGORY_INFO, RARITY_INFO } from '$lib/types';
|
||||
import type { AchievementCategory } from '$lib/types';
|
||||
import AchievementCard from '$lib/components/AchievementCard.svelte';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Trophy,
|
||||
Star,
|
||||
} from '@manacore/shared-icons';
|
||||
import { ArrowLeft, Trophy, Star } from '@manacore/shared-icons';
|
||||
|
||||
let selectedCategory = $state<AchievementCategory | 'all'>('all');
|
||||
let showOnlyUnlocked = $state(false);
|
||||
|
|
@ -23,7 +19,10 @@
|
|||
return list.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
});
|
||||
|
||||
const categoryEntries = Object.entries(ACHIEVEMENT_CATEGORY_INFO) as [AchievementCategory, { name: string; icon: string }][];
|
||||
const categoryEntries = Object.entries(ACHIEVEMENT_CATEGORY_INFO) as [
|
||||
AchievementCategory,
|
||||
{ name: string; icon: string },
|
||||
][];
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen">
|
||||
|
|
@ -60,7 +59,9 @@
|
|||
<div class="mb-8 rounded-xl border border-gray-700 bg-gray-800/50 p-6">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h2 class="text-lg font-semibold text-white">Fortschritt</h2>
|
||||
<span class="text-2xl font-bold text-yellow-400">{achievementStore.completionPercentage()}%</span>
|
||||
<span class="text-2xl font-bold text-yellow-400"
|
||||
>{achievementStore.completionPercentage()}%</span
|
||||
>
|
||||
</div>
|
||||
<div class="h-3 overflow-hidden rounded-full bg-gray-700">
|
||||
<div
|
||||
|
|
@ -70,7 +71,9 @@
|
|||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-4 text-sm">
|
||||
{#each Object.entries(RARITY_INFO) as [rarity, info]}
|
||||
{@const count = achievementStore.achievements.filter((a) => a.rarity === rarity && a.unlocked).length}
|
||||
{@const count = achievementStore.achievements.filter(
|
||||
(a) => a.rarity === rarity && a.unlocked
|
||||
).length}
|
||||
{@const total = achievementStore.achievements.filter((a) => a.rarity === rarity).length}
|
||||
<span class="flex items-center gap-1.5 {info.color}">
|
||||
<Star class="h-3 w-3" />
|
||||
|
|
|
|||
1
apps/skilltree/apps/web/src/routes/offline/+page.ts
Normal file
1
apps/skilltree/apps/web/src/routes/offline/+page.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const prerender = true;
|
||||
Loading…
Add table
Add a link
Reference in a new issue