diff --git a/.vscode/settings.json b/.vscode/settings.json index 09c7a0e7..632384f7 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" + }, } diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go index f3bca420..6b04dff3 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items.go @@ -56,16 +56,18 @@ 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")), + OnlyWithPhoto: queryBool(params.Get("onlyWithPhoto")), + 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 33b37b56..9f8532ff 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -30,18 +30,20 @@ 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"` + OnlyWithPhoto bool `json:"onlyWithPhoto"` + ParentItemIDs []uuid.UUID `json:"parentIds"` + SortBy string `json:"sortBy"` + IncludeArchived bool `json:"includeArchived"` + Fields []FieldQuery `json:"fields"` + OrderBy string `json:"orderBy"` } ItemField struct { @@ -385,6 +387,27 @@ 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 q.OnlyWithPhoto { + andPredicates = append(andPredicates, 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 b27b65e2..a19ebe6b 100644 --- a/frontend/lib/api/classes/items.ts +++ b/frontend/lib/api/classes/items.ts @@ -24,6 +24,8 @@ export type ItemsQuery = { locations?: string[]; labels?: string[]; negateLabels?: boolean; + onlyWithoutPhoto?: boolean; + onlyWithPhoto?: boolean; parentIds?: string[]; q?: string; fields?: string[]; diff --git a/frontend/locales/en.json b/frontend/locales/en.json index b3200022..5c848056 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -179,6 +179,8 @@ "model_number": "Model Number", "name": "Name", "negate_labels": "Negate Selected Labels", + "only_without_photo": "Only items without photo", + "only_with_photo": "Only items with photo", "next_page": "Next Page", "no_results": "No Items Found", "notes": "Notes", diff --git a/frontend/pages/items.vue b/frontend/pages/items.vue index 039626bc..dbd34e9b 100644 --- a/frontend/pages/items.vue +++ b/frontend/pages/items.vue @@ -41,6 +41,8 @@ const includeArchived = useRouteQuery("archived", false); const fieldSelector = useRouteQuery("fieldSelector", false); const negateLabels = useRouteQuery("negateLabels", false); + const onlyWithoutPhoto = useRouteQuery("onlyWithoutPhoto", false); + const onlyWithPhoto = useRouteQuery("onlyWithPhoto", false); const orderBy = useRouteQuery("orderBy", "name"); const totalPages = computed(() => Math.ceil(total.value / pageSize.value)); @@ -177,6 +179,24 @@ } }); + watch(onlyWithoutPhoto, (newV, oldV) => { + if (newV && onlyWithPhoto) { + onlyWithPhoto.value = false; + } + if (newV !== oldV) { + search(); + } + }); + + watch(onlyWithPhoto, (newV, oldV) => { + if (newV && onlyWithoutPhoto) { + onlyWithoutPhoto.value = false; + } + if (newV !== oldV) { + search(); + } + }); + watch(orderBy, (newV, oldV) => { if (newV !== oldV) { search(); @@ -215,6 +235,8 @@ pageSize: pageSize.value, includeArchived: includeArchived.value ? "true" : "false", negateLabels: negateLabels.value ? "true" : "false", + onlyWithoutPhoto: onlyWithoutPhoto.value ? "true" : "false", + onlyWithPhoto: onlyWithPhoto.value ? "true" : "false", orderBy: orderBy.value, }, }); @@ -243,6 +265,8 @@ locations: locIDs.value, labels: labIDs.value, negateLabels: negateLabels.value, + onlyWithoutPhoto: onlyWithoutPhoto.value, + onlyWithPhoto: onlyWithPhoto.value, includeArchived: includeArchived.value, page: page.value, pageSize: pageSize.value, @@ -294,6 +318,8 @@ archived: includeArchived.value ? "true" : "false", fieldSelector: fieldSelector.value ? "true" : "false", negateLabels: negateLabels.value ? "true" : "false", + onlyWithoutPhoto: onlyWithoutPhoto.value ? "true" : "false", + onlyWithPhoto: onlyWithPhoto.value ? "true" : "false", orderBy: orderBy.value, pageSize: pageSize.value, page: page.value, @@ -377,19 +403,27 @@