show no site category

This commit is contained in:
miloschwartz
2026-06-30 21:49:08 -04:00
parent 9f68be2a9b
commit 376dd465b3
4 changed files with 121 additions and 5 deletions
+1
View File
@@ -3572,6 +3572,7 @@
"resourceLauncherDeleteView": "Delete View", "resourceLauncherDeleteView": "Delete View",
"resourceLauncherViewAsAdmin": "View as Admin", "resourceLauncherViewAsAdmin": "View as Admin",
"resourceLauncherUnlabeled": "Unlabeled", "resourceLauncherUnlabeled": "Unlabeled",
"resourceLauncherNoSite": "No Site",
"resourceLauncherNoResourcesInGroup": "No resources in this group", "resourceLauncherNoResourcesInGroup": "No resources in this group",
"resourceLauncherEmptyStateTitle": "No Resources Available", "resourceLauncherEmptyStateTitle": "No Resources Available",
"resourceLauncherEmptyStateDescription": "You don't have access to any resources yet. Contact your administrator to request access.", "resourceLauncherEmptyStateDescription": "You don't have access to any resources yet. Contact your administrator to request access.",
@@ -27,6 +27,7 @@ import {
countDistinct, countDistinct,
eq, eq,
inArray, inArray,
isNull,
like, like,
or, or,
sql sql
@@ -38,6 +39,7 @@ import {
formatSiteResourceAccess formatSiteResourceAccess
} from "./formatLauncherAccess"; } from "./formatLauncherAccess";
import { import {
LAUNCHER_NO_SITE_GROUP_KEY,
LAUNCHER_UNLABELED_GROUP_KEY, LAUNCHER_UNLABELED_GROUP_KEY,
type LauncherGroup, type LauncherGroup,
type LauncherLabel, type LauncherLabel,
@@ -508,6 +510,83 @@ async function listSiteGroups(
} }
} }
let noSiteCount = 0;
if (accessible.resourceIds.length > 0 && siteFilterIds.length === 0) {
const noSitePublicConditions = [
inArray(resources.resourceId, accessible.resourceIds),
eq(resources.orgId, orgId),
eq(resources.enabled, true)
];
if (searchPublic) {
noSitePublicConditions.push(searchPublic);
}
let noSitePublicQuery = db
.select({
itemCount: countDistinct(resources.resourceId)
})
.from(resources)
.leftJoin(targets, eq(targets.resourceId, resources.resourceId));
if (labelFilterIds.length > 0) {
noSitePublicQuery = noSitePublicQuery.innerJoin(
resourceLabels,
eq(resourceLabels.resourceId, resources.resourceId)
);
noSitePublicConditions.push(
inArray(resourceLabels.labelId, labelFilterIds)
);
}
const [noSitePublicRow] = await noSitePublicQuery.where(
and(...noSitePublicConditions, isNull(targets.targetId))
);
noSiteCount += Number(noSitePublicRow?.itemCount ?? 0);
}
if (accessible.siteResourceIds.length > 0 && siteFilterIds.length === 0) {
const noSiteSiteConditions = [
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
eq(siteResources.orgId, orgId),
eq(siteResources.enabled, true)
];
if (searchSite) {
noSiteSiteConditions.push(searchSite);
}
let noSiteSiteQuery = db
.select({
itemCount: countDistinct(siteResources.siteResourceId)
})
.from(siteResources)
.leftJoin(
siteNetworks,
eq(siteResources.networkId, siteNetworks.networkId)
)
.leftJoin(sites, eq(siteNetworks.siteId, sites.siteId));
if (labelFilterIds.length > 0) {
noSiteSiteQuery = noSiteSiteQuery.innerJoin(
siteResourceLabels,
eq(
siteResourceLabels.siteResourceId,
siteResources.siteResourceId
)
);
noSiteSiteConditions.push(
inArray(siteResourceLabels.labelId, labelFilterIds)
);
}
const [noSiteSiteRow] = await noSiteSiteQuery.where(
and(...noSiteSiteConditions, isNull(sites.siteId))
);
noSiteCount += Number(noSiteSiteRow?.itemCount ?? 0);
}
let groups: LauncherGroup[] = Array.from(siteCountMap.values()).map( let groups: LauncherGroup[] = Array.from(siteCountMap.values()).map(
(row) => ({ (row) => ({
groupKey: String(row.siteId), groupKey: String(row.siteId),
@@ -519,6 +598,15 @@ async function listSiteGroups(
}) })
); );
if (noSiteCount > 0 && siteFilterIds.length === 0) {
groups.push({
groupKey: LAUNCHER_NO_SITE_GROUP_KEY,
name: "No Site",
groupType: "site",
itemCount: noSiteCount
});
}
groups.sort((a, b) => { groups.sort((a, b) => {
const cmp = a.name.localeCompare(b.name, undefined, { const cmp = a.name.localeCompare(b.name, undefined, {
sensitivity: "base" sensitivity: "base"
@@ -923,6 +1011,20 @@ async function mapSiteResources(
return result; return result;
} }
function filterResourcesBySite(
items: LauncherResource[],
groupKey: string
): LauncherResource[] {
if (groupKey === LAUNCHER_NO_SITE_GROUP_KEY) {
return items.filter((item) => !item.site);
}
const siteId = Number.parseInt(groupKey, 10);
if (!Number.isFinite(siteId)) {
return items;
}
return items.filter((item) => item.site?.siteId === siteId);
}
function filterResourcesByLabel( function filterResourcesByLabel(
items: LauncherResource[], items: LauncherResource[],
groupKey: string groupKey: string
@@ -1052,10 +1154,14 @@ export async function listLauncherResourcesForUser(
filteredSiteResourceIds filteredSiteResourceIds
); );
const siteIdFilter = const parsedSiteId =
query.groupBy === "site" query.groupBy === "site" &&
query.groupKey !== LAUNCHER_NO_SITE_GROUP_KEY
? Number.parseInt(query.groupKey, 10) ? Number.parseInt(query.groupKey, 10)
: undefined; : Number.NaN;
const siteIdFilter = Number.isFinite(parsedSiteId)
? parsedSiteId
: undefined;
const [publicItems, siteItems] = await Promise.all([ const [publicItems, siteItems] = await Promise.all([
mapPublicResources( mapPublicResources(
@@ -1077,6 +1183,8 @@ export async function listLauncherResourcesForUser(
if (query.groupBy === "label") { if (query.groupBy === "label") {
items = filterResourcesByLabel(items, query.groupKey); items = filterResourcesByLabel(items, query.groupKey);
} else if (query.groupBy === "site") {
items = filterResourcesBySite(items, query.groupKey);
} }
items = sortLauncherResources(items, query.order); items = sortLauncherResources(items, query.order);
+1
View File
@@ -1,6 +1,7 @@
import { z } from "zod"; import { z } from "zod";
export const LAUNCHER_UNLABELED_GROUP_KEY = "unlabeled"; export const LAUNCHER_UNLABELED_GROUP_KEY = "unlabeled";
export const LAUNCHER_NO_SITE_GROUP_KEY = "no-site";
export const launcherViewConfigSchema = z.object({ export const launcherViewConfigSchema = z.object({
groupBy: z.enum(["site", "label"]).default("site"), groupBy: z.enum(["site", "label"]).default("site"),
@@ -16,6 +16,10 @@ import type {
LauncherResource, LauncherResource,
LauncherViewConfig LauncherViewConfig
} from "@server/routers/launcher/types"; } from "@server/routers/launcher/types";
import {
LAUNCHER_NO_SITE_GROUP_KEY,
LAUNCHER_UNLABELED_GROUP_KEY
} from "@server/routers/launcher/types";
import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteQuery } from "@tanstack/react-query";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@@ -140,9 +144,11 @@ export function LauncherGroupSection({
}, [fetchNextPage, hasNextPage, isFetchingNextPage, isOpen]); }, [fetchNextPage, hasNextPage, isFetchingNextPage, isOpen]);
const groupTitle = const groupTitle =
group.groupKey === "unlabeled" group.groupKey === LAUNCHER_UNLABELED_GROUP_KEY
? t("resourceLauncherUnlabeled") ? t("resourceLauncherUnlabeled")
: group.name; : group.groupKey === LAUNCHER_NO_SITE_GROUP_KEY
? t("resourceLauncherNoSite")
: group.name;
return ( return (
<Collapsible <Collapsible