fix: keep Holo-work rows stable until sorting is reapplied
Publish GitHub Pages output / publish (push) Successful in 31s
Publish GitHub Pages output / publish (push) Successful in 31s
This commit is contained in:
+7
-1
@@ -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일
|
||||
|
||||
### 홀로워크 관리
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
@@ -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<T extends { id: string; order: number }>(
|
||||
members: readonly T[], counts: Readonly<Record<string, number>>, 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;
|
||||
});
|
||||
}
|
||||
@@ -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 @@
|
||||
<h2 class="all-members-title">멤버별 기록</h2>
|
||||
<div class="filters">
|
||||
<label>멤버 검색<input type="search" bind:value={search} placeholder="멤버 이름 입력" /></label>
|
||||
<label>정렬<select bind:value={sort}><option value="member">멤버순</option><option value="asc">횟수 적은 순</option><option value="desc">횟수 많은 순</option></select></label>
|
||||
<label>정렬<select value={sort} on:change={(event) => applySort(event.currentTarget.value as HoloWorkSort)}><option value="member">멤버순</option><option value="asc">횟수 적은 순</option><option value="desc">횟수 많은 순</option></select></label>
|
||||
</div>
|
||||
<div class="sort-actions"><p class="hint">횟수를 변경해도 위치는 유지됩니다. 정렬 기준을 바꾸거나 다시 정렬하면 최신 횟수를 반영합니다.</p><button type="button" on:click={() => applySort()} disabled={!ready}>다시 정렬</button></div>
|
||||
<ul aria-label="멤버별 Holo워크 횟수">
|
||||
{#each visibleMembers as member (member.id)}
|
||||
<li>
|
||||
@@ -253,6 +261,9 @@
|
||||
.summary strong { display: block; margin-top: 10px; color: #f3b4d0; font-size: 26px; overflow-wrap: anywhere; }
|
||||
small { margin-left: 7px; color: #a4adba; font-size: 13px; font-weight: 400; }
|
||||
.filters { display: grid; grid-template-columns: 1fr 150px; gap: 12px; }
|
||||
.sort-actions { display: flex; align-items: center; gap: 12px; justify-content: space-between; margin-top: 12px; }
|
||||
.sort-actions .hint { margin: 0; }
|
||||
.sort-actions button { flex-shrink: 0; padding: 8px 12px; font-size: 13px; }
|
||||
label { display: grid; gap: 8px; color: #a4adba; font-size: 13px; }
|
||||
input, select { box-sizing: border-box; min-width: 0; width: 100%; height: 44px; padding: 0 12px; border: 1px solid #414b5b; border-radius: 9px; background: #101319; color: #e8ebf1; font: inherit; }
|
||||
.status { min-height: 20px; color: #f3b4d0; font-size: 13px; }
|
||||
|
||||
Reference in New Issue
Block a user