diff --git a/UPDATES.md b/UPDATES.md index 04df312..520d5c0 100644 --- a/UPDATES.md +++ b/UPDATES.md @@ -1,7 +1,13 @@ --- -updatedAt: 2026-09-12T00:00:00+09:00 +updatedAt: 2026-09-14T00:00:00+09:00 --- +## 2026년 9월 14일 + +### 홀로워크 기록 정렬 개선 + +`IMPROVED` 횟수 변경 중 멤버 행이 이동하지 않도록 목록 순서를 유지합니다. 정렬 기준을 바꾸거나 ‘다시 정렬’을 누르면 최신 횟수로 정렬하며, 검색 중에도 전체 목록의 정렬 순서를 유지합니다. + ## 2026년 9월 12일 ### 홀로워크 관리 diff --git a/src/lib/engine/holo-work-sort.test.ts b/src/lib/engine/holo-work-sort.test.ts new file mode 100644 index 0000000..260b595 --- /dev/null +++ b/src/lib/engine/holo-work-sort.test.ts @@ -0,0 +1,23 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { sortHoloWorkMembers } from '../models/holo-work-sort.ts'; + +const members = [{ id: 'a', order: 1 }, { id: 'b', order: 2 }, { id: 'c', order: 3 }]; +const ids = (rows: typeof members) => rows.map((row) => row.id); + +test('횟수 변경은 기존 순서를 유지하고 다시 정렬할 때 최신 횟수를 반영한다', () => { + const counts = { a: 1, b: 2, c: 3 }; + const snapshot = sortHoloWorkMembers(members, counts, 'asc'); + counts.a = 5; + assert.deepEqual(ids(snapshot), ['a', 'b', 'c']); + assert.deepEqual(ids(sortHoloWorkMembers(members, counts, 'asc')), ['b', 'c', 'a']); + assert.deepEqual(ids(sortHoloWorkMembers(members, counts, 'desc')), ['a', 'c', 'b']); + assert.deepEqual(ids(members), ['a', 'b', 'c']); +}); + +test('미등록 횟수는 0이며 동률은 멤버순, 기준 변경은 전체 목록에 적용한다', () => { + const counts = { a: 2, b: 2 }; + assert.deepEqual(ids(sortHoloWorkMembers(members, counts, 'asc')), ['c', 'a', 'b']); + assert.deepEqual(ids(sortHoloWorkMembers(members, counts, 'desc')), ['a', 'b', 'c']); + assert.deepEqual(ids(sortHoloWorkMembers([...members].reverse(), counts, 'member')), ['a', 'b', 'c']); +}); diff --git a/src/lib/models/holo-work-sort.ts b/src/lib/models/holo-work-sort.ts new file mode 100644 index 0000000..cb12c6a --- /dev/null +++ b/src/lib/models/holo-work-sort.ts @@ -0,0 +1,11 @@ +export type HoloWorkSort = 'member' | 'asc' | 'desc'; + +/** A fresh ordering snapshot; subsequent count edits do not move its rows. */ +export function sortHoloWorkMembers( + members: readonly T[], counts: Readonly>, sort: HoloWorkSort +): T[] { + return [...members].sort((a, b) => { + const difference = (counts[a.id] ?? 0) - (counts[b.id] ?? 0); + return (sort === 'asc' ? difference : sort === 'desc' ? -difference : 0) || a.order - b.order; + }); +} diff --git a/src/routes/tools/holo-work/+page.svelte b/src/routes/tools/holo-work/+page.svelte index ac8acbe..87b7d6c 100644 --- a/src/routes/tools/holo-work/+page.svelte +++ b/src/routes/tools/holo-work/+page.svelte @@ -8,6 +8,7 @@ import boardSummary from '$lib/data/boards-summary.json'; import { adjustWorkTeam, isWorkCount, parseWorkTeams, type HoloWorkCounts } from '$lib/storage/holo-work'; import type { HoloWorkTeam } from '$lib/models/user-data'; + import { sortHoloWorkMembers, type HoloWorkSort } from '$lib/models/holo-work-sort'; import { USER_DATA_STORAGE_KEY, loadBrowserData, saveBrowserData } from '$lib/storage/browser-storage'; import { defaultPreferences } from '$lib/models/default-preferences'; @@ -22,14 +23,20 @@ $: validTeam = Boolean(teamName.trim()) && selectedMembers.every((id) => members.some((member) => member.id === id)) && new Set(selectedMembers).size === 5; let ready = false; let search = ''; - let sort = 'member'; + let sort: HoloWorkSort = 'member'; + let sortedMembers = [...members]; let status = ''; let error = ''; - $: visibleMembers = members.filter((member) => member.name.replace(/\s/g, '').toLocaleLowerCase().includes(search.replace(/\s/g, '').toLocaleLowerCase())) - .sort((a, b) => (sort === 'asc' ? (counts[a.id] ?? 0) - (counts[b.id] ?? 0) : sort === 'desc' ? (counts[b.id] ?? 0) - (counts[a.id] ?? 0) : 0) || a.order - b.order); + $: visibleMembers = sortedMembers.filter((member) => member.name.replace(/\s/g, '').toLocaleLowerCase().includes(search.replace(/\s/g, '').toLocaleLowerCase())); $: total = members.reduce((sum, member) => sum + BigInt(counts[member.id] ?? 0), 0n); $: recorded = members.filter((member) => (counts[member.id] ?? 0) > 0).length; + function applySort(nextSort: HoloWorkSort = sort) { + sort = nextSort; + sortedMembers = sortHoloWorkMembers(members, counts, sort); + status = '현재 횟수로 정렬했습니다.'; + } + function loadLatestData() { const data = loadBrowserData(defaultPreferences()); bonuses = calculateHoloWorkBoardBonuses(boardSummary, data.memberBoards, { @@ -224,8 +231,9 @@

멤버별 기록

- +
+

횟수를 변경해도 위치는 유지됩니다. 정렬 기준을 바꾸거나 다시 정렬하면 최신 횟수를 반영합니다.