This commit is contained in:
2026-08-16 16:57:36 +02:00
parent 96f8c1115c
commit 532d439c41
106 changed files with 10147 additions and 494 deletions
+108 -23
View File
@@ -11,6 +11,7 @@
STAFF_ROLE_OPTIONS,
assignAdminPlan,
cloneAdminCompanyCatalog,
fixAdminCompanyCatalog,
companyPlanBadge,
isStaffRoleApiUnavailable,
listAdminCompanies,
@@ -54,7 +55,7 @@
TabsList,
TabsTrigger
} from "$lib/components/ui";
import { Building2, Copy, KeyRound, Search, Shield, UserPlus, Users } from "@lucide/svelte";
import { Building2, Copy, KeyRound, Search, Shield, UserPlus, Users, Wrench } from "@lucide/svelte";
type TabKey = "users" | "companies";
@@ -93,6 +94,9 @@
let cloneOpen = $state(false);
let cloneCompany = $state<AdminOrgCompany | null>(null);
let fixOpen = $state(false);
let fixCompany = $state<AdminOrgCompany | null>(null);
const usersPage = $derived(Math.floor(usersOffset / PAGE_SIZE) + 1);
const usersPages = $derived(Math.max(1, Math.ceil(usersTotal / PAGE_SIZE)));
const companiesPage = $derived(Math.floor(companiesOffset / PAGE_SIZE) + 1);
@@ -362,6 +366,39 @@
success = "";
}
function openFixDialog(company: AdminOrgCompany) {
fixCompany = company;
fixOpen = true;
error = "";
success = "";
}
async function confirmFixCatalog() {
if (!fixCompany) return;
busy = true;
error = "";
success = "";
const targetName = fixCompany.name;
try {
const res = await fixAdminCompanyCatalog(fixCompany.id, {
reprocessSampleLimit: 25
});
const r = res.result;
success = i18n.t("flash.admin.fixA1Success", {
name: targetName,
prompts: String(r.prompts ?? r.category_prompts_updated ?? 0),
hashes: String(r.hashes ?? r.weak_hashes_cleared ?? 0),
categories: String(r.categories ?? r.categories_backfilled ?? 0)
});
fixOpen = false;
fixCompany = null;
} catch (err) {
error = failureMessage(err, i18n.t("flash.admin.fixA1Error", { name: targetName }));
} finally {
busy = false;
}
}
async function confirmCloneCatalog() {
if (!cloneCompany) return;
busy = true;
@@ -776,28 +813,40 @@
</TableCell>
<TableCell class="hidden text-muted-foreground lg:table-cell">{company.language || "—"}</TableCell>
<TableCell stickyRight>
<div class="flex flex-wrap items-center justify-end gap-1">
<Button
size="sm"
variant="outline"
onclick={() => openCloneDialog(company)}
aria-label={i18n.t("admin.users.cloneCatalogAria", {
name: company.name
})}
data-testid="admin-clone-catalog"
>
<Copy class="h-3.5 w-3.5 lg:mr-1" aria-hidden="true" />
<span class="hidden lg:inline">{i18n.t("admin.users.cloneCatalog")}</span>
</Button>
<Button
size="sm"
onclick={() => openAssignDialog(company)}
aria-label={`Assign plan to ${company.name}`}
>
<UserPlus class="h-3.5 w-3.5 lg:mr-1" aria-hidden="true" />
<span class="hidden lg:inline">{i18n.t("admin.users.assignPlan")}</span>
</Button>
</div>
<div class="flex flex-wrap items-center justify-end gap-1">
<Button
size="sm"
variant="outline"
onclick={() => openFixDialog(company)}
aria-label={i18n.t("admin.users.fixA1Aria", {
name: company.name
})}
data-testid="admin-fix-catalog"
>
<Wrench class="h-3.5 w-3.5 lg:mr-1" aria-hidden="true" />
<span class="hidden lg:inline">{i18n.t("admin.users.fixA1")}</span>
</Button>
<Button
size="sm"
variant="outline"
onclick={() => openCloneDialog(company)}
aria-label={i18n.t("admin.users.cloneCatalogAria", {
name: company.name
})}
data-testid="admin-clone-catalog"
>
<Copy class="h-3.5 w-3.5 lg:mr-1" aria-hidden="true" />
<span class="hidden lg:inline">{i18n.t("admin.users.cloneCatalog")}</span>
</Button>
<Button
size="sm"
onclick={() => openAssignDialog(company)}
aria-label={`Assign plan to ${company.name}`}
>
<UserPlus class="h-3.5 w-3.5 lg:mr-1" aria-hidden="true" />
<span class="hidden lg:inline">{i18n.t("admin.users.assignPlan")}</span>
</Button>
</div>
</TableCell>
</TableRow>
{/each}
@@ -957,3 +1006,39 @@
</div>
</div>
</Dialog>
<Dialog
bind:open={fixOpen}
title={i18n.t("admin.users.fixA1Title")}
description={i18n.t("admin.users.fixA1Desc")}
>
<div class="space-y-4">
{#if error}
<p class="text-sm text-destructive" role="alert">{error}</p>
{/if}
{#if fixCompany}
<p class="text-sm text-foreground">
{i18n.t("admin.users.fixA1Company", { name: fixCompany.name })}
</p>
{/if}
<p class="text-sm text-muted-foreground">
{i18n.t("admin.users.fixA1Warning")}
</p>
<div class="flex flex-wrap justify-end gap-2">
<Button
type="button"
variant="outline"
disabled={busy}
onclick={() => {
fixOpen = false;
fixCompany = null;
}}
>
{i18n.t("admin.users.fixA1Cancel")}
</Button>
<Button type="button" loading={busy} onclick={() => confirmFixCatalog()}>
{i18n.t("admin.users.fixA1Confirm")}
</Button>
</div>
</div>
</Dialog>