fix
This commit is contained in:
@@ -27,6 +27,9 @@ export const ADMIN_SYNC_A1_PATH = (companyId: string) =>
|
||||
export const ADMIN_STAFF_ROLE_PATH = (userId: string) =>
|
||||
`/api/admin/users/${encodeURIComponent(userId)}/staff-role`;
|
||||
|
||||
/** Matches API `platformDemoCompanyName` (catalog/prompt_repair.go). */
|
||||
export const PLATFORM_DEMO_COMPANY_NAME = "Platform Demo";
|
||||
|
||||
export const PAGE_SIZE = 25;
|
||||
|
||||
export type PlatformStaffRole = "admin" | "developer" | "support_staff";
|
||||
@@ -217,7 +220,7 @@ export type CloneCatalogResult = {
|
||||
counts: Record<string, number>;
|
||||
};
|
||||
|
||||
/** Copy source company catalog into the admin sandbox (home / dest). Source is unchanged. */
|
||||
/** Copy source company catalog into dest (explicit id preferred). Source is unchanged. */
|
||||
export async function cloneAdminCompanyCatalog(
|
||||
sourceCompanyId: string,
|
||||
opts?: { destCompanyId?: string }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MessageDict } from "./types";
|
||||
import type { MessageDict } from "./types";
|
||||
|
||||
/** German (de) UI pack — keys must stay in sync with en.ts. */
|
||||
export const de: MessageDict = {
|
||||
@@ -812,6 +812,9 @@ export const de: MessageDict = {
|
||||
"admin.users.cloneCatalogCancel": "Abbrechen",
|
||||
"admin.users.cloneCatalogConfirm": "Katalog kopieren",
|
||||
"admin.users.cloneDestFallback": "Ihr Sandbox-Unternehmen",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "A1 synchronisieren",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "A1-Katalog synchronisieren",
|
||||
@@ -2307,7 +2310,6 @@ export const de: MessageDict = {
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "A1-Sync für {name} fehlgeschlagen.",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch to Platform Demo (staff home), then clone again.",
|
||||
"flash.admin.planAssignedShort": "Plan zugewiesen.",
|
||||
"flash.admin.creditsUpdated": "Credits aktualisiert.",
|
||||
"flash.admin.cyclesProcessed": "Abrechnungszyklen verarbeitet: {count}",
|
||||
|
||||
@@ -839,6 +839,9 @@ export const en: MessageDict = {
|
||||
"admin.users.cloneCatalogCancel": "Cancel",
|
||||
"admin.users.cloneCatalogConfirm": "Copy catalog",
|
||||
"admin.users.cloneDestFallback": "your sandbox company",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "Sync A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sync A1 catalog",
|
||||
@@ -2333,10 +2336,9 @@ export const en: MessageDict = {
|
||||
"flash.admin.staffRoleUpdated": "Staff role updated for {email}.",
|
||||
"flash.admin.staffRoleUnavailable": "Staff role updates are not available on this server yet.",
|
||||
"flash.admin.planAssigned": "Plan assigned to {name}.",
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} products with mapped category. Open Products — uncategorized SKUs need dump backfill or categorize, not another Fix.",
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} products with mapped category. Open Products ΓÇö uncategorized SKUs need dump backfill or categorize, not another Fix.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "Sync A1 failed for {name}.",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch active company to Platform Demo (or your staff home), then clone again. Clone refuses to overwrite A1.",
|
||||
"flash.admin.planAssignedShort": "Plan assigned.",
|
||||
"flash.admin.creditsUpdated": "Credits updated.",
|
||||
"flash.admin.cyclesProcessed": "Billing cycles processed: {count}",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MessageDict } from "./types";
|
||||
import type { MessageDict } from "./types";
|
||||
|
||||
/** Spanish (es) UI pack — keys must stay in sync with en.ts. */
|
||||
export const es: MessageDict = {
|
||||
@@ -812,6 +812,9 @@ export const es: MessageDict = {
|
||||
"admin.users.cloneCatalogCancel": "Cancelar",
|
||||
"admin.users.cloneCatalogConfirm": "Copiar catálogo",
|
||||
"admin.users.cloneDestFallback": "tu empresa sandbox",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "Sincronizar A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sincronizar catálogo A1",
|
||||
@@ -2306,7 +2309,6 @@ export const es: MessageDict = {
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "Falló la sincronización A1 de {name}.",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch to Platform Demo (staff home), then clone again.",
|
||||
"flash.admin.planAssigned": "Plan asignado a {name}.",
|
||||
"flash.admin.planAssignedShort": "Plan asignado.",
|
||||
"flash.admin.creditsUpdated": "Créditos actualizados.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MessageDict } from "./types";
|
||||
import type { MessageDict } from "./types";
|
||||
|
||||
/** French (fr) UI pack — keys must stay in sync with en.ts. */
|
||||
export const fr: MessageDict = {
|
||||
@@ -812,6 +812,9 @@ export const fr: MessageDict = {
|
||||
"admin.users.cloneCatalogCancel": "Annuler",
|
||||
"admin.users.cloneCatalogConfirm": "Copier le catalogue",
|
||||
"admin.users.cloneDestFallback": "votre entreprise sandbox",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "Synchroniser A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Synchroniser le catalogue A1",
|
||||
@@ -2306,7 +2309,6 @@ export const fr: MessageDict = {
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "Échec de la synchronisation A1 pour {name}.",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch to Platform Demo (staff home), then clone again.",
|
||||
"flash.admin.planAssigned": "Offre assignée à {name}.",
|
||||
"flash.admin.planAssignedShort": "Offre assignée.",
|
||||
"flash.admin.creditsUpdated": "Crédits mis à jour.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MessageDict } from "./types";
|
||||
import type { MessageDict } from "./types";
|
||||
|
||||
/** Italian (it) UI pack — keys must stay in sync with en.ts. */
|
||||
export const it: MessageDict = {
|
||||
@@ -812,6 +812,9 @@ export const it: MessageDict = {
|
||||
"admin.users.cloneCatalogCancel": "Annulla",
|
||||
"admin.users.cloneCatalogConfirm": "Copia catalogo",
|
||||
"admin.users.cloneDestFallback": "la tua azienda sandbox",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "Sincronizza A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sincronizza catalogo A1",
|
||||
@@ -2306,7 +2309,6 @@ export const it: MessageDict = {
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "Sincronizzazione A1 per {name} non riuscita.",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch to Platform Demo (staff home), then clone again.",
|
||||
"flash.admin.planAssigned": "Piano assegnato a {name}.",
|
||||
"flash.admin.planAssignedShort": "Piano assegnato.",
|
||||
"flash.admin.creditsUpdated": "Crediti aggiornati.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MessageDict } from "./types";
|
||||
import type { MessageDict } from "./types";
|
||||
|
||||
/** Japanese (ja) UI pack — keys must stay in sync with en.ts. */
|
||||
export const ja: MessageDict = {
|
||||
@@ -812,6 +812,9 @@ export const ja: MessageDict = {
|
||||
"admin.users.cloneCatalogCancel": "ã‚ャンセル",
|
||||
"admin.users.cloneCatalogConfirm": "ã‚«ã‚¿ãƒã‚°ã‚’コピー",
|
||||
"admin.users.cloneDestFallback": "サンドボックス会社",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "A1を同期",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "A1カタログを同期",
|
||||
@@ -2306,7 +2309,6 @@ export const ja: MessageDict = {
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "{name} のA1同期に失敗しました。",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch to Platform Demo (staff home), then clone again.",
|
||||
"flash.admin.planAssigned": "{name} ã«ãƒ—ランを割り当ã¦ã¾ã—ãŸã€‚",
|
||||
"flash.admin.planAssignedShort": "プランを割り当ã¦ã¾ã—ãŸã€‚",
|
||||
"flash.admin.creditsUpdated": "クレジットを更新ã—ã¾ã—ãŸã€‚",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MessageDict } from "./types";
|
||||
import type { MessageDict } from "./types";
|
||||
|
||||
/** Dutch (nl) UI pack — keys must stay in sync with en.ts. */
|
||||
export const nl: MessageDict = {
|
||||
@@ -812,6 +812,9 @@ export const nl: MessageDict = {
|
||||
"admin.users.cloneCatalogCancel": "Annuleren",
|
||||
"admin.users.cloneCatalogConfirm": "Catalogus kopiëren",
|
||||
"admin.users.cloneDestFallback": "je sandboxbedrijf",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "A1 synchroniseren",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "A1-catalogus synchroniseren",
|
||||
@@ -2306,7 +2309,6 @@ export const nl: MessageDict = {
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "A1-synchronisatie voor {name} mislukt.",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch to Platform Demo (staff home), then clone again.",
|
||||
"flash.admin.planAssigned": "Plan toegewezen aan {name}.",
|
||||
"flash.admin.planAssignedShort": "Plan toegewezen.",
|
||||
"flash.admin.creditsUpdated": "Credits bijgewerkt.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MessageDict } from "./types";
|
||||
import type { MessageDict } from "./types";
|
||||
|
||||
/** Polish (pl) UI pack — keys must stay in sync with en.ts. */
|
||||
export const pl: MessageDict = {
|
||||
@@ -812,6 +812,9 @@ export const pl: MessageDict = {
|
||||
"admin.users.cloneCatalogCancel": "Anuluj",
|
||||
"admin.users.cloneCatalogConfirm": "Kopiuj katalog",
|
||||
"admin.users.cloneDestFallback": "twoja firma sandbox",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "Synchronizuj A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Synchronizuj katalog A1",
|
||||
@@ -2306,7 +2309,6 @@ export const pl: MessageDict = {
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "Synchronizacja A1 dla {name} nie powiodła się.",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch to Platform Demo (staff home), then clone again.",
|
||||
"flash.admin.planAssigned": "Przypisano plan do {name}.",
|
||||
"flash.admin.planAssignedShort": "Przypisano plan.",
|
||||
"flash.admin.creditsUpdated": "Zaktualizowano kredyty.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MessageDict } from "./types";
|
||||
import type { MessageDict } from "./types";
|
||||
|
||||
/** Portuguese (pt) UI pack — keys must stay in sync with en.ts. */
|
||||
export const pt: MessageDict = {
|
||||
@@ -812,6 +812,9 @@ export const pt: MessageDict = {
|
||||
"admin.users.cloneCatalogCancel": "Cancelar",
|
||||
"admin.users.cloneCatalogConfirm": "Copiar catálogo",
|
||||
"admin.users.cloneDestFallback": "a sua empresa sandbox",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "Sincronizar A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sincronizar catálogo A1",
|
||||
@@ -2306,7 +2309,6 @@ export const pt: MessageDict = {
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "Falha na sincronização A1 de {name}.",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch to Platform Demo (staff home), then clone again.",
|
||||
"flash.admin.planAssigned": "Plano atribuÃdo a {name}.",
|
||||
"flash.admin.planAssignedShort": "Plano atribuÃdo.",
|
||||
"flash.admin.creditsUpdated": "Créditos atualizados.",
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import type { MessageDict } from "./types.ts";
|
||||
import type { MessageDict } from "./types.ts";
|
||||
|
||||
/** Slovenian (sl) UI strings for admin Fix A1 — ready pack. Not registered in UI_LOCALES yet (avoid inventing locale switcher). */
|
||||
export const sl: MessageDict = {
|
||||
"admin.users.cloneCatalog": "Copy to my company",
|
||||
"admin.users.cloneCatalogAria": "Copy {name} catalog into your sandbox company",
|
||||
"admin.users.cloneCatalogTitle": "Copy company catalog",
|
||||
"admin.users.cloneCatalogDesc": "Stay on your admin account. Source data is not changed.",
|
||||
"admin.users.cloneCatalogFrom": "Source: {name}",
|
||||
"admin.users.cloneCatalogInto": "Destination: {name}",
|
||||
"admin.users.cloneCatalogWarning": "Replaces categories, feeds, products, attributes, mappings, and brand on the destination. Plan, billing, members, API keys, and store connectors are not copied.",
|
||||
"admin.users.cloneCatalogCancel": "Cancel",
|
||||
"admin.users.cloneCatalogConfirm": "Copy catalog",
|
||||
"admin.users.cloneDestFallback": "your sandbox company",
|
||||
"admin.users.cloneDestLabel": "Destination company",
|
||||
"admin.users.cloneDestSelect": "Select destination",
|
||||
"admin.users.cloneDestRequired": "Choose a destination company different from the source.",
|
||||
"admin.users.syncA1": "Sinhroniziraj A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sinhroniziraj katalog A1",
|
||||
@@ -10,7 +23,7 @@ export const sl: MessageDict = {
|
||||
"admin.users.syncA1Warning": "Requires descrybe_new.sql on the API server (scripts/seed/ or SEED_A1_MYSQL_DUMP) for dump category coverage. Without a dump, only DB hygiene runs. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "Prekliči",
|
||||
"admin.users.syncA1Confirm": "Sinhroniziraj A1",
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "Sinhronizacija A1 za {name} ni uspela.",
|
||||
"flash.admin.cloneDestMissing": "Ni ciljnega sandbox podjetja. Preklopite na Platform Demo, nato klonirajte.",
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { formatCredits, formatDate } from "$lib/utils";
|
||||
import {
|
||||
PAGE_SIZE,
|
||||
PLATFORM_DEMO_COMPANY_NAME,
|
||||
STAFF_ROLE_OPTIONS,
|
||||
assignAdminPlan,
|
||||
cloneAdminCompanyCatalog,
|
||||
@@ -93,23 +94,23 @@
|
||||
|
||||
let cloneOpen = $state(false);
|
||||
let cloneCompany = $state<AdminOrgCompany | null>(null);
|
||||
let cloneDestId = $state("");
|
||||
let cloneDestOptions = $state<AdminOrgCompany[]>([]);
|
||||
|
||||
let syncOpen = $state(false);
|
||||
let syncCompany = $state<AdminOrgCompany | null>(null);
|
||||
|
||||
const cloneDestLabel = $derived.by(() => {
|
||||
const selected = cloneDestOptions.find((c) => c.id === cloneDestId);
|
||||
if (selected?.name) return selected.name;
|
||||
return i18n.t("admin.users.cloneDestFallback");
|
||||
});
|
||||
|
||||
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);
|
||||
const companiesPages = $derived(Math.max(1, Math.ceil(companiesTotal / PAGE_SIZE)));
|
||||
|
||||
const cloneDestLabel = $derived.by(() => {
|
||||
const me = authSession.me;
|
||||
const home = me?.staff_home_company;
|
||||
if (home?.name) return home.name;
|
||||
if (me?.company?.name) return me.company.name;
|
||||
return i18n.t("admin.users.cloneDestFallback");
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
const gate = await requirePlatformAdmin();
|
||||
if (!gate.ok) {
|
||||
@@ -359,11 +360,49 @@
|
||||
}
|
||||
}
|
||||
|
||||
function openCloneDialog(company: AdminOrgCompany) {
|
||||
async function openCloneDialog(company: AdminOrgCompany) {
|
||||
cloneCompany = company;
|
||||
cloneOpen = true;
|
||||
error = "";
|
||||
success = "";
|
||||
cloneDestId = "";
|
||||
const byId = new Map<string, AdminOrgCompany>();
|
||||
for (const c of companies) {
|
||||
if (c.id !== company.id) byId.set(c.id, c);
|
||||
}
|
||||
try {
|
||||
const demoRes = await listAdminCompanies({
|
||||
limit: PAGE_SIZE,
|
||||
offset: 0,
|
||||
q: PLATFORM_DEMO_COMPANY_NAME
|
||||
});
|
||||
for (const c of demoRes.companies) {
|
||||
if (c.id !== company.id) byId.set(c.id, c);
|
||||
}
|
||||
} catch {
|
||||
/* keep page companies as fallback */
|
||||
}
|
||||
const me = authSession.me;
|
||||
const home = me?.staff_home_company;
|
||||
if (home?.id && home.id !== company.id && home.name) {
|
||||
byId.set(home.id, { id: home.id, name: home.name });
|
||||
}
|
||||
for (const c of me?.companies ?? []) {
|
||||
if (c.id && c.id !== company.id && c.name) {
|
||||
byId.set(c.id, { id: c.id, name: c.name });
|
||||
}
|
||||
}
|
||||
const opts = [...byId.values()].sort((a, b) => a.name.localeCompare(b.name));
|
||||
cloneDestOptions = opts;
|
||||
const demo =
|
||||
opts.find((c) => c.name.trim().toLowerCase() === PLATFORM_DEMO_COMPANY_NAME.toLowerCase()) ??
|
||||
null;
|
||||
const homeId = (me?.staff_home_company_id || me?.staff_home_company?.id || "").trim();
|
||||
cloneDestId =
|
||||
demo?.id ||
|
||||
(homeId && homeId !== company.id ? homeId : "") ||
|
||||
opts[0]?.id ||
|
||||
"";
|
||||
}
|
||||
|
||||
function openSyncDialog(company: AdminOrgCompany) {
|
||||
@@ -373,6 +412,54 @@
|
||||
success = "";
|
||||
}
|
||||
|
||||
async function confirmCloneCatalog() {
|
||||
if (!cloneCompany) return;
|
||||
const destId = cloneDestId.trim();
|
||||
if (!destId || destId === cloneCompany.id) {
|
||||
error = i18n.t("admin.users.cloneDestRequired");
|
||||
return;
|
||||
}
|
||||
busy = true;
|
||||
error = "";
|
||||
success = "";
|
||||
try {
|
||||
const res = await cloneAdminCompanyCatalog(cloneCompany.id, {
|
||||
destCompanyId: destId
|
||||
});
|
||||
const products = Number(res.counts?.raw_products ?? 0);
|
||||
const categories = Number(res.counts?.categories ?? 0);
|
||||
const mappedWith = Number(res.counts?.mapped_with_category ?? 0);
|
||||
const activeDest = (res.active_company_id || res.dest_company_id || "").trim();
|
||||
success = i18n.t("flash.admin.catalogCloned", {
|
||||
source: cloneCompany.name,
|
||||
dest: cloneDestLabel,
|
||||
products: String(products),
|
||||
categories: String(categories),
|
||||
mapped_with: String(mappedWith)
|
||||
});
|
||||
cloneOpen = false;
|
||||
cloneCompany = null;
|
||||
cloneDestId = "";
|
||||
cloneDestOptions = [];
|
||||
if (activeDest) {
|
||||
try {
|
||||
await api("/api/auth/select-company", {
|
||||
method: "POST",
|
||||
body: { company_id: activeDest }
|
||||
});
|
||||
} catch {
|
||||
/* clone handler may already have switched the session */
|
||||
}
|
||||
window.location.assign("/products");
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
error = failureMessage(err, "Clone catalog failed");
|
||||
} finally {
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmSyncA1() {
|
||||
if (!syncCompany) return;
|
||||
busy = true;
|
||||
@@ -409,56 +496,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmCloneCatalog() {
|
||||
if (!cloneCompany) return;
|
||||
busy = true;
|
||||
error = "";
|
||||
success = "";
|
||||
try {
|
||||
const me = authSession.me;
|
||||
const destId =
|
||||
me?.staff_home_company_id?.trim() ||
|
||||
me?.staff_home_company?.id?.trim() ||
|
||||
undefined;
|
||||
if (!destId && me?.company?.id && me.company.id === cloneCompany.id) {
|
||||
error = i18n.t("flash.admin.cloneDestMissing");
|
||||
return;
|
||||
}
|
||||
const res = await cloneAdminCompanyCatalog(cloneCompany.id, {
|
||||
destCompanyId: destId
|
||||
});
|
||||
const products = Number(res.counts?.raw_products ?? 0);
|
||||
const categories = Number(res.counts?.categories ?? 0);
|
||||
const mappedWith = Number(res.counts?.mapped_with_category ?? 0);
|
||||
const activeDest = (res.active_company_id || res.dest_company_id || "").trim();
|
||||
success = i18n.t("flash.admin.catalogCloned", {
|
||||
source: cloneCompany.name,
|
||||
dest: cloneDestLabel,
|
||||
products: String(products),
|
||||
categories: String(categories),
|
||||
mapped_with: String(mappedWith)
|
||||
});
|
||||
cloneOpen = false;
|
||||
cloneCompany = null;
|
||||
if (activeDest) {
|
||||
try {
|
||||
await api("/api/auth/select-company", {
|
||||
method: "POST",
|
||||
body: { company_id: activeDest }
|
||||
});
|
||||
} catch {
|
||||
/* clone handler may already have switched the session */
|
||||
}
|
||||
window.location.assign("/products");
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
error = failureMessage(err, "Clone catalog failed");
|
||||
} finally {
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function sendSetPasswordEmails(userId?: string) {
|
||||
busy = true;
|
||||
error = "";
|
||||
@@ -998,9 +1035,15 @@
|
||||
{i18n.t("admin.users.cloneCatalogFrom", { name: cloneCompany.name })}
|
||||
</p>
|
||||
{/if}
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{i18n.t("admin.users.cloneCatalogInto", { name: cloneDestLabel })}
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
<Label for="clone-dest">{i18n.t("admin.users.cloneDestLabel")}</Label>
|
||||
<Select id="clone-dest" bind:value={cloneDestId} data-testid="admin-clone-dest">
|
||||
<option value="">{i18n.t("admin.users.cloneDestSelect")}</option>
|
||||
{#each cloneDestOptions as dest}
|
||||
<option value={dest.id}>{dest.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{i18n.t("admin.users.cloneCatalogWarning")}
|
||||
</p>
|
||||
@@ -1012,11 +1055,18 @@
|
||||
onclick={() => {
|
||||
cloneOpen = false;
|
||||
cloneCompany = null;
|
||||
cloneDestId = "";
|
||||
cloneDestOptions = [];
|
||||
}}
|
||||
>
|
||||
{i18n.t("admin.users.cloneCatalogCancel")}
|
||||
</Button>
|
||||
<Button type="button" loading={busy} onclick={() => confirmCloneCatalog()}>
|
||||
<Button
|
||||
type="button"
|
||||
loading={busy}
|
||||
disabled={!cloneDestId || cloneDestId === cloneCompany?.id}
|
||||
onclick={() => confirmCloneCatalog()}
|
||||
>
|
||||
{i18n.t("admin.users.cloneCatalogConfirm")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user