From ab76e88aaf88248a6052d36f9162d6f5270b30e6 Mon Sep 17 00:00:00 2001 From: zebrapurring <> Date: Thu, 12 Dec 2024 13:34:02 +0100 Subject: [PATCH 1/3] feat: add search filter for items without photos --- backend/app/api/handlers/v1/v1_ctrl_items.go | 21 ++++++------ backend/internal/data/repo/repo_items.go | 36 +++++++++++++------- frontend/lib/api/classes/items.ts | 1 + frontend/locales/ca.json | 1 + frontend/locales/de.json | 1 + frontend/locales/en.json | 1 + frontend/locales/es.json | 1 + frontend/locales/fr.json | 1 + frontend/locales/it.json | 1 + frontend/locales/nl.json | 1 + frontend/locales/pl.json | 1 + frontend/locales/ru.json | 1 + frontend/locales/sl.json | 1 + frontend/locales/sv.json | 1 + frontend/locales/tr.json | 1 + frontend/locales/zh-CN.json | 1 + frontend/locales/zh-HK.json | 1 + frontend/locales/zh-MO.json | 1 + frontend/locales/zh-TW.json | 1 + frontend/pages/items.vue | 14 ++++++++ 20 files changed, 66 insertions(+), 22 deletions(-) diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go index ed6f11524..52a71387e 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items.go @@ -56,16 +56,17 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc { } v := repo.ItemQuery{ - Page: queryIntOrNegativeOne(params.Get("page")), - PageSize: queryIntOrNegativeOne(params.Get("pageSize")), - Search: params.Get("q"), - LocationIDs: queryUUIDList(params, "locations"), - LabelIDs: queryUUIDList(params, "labels"), - NegateLabels: queryBool(params.Get("negateLabels")), - ParentItemIDs: queryUUIDList(params, "parentIds"), - IncludeArchived: queryBool(params.Get("includeArchived")), - Fields: filterFieldItems(params["fields"]), - OrderBy: params.Get("orderBy"), + Page: queryIntOrNegativeOne(params.Get("page")), + PageSize: queryIntOrNegativeOne(params.Get("pageSize")), + Search: params.Get("q"), + LocationIDs: queryUUIDList(params, "locations"), + LabelIDs: queryUUIDList(params, "labels"), + NegateLabels: queryBool(params.Get("negateLabels")), + OnlyWithoutPhoto: queryBool(params.Get("onlyWithoutPhoto")), + ParentItemIDs: queryUUIDList(params, "parentIds"), + IncludeArchived: queryBool(params.Get("includeArchived")), + Fields: filterFieldItems(params["fields"]), + OrderBy: params.Get("orderBy"), } if strings.HasPrefix(v.Search, "#") { diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go index dd6a1c647..68cd13ef4 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -30,18 +30,19 @@ type ( } ItemQuery struct { - Page int - PageSize int - Search string `json:"search"` - AssetID AssetID `json:"assetId"` - LocationIDs []uuid.UUID `json:"locationIds"` - LabelIDs []uuid.UUID `json:"labelIds"` - NegateLabels bool `json:"negateLabels"` - ParentItemIDs []uuid.UUID `json:"parentIds"` - SortBy string `json:"sortBy"` - IncludeArchived bool `json:"includeArchived"` - Fields []FieldQuery `json:"fields"` - OrderBy string `json:"orderBy"` + Page int + PageSize int + Search string `json:"search"` + AssetID AssetID `json:"assetId"` + LocationIDs []uuid.UUID `json:"locationIds"` + LabelIDs []uuid.UUID `json:"labelIds"` + NegateLabels bool `json:"negateLabels"` + OnlyWithoutPhoto bool `json:"onlyWithoutPhoto"` + ParentItemIDs []uuid.UUID `json:"parentIds"` + SortBy string `json:"sortBy"` + IncludeArchived bool `json:"includeArchived"` + Fields []FieldQuery `json:"fields"` + OrderBy string `json:"orderBy"` } ItemField struct { @@ -381,6 +382,17 @@ func (e *ItemsRepository) QueryByGroup(ctx context.Context, gid uuid.UUID, q Ite } } + if q.OnlyWithoutPhoto { + andPredicates = append(andPredicates, item.Not( + item.HasAttachmentsWith( + attachment.And( + attachment.Primary(true), + attachment.TypeEQ(attachment.TypePhoto), + ), + )), + ) + } + if len(q.LocationIDs) > 0 { locationPredicates := make([]predicate.Item, 0, len(q.LocationIDs)) for _, l := range q.LocationIDs { diff --git a/frontend/lib/api/classes/items.ts b/frontend/lib/api/classes/items.ts index b27b65e26..1aebc8e1e 100644 --- a/frontend/lib/api/classes/items.ts +++ b/frontend/lib/api/classes/items.ts @@ -24,6 +24,7 @@ export type ItemsQuery = { locations?: string[]; labels?: string[]; negateLabels?: boolean; + onlyWithoutPhoto?: boolean; parentIds?: string[]; q?: string; fields?: string[]; diff --git a/frontend/locales/ca.json b/frontend/locales/ca.json index 9807bb61e..fd4bf7f9b 100644 --- a/frontend/locales/ca.json +++ b/frontend/locales/ca.json @@ -165,6 +165,7 @@ "next_page": "Pàgina següent", "no_results": "No s'ha trobat cap element", "notes": "Notes", + "only_without_photo": "Només articles sense foto", "options": "Opcions", "order_by": "Ordena per", "pages": "Pàgina { page } de { totalPages }", diff --git a/frontend/locales/de.json b/frontend/locales/de.json index 303638209..534957d77 100644 --- a/frontend/locales/de.json +++ b/frontend/locales/de.json @@ -162,6 +162,7 @@ "model_number": "Modelnummer", "name": "Name", "negate_labels": "Ausgewählte Etiketten negieren", + "only_without_photo": "Nur Artikel ohne Foto", "next_page": "Nächste Seite", "no_results": "Keine Elemente gefunden", "notes": "Anmerkungen", diff --git a/frontend/locales/en.json b/frontend/locales/en.json index c18480d86..ce64519c6 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -162,6 +162,7 @@ "model_number": "Model Number", "name": "Name", "negate_labels": "Negate Selected Labels", + "only_without_photo": "Only items without photo", "next_page": "Next Page", "no_results": "No Items Found", "notes": "Notes", diff --git a/frontend/locales/es.json b/frontend/locales/es.json index b5c38dd4d..b9112eed6 100644 --- a/frontend/locales/es.json +++ b/frontend/locales/es.json @@ -162,6 +162,7 @@ "model_number": "Número de Modelo", "name": "Nombre", "negate_labels": "Negar Etiquetas Seleccionadas", + "only_without_photo": "Solo artículos sin foto", "next_page": "Siguiente Página", "no_results": "No se Encontraron Elementos", "notes": "Notas", diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index 3cb2c609b..fb540dcb9 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -132,6 +132,7 @@ "include_archive": "Inclure les éléments archivés", "last": "Dernier", "negate_labels": "Négliger les étiquettes sélectionnées", + "only_without_photo": "Seulement des articles sans photo", "next_page": "Page suivante", "no_results": "Aucun élément trouvé", "options": "Options", diff --git a/frontend/locales/it.json b/frontend/locales/it.json index 6345e112e..50badfd9e 100644 --- a/frontend/locales/it.json +++ b/frontend/locales/it.json @@ -162,6 +162,7 @@ "model_number": "Numero modello", "name": "Nome", "negate_labels": "Negare Etichette Selezionate", + "only_without_photo": "Solo articoli senza foto", "next_page": "Pagina Successiva", "no_results": "Nessun Articolo Trovato", "notes": "Note", diff --git a/frontend/locales/nl.json b/frontend/locales/nl.json index b09d61d0e..230f8357f 100644 --- a/frontend/locales/nl.json +++ b/frontend/locales/nl.json @@ -165,6 +165,7 @@ "next_page": "Volgende pagina", "no_results": "Geen Items Gevonden", "notes": "Opmerkingen", + "only_without_photo": "Alleen items zonder foto", "options": "Opties", "order_by": "Sorteren op", "pages": "Pagina { page } van { totalPages }", diff --git a/frontend/locales/pl.json b/frontend/locales/pl.json index 6ff446264..58d990d55 100644 --- a/frontend/locales/pl.json +++ b/frontend/locales/pl.json @@ -162,6 +162,7 @@ "next_page": "Następna strona", "no_results": "Nie znaleziono przedmiotów", "notes": "Notatki", + "only_without_photo": "Tylko przedmioty bez zdjęcia", "options": "Opcje", "order_by": "Ułóż według", "pages": "Strona {page} z {totalPages}", diff --git a/frontend/locales/ru.json b/frontend/locales/ru.json index 67cfcb0c5..afa10bbca 100644 --- a/frontend/locales/ru.json +++ b/frontend/locales/ru.json @@ -162,6 +162,7 @@ "model_number": "Номер модели", "name": "Название", "negate_labels": "Снять выбранные ярлыки", + "only_without_photo": "Только товары без фото", "next_page": "Следующая страница", "no_results": "Элементы не найдены", "notes": "Заметки", diff --git a/frontend/locales/sl.json b/frontend/locales/sl.json index 689329e5a..f40139c57 100644 --- a/frontend/locales/sl.json +++ b/frontend/locales/sl.json @@ -165,6 +165,7 @@ "next_page": "Naslednja stran", "no_results": "Ni najdenih predmetov", "notes": "Opombe", + "only_without_photo": "Samo izdelki brez fotografije", "options": "Možnosti", "order_by": "Razvrsti po", "pages": "Stran { page } od { totalPages }", diff --git a/frontend/locales/sv.json b/frontend/locales/sv.json index 2eb6c84e1..b250460ff 100644 --- a/frontend/locales/sv.json +++ b/frontend/locales/sv.json @@ -162,6 +162,7 @@ "model_number": "Modellnummer", "name": "Namn", "negate_labels": "Negera valda etiketter", + "only_without_photo": "Endast objekt utan foto", "next_page": "Nästa sida", "no_results": "Inga föremål hittades", "notes": "Anteckningar", diff --git a/frontend/locales/tr.json b/frontend/locales/tr.json index 54704dec1..9a820d1ee 100644 --- a/frontend/locales/tr.json +++ b/frontend/locales/tr.json @@ -162,6 +162,7 @@ "model_number": "Model Numarası", "name": "İsim", "negate_labels": "Seçili Etiketleri Yoksay", + "only_without_photo": "Fotoğrafsız ürünler", "next_page": "Sonraki Sayfa", "no_results": "Öğe Bulunamadı", "notes": "Notlar", diff --git a/frontend/locales/zh-CN.json b/frontend/locales/zh-CN.json index 5681cb8d3..1e1f7655c 100644 --- a/frontend/locales/zh-CN.json +++ b/frontend/locales/zh-CN.json @@ -159,6 +159,7 @@ "model_number": "型号", "name": "名称", "negate_labels": "取消选中的标签", + "only_without_photo": "仅无照片的商品", "next_page": "下一页", "no_results": "没有可显示的物品", "notes": "笔记", diff --git a/frontend/locales/zh-HK.json b/frontend/locales/zh-HK.json index fb1eace7a..fb6ca9faf 100644 --- a/frontend/locales/zh-HK.json +++ b/frontend/locales/zh-HK.json @@ -12,6 +12,7 @@ "created_at": "創建於", "last": "最後一項", "negate_labels": "取消選定的標籤", + "only_without_photo": "僅無相片的商品", "next_page": "下一頁", "no_results": "沒有找到項目", "options": "選項", diff --git a/frontend/locales/zh-MO.json b/frontend/locales/zh-MO.json index fb1eace7a..fb6ca9faf 100644 --- a/frontend/locales/zh-MO.json +++ b/frontend/locales/zh-MO.json @@ -12,6 +12,7 @@ "created_at": "創建於", "last": "最後一項", "negate_labels": "取消選定的標籤", + "only_without_photo": "僅無相片的商品", "next_page": "下一頁", "no_results": "沒有找到項目", "options": "選項", diff --git a/frontend/locales/zh-TW.json b/frontend/locales/zh-TW.json index fb1eace7a..c13528d31 100644 --- a/frontend/locales/zh-TW.json +++ b/frontend/locales/zh-TW.json @@ -12,6 +12,7 @@ "created_at": "創建於", "last": "最後一項", "negate_labels": "取消選定的標籤", + "only_without_photo": "僅無照片的商品", "next_page": "下一頁", "no_results": "沒有找到項目", "options": "選項", diff --git a/frontend/pages/items.vue b/frontend/pages/items.vue index 039626bcf..0bf99bc0c 100644 --- a/frontend/pages/items.vue +++ b/frontend/pages/items.vue @@ -41,6 +41,7 @@ const includeArchived = useRouteQuery("archived", false); const fieldSelector = useRouteQuery("fieldSelector", false); const negateLabels = useRouteQuery("negateLabels", false); + const onlyWithoutPhoto = useRouteQuery("onlyWithoutPhoto", false); const orderBy = useRouteQuery("orderBy", "name"); const totalPages = computed(() => Math.ceil(total.value / pageSize.value)); @@ -177,6 +178,12 @@ } }); + watch(onlyWithoutPhoto, (newV, oldV) => { + if (newV !== oldV) { + search(); + } + }); + watch(orderBy, (newV, oldV) => { if (newV !== oldV) { search(); @@ -215,6 +222,7 @@ pageSize: pageSize.value, includeArchived: includeArchived.value ? "true" : "false", negateLabels: negateLabels.value ? "true" : "false", + onlyWithoutPhoto: onlyWithoutPhoto.value ? "true" : "false", orderBy: orderBy.value, }, }); @@ -243,6 +251,7 @@ locations: locIDs.value, labels: labIDs.value, negateLabels: negateLabels.value, + onlyWithoutPhoto: onlyWithoutPhoto.value, includeArchived: includeArchived.value, page: page.value, pageSize: pageSize.value, @@ -294,6 +303,7 @@ archived: includeArchived.value ? "true" : "false", fieldSelector: fieldSelector.value ? "true" : "false", negateLabels: negateLabels.value ? "true" : "false", + onlyWithoutPhoto: onlyWithoutPhoto.value ? "true" : "false", orderBy: orderBy.value, pageSize: pageSize.value, page: page.value, @@ -391,6 +401,10 @@ {{ $t("items.negate_labels") }} + + + {{ $t("items.only_without_photo") }} + {{ $t("global.name") }} From 5642d6ef6c6179bdb58d06cb4f9880ca46c047e5 Mon Sep 17 00:00:00 2001 From: zebrapurring <> Date: Thu, 12 Dec 2024 13:34:18 +0100 Subject: [PATCH 2/3] chore: configure Golang formatter for VSCode --- .vscode/settings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 09c7a0e7f..632384f75 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,5 +30,8 @@ "editor.quickSuggestions": { "strings": true }, - "tailwindCSS.experimental.configFile": "./frontend/tailwind.config.js" + "tailwindCSS.experimental.configFile": "./frontend/tailwind.config.js", + "[go]": { + "editor.defaultFormatter": "golang.go" + }, } From d65958d00103e9a906c663a852df3959b971646b Mon Sep 17 00:00:00 2001 From: zebrapurring <> Date: Thu, 12 Dec 2024 13:48:39 +0100 Subject: [PATCH 3/3] fix: displaying long filter labels for some locales --- frontend/pages/items.vue | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/pages/items.vue b/frontend/pages/items.vue index 0bf99bc0c..3366b7316 100644 --- a/frontend/pages/items.vue +++ b/frontend/pages/items.vue @@ -387,23 +387,23 @@ {{ $t("items.options") }} - {{ $t("items.include_archive") }} + {{ $t("items.include_archive") }} - {{ $t("items.field_selector") }} + {{ $t("items.field_selector") }} - {{ $t("items.negate_labels") }} + {{ $t("items.negate_labels") }} - {{ $t("items.only_without_photo") }} + {{ $t("items.only_without_photo") }} @@ -411,7 +411,7 @@ {{ $t("items.created_at") }} {{ $t("items.updated_at") }} - {{ $t("items.order_by") }} + {{ $t("items.order_by") }} {{ $t("items.reset_search") }}