Skip to content

Commit 4384b29

Browse files
committed
feat: support checklist item sorting/reordering
1 parent 86a8bd3 commit 4384b29

14 files changed

Lines changed: 1256 additions & 47 deletions

File tree

lib/Controller/ChecklistController.php

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ public function deleteList(int $houseId, int $listId): DataResponse {
178178
*
179179
* @param int $houseId House id.
180180
* @param int $listId List id.
181+
* @param string $sortBy Sort mode (custom, newest, oldest, name_asc, name_desc).
181182
* @param int<1, 1000> $limit Maximum number of items to return.
182183
* @param int<0, max> $offset Number of items to skip.
183184
*
@@ -187,12 +188,12 @@ public function deleteList(int $houseId, int $listId): DataResponse {
187188
*/
188189
#[ApiRoute(verb: 'GET', url: '/api/houses/{houseId}/lists/{listId}/items')]
189190
#[NoAdminRequired]
190-
public function indexItems(int $houseId, int $listId, int $limit = 200, int $offset = 0): DataResponse {
191-
return $this->runAction(function () use ($houseId, $listId, $limit, $offset): DataResponse {
191+
public function indexItems(int $houseId, int $listId, string $sortBy = 'custom', int $limit = 200, int $offset = 0): DataResponse {
192+
return $this->runAction(function () use ($houseId, $listId, $sortBy, $limit, $offset): DataResponse {
192193
$this->auth->requireMember($houseId, $this->requireUid());
193194
$list = $this->lists->getList($listId);
194195
$this->assertListInHouse($list->getHouseId(), $houseId);
195-
$all = $this->lists->listItems($listId);
196+
$all = $this->lists->listItems($listId, $sortBy);
196197
$sliced = array_slice($all, max(0, $offset), max(0, $limit));
197198
$items = array_map(fn ($i) => $i->jsonSerialize(), $sliced);
198199
return new DataResponse($items);
@@ -385,6 +386,29 @@ public function deleteItem(int $houseId, int $listId, int $itemId): DataResponse
385386
});
386387
}
387388

389+
/**
390+
* Batch reorder items in a list
391+
*
392+
* @param int $houseId House id.
393+
* @param int $listId List id.
394+
* @param list<array{id: int, sortOrder: int}> $items Reorder entries.
395+
*
396+
* @return DataResponse<Http::STATUS_OK, PantrySuccess, array{}>
397+
*
398+
* 200: Items reordered
399+
*/
400+
#[ApiRoute(verb: 'POST', url: '/api/houses/{houseId}/lists/{listId}/items/reorder')]
401+
#[NoAdminRequired]
402+
public function reorderItems(int $houseId, int $listId, array $items = []): DataResponse {
403+
return $this->runAction(function () use ($houseId, $listId, $items): DataResponse {
404+
$this->auth->requireMember($houseId, $this->requireUid());
405+
$list = $this->lists->getList($listId);
406+
$this->assertListInHouse($list->getHouseId(), $houseId);
407+
$this->lists->reorderItems($listId, $items);
408+
return new DataResponse(['success' => true]);
409+
});
410+
}
411+
388412
/**
389413
* Upload an image for an item
390414
*

lib/Controller/PrefsController.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,48 @@ public function setNoteSort(int $houseId, string $sort): DataResponse {
219219
});
220220
}
221221

222+
/**
223+
* Get checklist item sort preference for a house
224+
*
225+
* @param int $houseId House id.
226+
*
227+
* @return DataResponse<Http::STATUS_OK, array{sort: string}, array{}>
228+
*
229+
* 200: Sort preference returned
230+
*/
231+
#[ApiRoute(verb: 'GET', url: '/api/houses/{houseId}/prefs/checklist-item-sort')]
232+
#[NoAdminRequired]
233+
public function getChecklistItemSort(int $houseId): DataResponse {
234+
return $this->runAction(function () use ($houseId): DataResponse {
235+
$uid = $this->requireUid();
236+
$this->auth->requireMember($houseId, $uid);
237+
return new DataResponse([
238+
'sort' => $this->prefs->getChecklistItemSort($uid, $houseId),
239+
]);
240+
});
241+
}
242+
243+
/**
244+
* Set checklist item sort preference for a house
245+
*
246+
* @param int $houseId House id.
247+
* @param string $sort Sort mode.
248+
*
249+
* @return DataResponse<Http::STATUS_OK, array{sort: string}, array{}>
250+
*
251+
* 200: Sort preference updated
252+
*/
253+
#[ApiRoute(verb: 'PUT', url: '/api/houses/{houseId}/prefs/checklist-item-sort')]
254+
#[NoAdminRequired]
255+
public function setChecklistItemSort(int $houseId, string $sort): DataResponse {
256+
return $this->runAction(function () use ($houseId, $sort): DataResponse {
257+
$uid = $this->requireUid();
258+
$this->auth->requireMember($houseId, $uid);
259+
$stored = $this->prefs->setChecklistItemSort($uid, $houseId, $sort);
260+
return new DataResponse(['sort' => $stored]);
261+
});
262+
}
263+
222264
/**
223265
* Get notification preferences for a house
224266
*

lib/Db/ChecklistItemMapper.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,32 @@ public function __construct(IDBConnection $db) {
2424
/**
2525
* @return ChecklistItem[]
2626
*/
27-
public function findByList(int $listId): array {
27+
public function findByList(int $listId, string $sortBy = 'custom'): array {
2828
$qb = $this->db->getQueryBuilder();
2929
$qb->select('*')
3030
->from($this->getTableName())
31-
->where($qb->expr()->eq('list_id', $qb->createNamedParameter($listId, IQueryBuilder::PARAM_INT)))
32-
->orderBy('sort_order', 'ASC')
33-
->addOrderBy('created_at', 'ASC');
31+
->where($qb->expr()->eq('list_id', $qb->createNamedParameter($listId, IQueryBuilder::PARAM_INT)));
32+
33+
switch ($sortBy) {
34+
case 'newest':
35+
$qb->orderBy('created_at', 'DESC');
36+
break;
37+
case 'oldest':
38+
$qb->orderBy('created_at', 'ASC');
39+
break;
40+
case 'name_asc':
41+
$qb->orderBy('name', 'ASC')
42+
->addOrderBy('created_at', 'ASC');
43+
break;
44+
case 'name_desc':
45+
$qb->orderBy('name', 'DESC')
46+
->addOrderBy('created_at', 'ASC');
47+
break;
48+
default: // custom
49+
$qb->orderBy('sort_order', 'ASC')
50+
->addOrderBy('created_at', 'ASC');
51+
break;
52+
}
3453

3554
return $this->findEntities($qb);
3655
}

lib/Service/ChecklistService.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ public function deleteList(int $listId): void {
100100
*
101101
* @return ChecklistItem[]
102102
*/
103-
public function listItems(int $listId, ?int $now = null): array {
103+
public function listItems(int $listId, string $sortBy = 'custom', ?int $now = null): array {
104104
// Eagerly reopen any due recurring items in this list before returning.
105105
$this->reopenDueItems($now);
106-
return $this->itemMapper->findByList($listId);
106+
return $this->itemMapper->findByList($listId, $sortBy);
107107
}
108108

109109
public function getItem(int $itemId): ChecklistItem {
@@ -215,6 +215,33 @@ public function updateItem(int $itemId, array $patch): ChecklistItem {
215215
return $item;
216216
}
217217

218+
/**
219+
* Batch reorder items within a list.
220+
*
221+
* @param int $listId List id.
222+
* @param array<array{id: int, sortOrder: int}> $items Reorder entries.
223+
*/
224+
public function reorderItems(int $listId, array $items): void {
225+
foreach ($items as $entry) {
226+
$id = (int)($entry['id'] ?? 0);
227+
$sortOrder = (int)($entry['sortOrder'] ?? 0);
228+
if ($id <= 0) {
229+
continue;
230+
}
231+
try {
232+
$item = $this->itemMapper->findById($id);
233+
} catch (DoesNotExistException) {
234+
continue;
235+
}
236+
if ($item->getListId() !== $listId) {
237+
continue;
238+
}
239+
$item->setSortOrder($sortOrder);
240+
$item->setUpdatedAt(time());
241+
$this->itemMapper->update($item);
242+
}
243+
}
244+
218245
public function toggleItem(int $itemId, string $uid, ?int $now = null): ChecklistItem {
219246
$item = $this->getItem($itemId);
220247
$now ??= time();

lib/Service/PrefsService.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,28 @@ public function setNoteSort(string $uid, int $houseId, string $sort): string {
107107
return $sort;
108108
}
109109

110+
// ----- Checklist item sort preferences -----
111+
112+
private const KEY_CHECKLIST_ITEM_SORT = 'checklist_item_sort';
113+
114+
public function getChecklistItemSort(string $uid, int $houseId): string {
115+
return $this->config->getUserValue(
116+
$uid,
117+
Application::APP_ID,
118+
self::KEY_CHECKLIST_ITEM_SORT . '_' . $houseId,
119+
'custom',
120+
);
121+
}
122+
123+
public function setChecklistItemSort(string $uid, int $houseId, string $sort): string {
124+
$allowed = ['custom', 'newest', 'oldest', 'name_asc', 'name_desc'];
125+
if (!in_array($sort, $allowed, true)) {
126+
$sort = 'custom';
127+
}
128+
$this->config->setUserValue($uid, Application::APP_ID, self::KEY_CHECKLIST_ITEM_SORT . '_' . $houseId, $sort);
129+
return $sort;
130+
}
131+
110132
// ----- Notification preferences -----
111133

112134
public function getNotificationPref(string $uid, int $houseId, string $prefKey): bool {

0 commit comments

Comments
 (0)