From 84ebb288c9fd65a0d9e59ea45ef73885f31a8288 Mon Sep 17 00:00:00 2001 From: Hyunseong0303 Date: Mon, 2 Mar 2026 15:15:30 +0900 Subject: [PATCH 1/6] feat: implement scroll restoration and remove redundant frontend sorting --- dpbr_front/app/src/routes/+page.svelte | 42 ++++++++-- .../app/src/routes/member/[id]/+page.svelte | 78 +++++++++++-------- 2 files changed, 82 insertions(+), 38 deletions(-) diff --git a/dpbr_front/app/src/routes/+page.svelte b/dpbr_front/app/src/routes/+page.svelte index 4f552d3..cc3bb23 100644 --- a/dpbr_front/app/src/routes/+page.svelte +++ b/dpbr_front/app/src/routes/+page.svelte @@ -5,6 +5,7 @@ import CharacterCard from "$lib/components/CharacterCard.svelte"; import { getCharactersPaginated } from "$lib/api"; import type { Character } from "$lib/types"; + import type { Snapshot } from "./$types"; let sidebarOpen = $state(false); let characters = $state([]); @@ -12,9 +13,26 @@ let loadingMore = $state(false); let hasMore = $state(true); let error = $state(null); - let page = 1; + let page = $state(1); const limit = 12; let sentinel = $state(null); + let scrollContainer = $state(null); + let restoredScrollTop = 0; + + export const snapshot: Snapshot = { + capture: () => ({ + characters, + page, + hasMore, + scrollTop: scrollContainer?.scrollTop ?? 0, + }), + restore: (value) => { + characters = value.characters; + page = value.page; + hasMore = value.hasMore; + restoredScrollTop = value.scrollTop; + }, + }; async function loadMoreCharacters() { if (loadingMore || !hasMore) return; @@ -28,7 +46,8 @@ characters = [...characters, ...result.items]; } - hasMore = characters.length < result.total && result.items.length > 0; + hasMore = + characters.length < result.total && result.items.length > 0; if (result.items.length > 0) { page += 1; } @@ -43,7 +62,14 @@ } onMount(() => { - void loadMoreCharacters(); + if (characters.length === 0) { + void loadMoreCharacters(); + } else { + loading = false; + if (scrollContainer && restoredScrollTop > 0) { + scrollContainer.scrollTop = restoredScrollTop; + } + } }); $effect(() => { @@ -78,7 +104,10 @@
(sidebarOpen = true)} /> -
+
{#if loading}

로딩 중...

@@ -94,7 +123,10 @@ {/each}
{#if hasMore} -
+
{#if loadingMore}

불러오는 중...

{/if} diff --git a/dpbr_front/app/src/routes/member/[id]/+page.svelte b/dpbr_front/app/src/routes/member/[id]/+page.svelte index a8aa245..9c1105b 100644 --- a/dpbr_front/app/src/routes/member/[id]/+page.svelte +++ b/dpbr_front/app/src/routes/member/[id]/+page.svelte @@ -17,6 +17,7 @@ SettlementItem, TeamMessageItem, } from "$lib/types"; + import type { Snapshot } from "./$types"; const ADMIN_TEAM_INFO = { generation: "13기", @@ -47,15 +48,49 @@ let settlements = $state([]); let settlementsLoadingMore = $state(false); let settlementsHasMore = $state(false); - let settlementsPage = 1; + let settlementsPage = $state(1); const settlementsLimit = 10; let settlementsSentinel = $state(null); let teamMessages = $state([]); let loading = $state(true); let error = $state(null); + let scrollContainer = $state(null); + let restoredScrollTop = 0; + let restoredCharacterId = ""; + + export const snapshot: Snapshot = { + capture: () => ({ + character, + settlements, + settlementsPage, + settlementsHasMore, + teamMessages, + scrollTop: scrollContainer?.scrollTop ?? 0, + charId: characterId, + }), + restore: (value) => { + character = value.character; + settlements = value.settlements; + settlementsPage = value.settlementsPage; + settlementsHasMore = value.settlementsHasMore; + teamMessages = value.teamMessages; + restoredScrollTop = value.scrollTop; + restoredCharacterId = value.charId; + }, + }; + $effect(() => { // characterId가 변경될 때마다 데이터 로드 + if (restoredCharacterId === characterId && character) { + loading = false; + if (scrollContainer && restoredScrollTop > 0) { + scrollContainer.scrollTop = restoredScrollTop; + } + // Reset restored ID after use to allow normal loads if ID changes later + restoredCharacterId = ""; + return; + } loadData(); }); @@ -76,21 +111,8 @@ } else { character = fallbackAdminCharacter; } - const members = await getTeamMembers(); - const rolePriority: Record = { - 인사팀원: 1, - 행사팀원: 2, - 홍보팀원: 3, - 인사팀장: 4, - 행사팀장: 5, - 홍보팀장: 6, - 회장: 7, - }; - teamMessages = members.sort( - (a, b) => - (rolePriority[a.role] || 99) - - (rolePriority[b.role] || 99), - ); + teamMessages = await getTeamMembers(); + settlements = []; settlementsHasMore = false; return; @@ -106,21 +128,8 @@ } if (charData.name === ADMIN_TEAM_NAME) { - const members = await getTeamMembers(); - const rolePriority: Record = { - 인사팀원: 1, - 행사팀원: 2, - 홍보팀원: 3, - 인사팀장: 4, - 행사팀장: 5, - 홍보팀장: 6, - 회장: 7, - }; - teamMessages = members.sort( - (a, b) => - (rolePriority[a.role] || 99) - - (rolePriority[b.role] || 99), - ); + teamMessages = await getTeamMembers(); + settlements = []; settlementsHasMore = false; } else { @@ -214,10 +223,13 @@
goto("/")} + onBackClick={() => history.back()} /> -
+
Date: Mon, 2 Mar 2026 15:22:43 +0900 Subject: [PATCH 2/6] fix: use tick() for more stable scroll restoration --- dpbr_front/app/src/routes/+page.svelte | 5 +++-- dpbr_front/app/src/routes/member/[id]/+page.svelte | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/dpbr_front/app/src/routes/+page.svelte b/dpbr_front/app/src/routes/+page.svelte index cc3bb23..bdba938 100644 --- a/dpbr_front/app/src/routes/+page.svelte +++ b/dpbr_front/app/src/routes/+page.svelte @@ -1,5 +1,5 @@ {#if variant === "main"} @@ -38,12 +65,19 @@ draggable="false" /> - 단풍바람 + import { toast } from "$lib/stores/toast"; import { fade, slide } from "svelte/transition"; + + let defaultToasts = $derived($toast.filter((t) => t.type !== "center")); + let centerToasts = $derived($toast.filter((t) => t.type === "center")); +
- {#each $toast as { id, message } (id)} + {#each defaultToasts as { id, message } (id)}
+ {message} +
+ {/each} +
+ + +
+ {#each centerToasts as { id, message } (id)} +
{message}
diff --git a/dpbr_front/app/src/lib/stores/toast.ts b/dpbr_front/app/src/lib/stores/toast.ts index 618f55a..a0f3774 100644 --- a/dpbr_front/app/src/lib/stores/toast.ts +++ b/dpbr_front/app/src/lib/stores/toast.ts @@ -3,6 +3,7 @@ import { writable } from 'svelte/store'; export interface ToastMessage { id: number; message: string; + type?: 'default' | 'center'; } function createToastStore() { @@ -12,9 +13,9 @@ function createToastStore() { return { subscribe, - show: (message: string, duration = 3000) => { + show: (message: string, duration = 3000, type: 'default' | 'center' = 'default') => { const id = nextId++; - update((toasts) => [...toasts, { id, message }]); + update((toasts) => [...toasts, { id, message, type }]); setTimeout(() => { update((toasts) => toasts.filter((t) => t.id !== id)); From 9c2a0364c1bf39d6ec22b7d36f4aed6c991dd38c Mon Sep 17 00:00:00 2001 From: Hyunseong0303 Date: Mon, 2 Mar 2026 15:29:19 +0900 Subject: [PATCH 4/6] style: adjust easter egg toast duration and font size --- dpbr_front/app/src/lib/components/Header.svelte | 2 +- dpbr_front/app/src/lib/components/Toast.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dpbr_front/app/src/lib/components/Header.svelte b/dpbr_front/app/src/lib/components/Header.svelte index 393e394..b2d9312 100644 --- a/dpbr_front/app/src/lib/components/Header.svelte +++ b/dpbr_front/app/src/lib/components/Header.svelte @@ -41,7 +41,7 @@ "\nBackend Engineer\n배승민 서민성 이서윤", "\nServer Engineer\n김형규", ].join("\n"); - toast.show(credits, 5000, "center"); + toast.show(credits, 2500, "center"); logoClickCount = 0; } } diff --git a/dpbr_front/app/src/lib/components/Toast.svelte b/dpbr_front/app/src/lib/components/Toast.svelte index 554b0b2..a1d4db1 100644 --- a/dpbr_front/app/src/lib/components/Toast.svelte +++ b/dpbr_front/app/src/lib/components/Toast.svelte @@ -29,7 +29,7 @@
{message}
From b04163d02a1bc83597549297e0009f4e951ff538 Mon Sep 17 00:00:00 2001 From: Hyunseong0303 Date: Mon, 2 Mar 2026 15:31:11 +0900 Subject: [PATCH 5/6] style: revert easter egg duration to 5s --- dpbr_front/app/src/lib/components/Header.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpbr_front/app/src/lib/components/Header.svelte b/dpbr_front/app/src/lib/components/Header.svelte index b2d9312..393e394 100644 --- a/dpbr_front/app/src/lib/components/Header.svelte +++ b/dpbr_front/app/src/lib/components/Header.svelte @@ -41,7 +41,7 @@ "\nBackend Engineer\n배승민 서민성 이서윤", "\nServer Engineer\n김형규", ].join("\n"); - toast.show(credits, 2500, "center"); + toast.show(credits, 5000, "center"); logoClickCount = 0; } } From 1b5e861020d94a0b9aeb8460648cb49e5ea46565 Mon Sep 17 00:00:00 2001 From: Hyunseong0303 Date: Mon, 2 Mar 2026 15:33:28 +0900 Subject: [PATCH 6/6] fix: prevent multiple easter egg toasts from stacking --- dpbr_front/app/src/lib/components/Header.svelte | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dpbr_front/app/src/lib/components/Header.svelte b/dpbr_front/app/src/lib/components/Header.svelte index 393e394..df609d8 100644 --- a/dpbr_front/app/src/lib/components/Header.svelte +++ b/dpbr_front/app/src/lib/components/Header.svelte @@ -23,8 +23,11 @@ let logoClickCount = $state(0); let lastLogoClickTime = 0; + let isEasterEggActive = false; function handleLogoClick() { + if (isEasterEggActive) return; + const now = Date.now(); if (now - lastLogoClickTime < 500) { logoClickCount += 1; @@ -34,6 +37,7 @@ lastLogoClickTime = now; if (logoClickCount === 3) { + isEasterEggActive = true; const credits = [ "Fullstack DevOps & PM\n강민", "\nDesigner & PM\n강민아", @@ -43,6 +47,10 @@ ].join("\n"); toast.show(credits, 5000, "center"); logoClickCount = 0; + + setTimeout(() => { + isEasterEggActive = false; + }, 5000); } }