@open-mercato/core 0.4.6-develop-6247c21d52 → 0.4.6-develop-bbfd75b1c8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/currencies/backend/currencies/page.js +1 -1
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/directory/api/tenants/route.js +4 -1
- package/dist/modules/directory/api/tenants/route.js.map +2 -2
- package/dist/modules/directory/components/TenantSelect.js +8 -1
- package/dist/modules/directory/components/TenantSelect.js.map +2 -2
- package/package.json +2 -2
- package/src/modules/currencies/backend/currencies/page.tsx +1 -1
- package/src/modules/directory/api/tenants/route.ts +4 -1
- package/src/modules/directory/components/TenantSelect.tsx +9 -1
|
@@ -70,7 +70,7 @@ function CurrenciesPage() {
|
|
|
70
70
|
const handleSetBase = React.useCallback(
|
|
71
71
|
async (row) => {
|
|
72
72
|
try {
|
|
73
|
-
const call = await apiCall("/api/currencies", {
|
|
73
|
+
const call = await apiCall("/api/currencies/currencies", {
|
|
74
74
|
method: "PUT",
|
|
75
75
|
headers: { "Content-Type": "application/json" },
|
|
76
76
|
body: JSON.stringify({ id: row.id, isBase: true })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/currencies/backend/currencies/page.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'\nimport { Plus, Star } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\n\ntype CurrencyRow = {\n id: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n isBase: boolean\n isActive: boolean\n organizationId: string\n tenantId: string\n createdAt: string\n updatedAt: string\n}\n\ntype ResponsePayload = {\n items: CurrencyRow[]\n total: number\n page: number\n totalPages: number\n}\n\nexport default function CurrenciesPage() {\n const t = useT()\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n const [rows, setRows] = React.useState<CurrencyRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filters, setFilters] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const scopeVersion = useOrganizationScopeVersion()\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '50')\n if (search) params.set('search', search)\n if (filters.isBase === true) params.set('isBase', 'true')\n if (filters.isActive === 'true') params.set('isActive', 'true')\n if (filters.isActive === 'false') params.set('isActive', 'false')\n\n const fallback: ResponsePayload = { items: [], total: 0, page, totalPages: 1 }\n const call = await apiCall<ResponsePayload>(\n `/api/currencies/currencies?${params.toString()}`,\n undefined,\n { fallback }\n )\n\n if (!call.ok) {\n flash(t('currencies.list.error.load'), 'error')\n return\n }\n\n const payload = call.result ?? fallback\n if (!cancelled) {\n setRows(Array.isArray(payload.items) ? payload.items : [])\n setTotal(payload.total || 0)\n setTotalPages(payload.totalPages || 1)\n }\n } catch (error) {\n if (!cancelled) {\n flash(t('currencies.list.error.load'), 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => {\n cancelled = true\n }\n }, [page, search, filters, reloadToken, scopeVersion, t])\n\n const handleSetBase = React.useCallback(\n async (row: CurrencyRow) => {\n try {\n const call = await apiCall('/api/currencies', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: row.id, isBase: true }),\n })\n\n if (!call.ok) {\n flash(t('currencies.flash.baseSetError'), 'error')\n return\n }\n\n flash(t('currencies.flash.baseSet'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n flash(t('currencies.flash.baseSetError'), 'error')\n }\n },\n [t]\n )\n\n const handleDelete = React.useCallback(\n async (row: CurrencyRow) => {\n const confirmed = await confirmDialog({\n title: t('currencies.list.confirmDelete', { code: row.code }),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n const call = await apiCall(`/api/currencies/currencies`, {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: row.id, organizationId: row.organizationId, tenantId: row.tenantId }),\n })\n\n if (!call.ok) {\n flash(t('currencies.flash.deleteError'), 'error')\n return\n }\n\n flash(t('currencies.flash.deleted'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n flash(t('currencies.flash.deleteError'), 'error')\n }\n },\n [t, confirmDialog]\n )\n\n const columns = React.useMemo<ColumnDef<CurrencyRow>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('currencies.list.columns.code'),\n cell: ({ row }) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"font-mono font-medium\">{row.original.code}</span>\n {row.original.isBase && (\n <Badge variant=\"default\" className=\"gap-1\">\n <Star className=\"h-3 w-3\" />\n {t('currencies.list.base')}\n </Badge>\n )}\n </div>\n ),\n },\n {\n accessorKey: 'name',\n header: t('currencies.list.columns.name'),\n },\n {\n accessorKey: 'symbol',\n header: t('currencies.list.columns.symbol'),\n cell: ({ row }) => row.original.symbol || '\u2014',\n },\n {\n accessorKey: 'decimalPlaces',\n header: t('currencies.list.columns.decimalPlaces'),\n },\n {\n accessorKey: 'isActive',\n header: t('currencies.list.columns.active'),\n enableSorting: false,\n cell: ({ getValue }) => <BooleanIcon value={Boolean(getValue())} />,\n },\n {\n accessorKey: 'createdAt',\n header: t('currencies.list.columns.createdAt'),\n cell: ({ row }) => {\n const date = row.original.createdAt\n return date ? new Date(date).toLocaleString() : '\u2014'\n },\n },\n {\n accessorKey: 'updatedAt',\n header: t('currencies.list.columns.updatedAt'),\n cell: ({ row }) => {\n const date = row.original.updatedAt\n return date ? new Date(date).toLocaleString() : '\u2014'\n },\n },\n ],\n [t]\n )\n\n const filterDefs = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'isBase',\n label: t('currencies.list.filters.baseOnly'),\n type: 'checkbox',\n },\n {\n id: 'isActive',\n label: t('currencies.list.filters.status'),\n type: 'select',\n options: [\n { label: t('currencies.list.filters.all'), value: '' },\n { label: t('currencies.list.filters.active'), value: 'true' },\n { label: t('currencies.list.filters.inactive'), value: 'false' },\n ],\n },\n ],\n [t]\n )\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={t('currencies.list.title')}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('currencies.list.searchPlaceholder')}\n filters={filterDefs}\n filterValues={filters}\n onFiltersApply={(values) => {\n setFilters(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilters({})\n setPage(1)\n }}\n actions={\n <Button asChild>\n <Link href=\"/backend/currencies/create\">\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('currencies.list.actions.create')}\n </Link>\n </Button>\n }\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('common.edit'),\n href: `/backend/currencies/${row.id}`,\n },\n ...(!row.isBase\n ? [\n {\n id: 'set-base',\n label: t('currencies.list.actions.setBase'),\n onSelect: () => handleSetBase(row),\n },\n ]\n : []),\n ...(!row.isBase\n ? [\n {\n id: 'delete',\n label: t('common.delete'),\n destructive: true,\n onSelect: () => handleDelete(row),\n },\n ]\n : []),\n ]}\n />\n )}\n pagination={{ page, pageSize: 50, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n perspective={{ tableId: 'currencies.list' }}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AA6JY,cAEE,YAFF;AA3JZ,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,MAAM,gBAAgB;AAC/B,SAAS,iBAAiB;AAE1B,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,MAAM,YAAY;AAC3B,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AAwBlB,SAAR,iBAAkC;AACvC,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,SAAS,eAAe,qBAAqB,IAAI,iBAAiB;AAC1E,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAwB,CAAC,CAAC;AACxD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,CAAC;AAC1C,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAuB,CAAC,CAAC;AAC7D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,CAAC;AACtD,QAAM,eAAe,4BAA4B;AAEjD,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,mBAAe,OAAO;AACpB,mBAAa,IAAI;AACjB,UAAI;AACF,cAAM,SAAS,IAAI,gBAAgB;AACnC,eAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;AAC/B,eAAO,IAAI,YAAY,IAAI;AAC3B,YAAI,OAAQ,QAAO,IAAI,UAAU,MAAM;AACvC,YAAI,QAAQ,WAAW,KAAM,QAAO,IAAI,UAAU,MAAM;AACxD,YAAI,QAAQ,aAAa,OAAQ,QAAO,IAAI,YAAY,MAAM;AAC9D,YAAI,QAAQ,aAAa,QAAS,QAAO,IAAI,YAAY,OAAO;AAEhE,cAAM,WAA4B,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,YAAY,EAAE;AAC7E,cAAM,OAAO,MAAM;AAAA,UACjB,8BAA8B,OAAO,SAAS,CAAC;AAAA,UAC/C;AAAA,UACA,EAAE,SAAS;AAAA,QACb;AAEA,YAAI,CAAC,KAAK,IAAI;AACZ,gBAAM,EAAE,4BAA4B,GAAG,OAAO;AAC9C;AAAA,QACF;AAEA,cAAM,UAAU,KAAK,UAAU;AAC/B,YAAI,CAAC,WAAW;AACd,kBAAQ,MAAM,QAAQ,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC,CAAC;AACzD,mBAAS,QAAQ,SAAS,CAAC;AAC3B,wBAAc,QAAQ,cAAc,CAAC;AAAA,QACvC;AAAA,MACF,SAAS,OAAO;AACd,YAAI,CAAC,WAAW;AACd,gBAAM,EAAE,4BAA4B,GAAG,OAAO;AAAA,QAChD;AAAA,MACF,UAAE;AACA,YAAI,CAAC,UAAW,cAAa,KAAK;AAAA,MACpC;AAAA,IACF;AACA,SAAK;AACL,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,QAAQ,SAAS,aAAa,cAAc,CAAC,CAAC;AAExD,QAAM,gBAAgB,MAAM;AAAA,IAC1B,OAAO,QAAqB;AAC1B,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'\nimport { Plus, Star } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\n\ntype CurrencyRow = {\n id: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n isBase: boolean\n isActive: boolean\n organizationId: string\n tenantId: string\n createdAt: string\n updatedAt: string\n}\n\ntype ResponsePayload = {\n items: CurrencyRow[]\n total: number\n page: number\n totalPages: number\n}\n\nexport default function CurrenciesPage() {\n const t = useT()\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n const [rows, setRows] = React.useState<CurrencyRow[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [filters, setFilters] = React.useState<FilterValues>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const scopeVersion = useOrganizationScopeVersion()\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '50')\n if (search) params.set('search', search)\n if (filters.isBase === true) params.set('isBase', 'true')\n if (filters.isActive === 'true') params.set('isActive', 'true')\n if (filters.isActive === 'false') params.set('isActive', 'false')\n\n const fallback: ResponsePayload = { items: [], total: 0, page, totalPages: 1 }\n const call = await apiCall<ResponsePayload>(\n `/api/currencies/currencies?${params.toString()}`,\n undefined,\n { fallback }\n )\n\n if (!call.ok) {\n flash(t('currencies.list.error.load'), 'error')\n return\n }\n\n const payload = call.result ?? fallback\n if (!cancelled) {\n setRows(Array.isArray(payload.items) ? payload.items : [])\n setTotal(payload.total || 0)\n setTotalPages(payload.totalPages || 1)\n }\n } catch (error) {\n if (!cancelled) {\n flash(t('currencies.list.error.load'), 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => {\n cancelled = true\n }\n }, [page, search, filters, reloadToken, scopeVersion, t])\n\n const handleSetBase = React.useCallback(\n async (row: CurrencyRow) => {\n try {\n const call = await apiCall('/api/currencies/currencies', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: row.id, isBase: true }),\n })\n\n if (!call.ok) {\n flash(t('currencies.flash.baseSetError'), 'error')\n return\n }\n\n flash(t('currencies.flash.baseSet'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n flash(t('currencies.flash.baseSetError'), 'error')\n }\n },\n [t]\n )\n\n const handleDelete = React.useCallback(\n async (row: CurrencyRow) => {\n const confirmed = await confirmDialog({\n title: t('currencies.list.confirmDelete', { code: row.code }),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n const call = await apiCall(`/api/currencies/currencies`, {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: row.id, organizationId: row.organizationId, tenantId: row.tenantId }),\n })\n\n if (!call.ok) {\n flash(t('currencies.flash.deleteError'), 'error')\n return\n }\n\n flash(t('currencies.flash.deleted'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n flash(t('currencies.flash.deleteError'), 'error')\n }\n },\n [t, confirmDialog]\n )\n\n const columns = React.useMemo<ColumnDef<CurrencyRow>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('currencies.list.columns.code'),\n cell: ({ row }) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"font-mono font-medium\">{row.original.code}</span>\n {row.original.isBase && (\n <Badge variant=\"default\" className=\"gap-1\">\n <Star className=\"h-3 w-3\" />\n {t('currencies.list.base')}\n </Badge>\n )}\n </div>\n ),\n },\n {\n accessorKey: 'name',\n header: t('currencies.list.columns.name'),\n },\n {\n accessorKey: 'symbol',\n header: t('currencies.list.columns.symbol'),\n cell: ({ row }) => row.original.symbol || '\u2014',\n },\n {\n accessorKey: 'decimalPlaces',\n header: t('currencies.list.columns.decimalPlaces'),\n },\n {\n accessorKey: 'isActive',\n header: t('currencies.list.columns.active'),\n enableSorting: false,\n cell: ({ getValue }) => <BooleanIcon value={Boolean(getValue())} />,\n },\n {\n accessorKey: 'createdAt',\n header: t('currencies.list.columns.createdAt'),\n cell: ({ row }) => {\n const date = row.original.createdAt\n return date ? new Date(date).toLocaleString() : '\u2014'\n },\n },\n {\n accessorKey: 'updatedAt',\n header: t('currencies.list.columns.updatedAt'),\n cell: ({ row }) => {\n const date = row.original.updatedAt\n return date ? new Date(date).toLocaleString() : '\u2014'\n },\n },\n ],\n [t]\n )\n\n const filterDefs = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'isBase',\n label: t('currencies.list.filters.baseOnly'),\n type: 'checkbox',\n },\n {\n id: 'isActive',\n label: t('currencies.list.filters.status'),\n type: 'select',\n options: [\n { label: t('currencies.list.filters.all'), value: '' },\n { label: t('currencies.list.filters.active'), value: 'true' },\n { label: t('currencies.list.filters.inactive'), value: 'false' },\n ],\n },\n ],\n [t]\n )\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={t('currencies.list.title')}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('currencies.list.searchPlaceholder')}\n filters={filterDefs}\n filterValues={filters}\n onFiltersApply={(values) => {\n setFilters(values)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilters({})\n setPage(1)\n }}\n actions={\n <Button asChild>\n <Link href=\"/backend/currencies/create\">\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('currencies.list.actions.create')}\n </Link>\n </Button>\n }\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('common.edit'),\n href: `/backend/currencies/${row.id}`,\n },\n ...(!row.isBase\n ? [\n {\n id: 'set-base',\n label: t('currencies.list.actions.setBase'),\n onSelect: () => handleSetBase(row),\n },\n ]\n : []),\n ...(!row.isBase\n ? [\n {\n id: 'delete',\n label: t('common.delete'),\n destructive: true,\n onSelect: () => handleDelete(row),\n },\n ]\n : []),\n ]}\n />\n )}\n pagination={{ page, pageSize: 50, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n perspective={{ tableId: 'currencies.list' }}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA6JY,cAEE,YAFF;AA3JZ,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,MAAM,gBAAgB;AAC/B,SAAS,iBAAiB;AAE1B,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,MAAM,YAAY;AAC3B,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AAwBlB,SAAR,iBAAkC;AACvC,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,SAAS,eAAe,qBAAqB,IAAI,iBAAiB;AAC1E,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAwB,CAAC,CAAC;AACxD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,CAAC;AAC1C,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAuB,CAAC,CAAC;AAC7D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,CAAC;AACtD,QAAM,eAAe,4BAA4B;AAEjD,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,mBAAe,OAAO;AACpB,mBAAa,IAAI;AACjB,UAAI;AACF,cAAM,SAAS,IAAI,gBAAgB;AACnC,eAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;AAC/B,eAAO,IAAI,YAAY,IAAI;AAC3B,YAAI,OAAQ,QAAO,IAAI,UAAU,MAAM;AACvC,YAAI,QAAQ,WAAW,KAAM,QAAO,IAAI,UAAU,MAAM;AACxD,YAAI,QAAQ,aAAa,OAAQ,QAAO,IAAI,YAAY,MAAM;AAC9D,YAAI,QAAQ,aAAa,QAAS,QAAO,IAAI,YAAY,OAAO;AAEhE,cAAM,WAA4B,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,MAAM,YAAY,EAAE;AAC7E,cAAM,OAAO,MAAM;AAAA,UACjB,8BAA8B,OAAO,SAAS,CAAC;AAAA,UAC/C;AAAA,UACA,EAAE,SAAS;AAAA,QACb;AAEA,YAAI,CAAC,KAAK,IAAI;AACZ,gBAAM,EAAE,4BAA4B,GAAG,OAAO;AAC9C;AAAA,QACF;AAEA,cAAM,UAAU,KAAK,UAAU;AAC/B,YAAI,CAAC,WAAW;AACd,kBAAQ,MAAM,QAAQ,QAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC,CAAC;AACzD,mBAAS,QAAQ,SAAS,CAAC;AAC3B,wBAAc,QAAQ,cAAc,CAAC;AAAA,QACvC;AAAA,MACF,SAAS,OAAO;AACd,YAAI,CAAC,WAAW;AACd,gBAAM,EAAE,4BAA4B,GAAG,OAAO;AAAA,QAChD;AAAA,MACF,UAAE;AACA,YAAI,CAAC,UAAW,cAAa,KAAK;AAAA,MACpC;AAAA,IACF;AACA,SAAK;AACL,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,QAAQ,SAAS,aAAa,cAAc,CAAC,CAAC;AAExD,QAAM,gBAAgB,MAAM;AAAA,IAC1B,OAAO,QAAqB;AAC1B,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,8BAA8B;AAAA,UACvD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,IAAI,IAAI,QAAQ,KAAK,CAAC;AAAA,QACnD,CAAC;AAED,YAAI,CAAC,KAAK,IAAI;AACZ,gBAAM,EAAE,+BAA+B,GAAG,OAAO;AACjD;AAAA,QACF;AAEA,cAAM,EAAE,0BAA0B,GAAG,SAAS;AAC9C,uBAAe,CAAC,UAAU,QAAQ,CAAC;AAAA,MACrC,SAAS,OAAO;AACd,cAAM,EAAE,+BAA+B,GAAG,OAAO;AAAA,MACnD;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,eAAe,MAAM;AAAA,IACzB,OAAO,QAAqB;AAC1B,YAAM,YAAY,MAAM,cAAc;AAAA,QACpC,OAAO,EAAE,iCAAiC,EAAE,MAAM,IAAI,KAAK,CAAC;AAAA,QAC5D,SAAS;AAAA,MACX,CAAC;AACD,UAAI,CAAC,UAAW;AAEhB,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,8BAA8B;AAAA,UACvD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,IAAI,IAAI,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS,CAAC;AAAA,QACjG,CAAC;AAED,YAAI,CAAC,KAAK,IAAI;AACZ,gBAAM,EAAE,8BAA8B,GAAG,OAAO;AAChD;AAAA,QACF;AAEA,cAAM,EAAE,0BAA0B,GAAG,SAAS;AAC9C,uBAAe,CAAC,UAAU,QAAQ,CAAC;AAAA,MACrC,SAAS,OAAO;AACd,cAAM,EAAE,8BAA8B,GAAG,OAAO;AAAA,MAClD;AAAA,IACF;AAAA,IACA,CAAC,GAAG,aAAa;AAAA,EACnB;AAEA,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,8BAA8B;AAAA,QACxC,MAAM,CAAC,EAAE,IAAI,MACX,qBAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,UAAK,WAAU,yBAAyB,cAAI,SAAS,MAAK;AAAA,UAC1D,IAAI,SAAS,UACZ,qBAAC,SAAM,SAAQ,WAAU,WAAU,SACjC;AAAA,gCAAC,QAAK,WAAU,WAAU;AAAA,YACzB,EAAE,sBAAsB;AAAA,aAC3B;AAAA,WAEJ;AAAA,MAEJ;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,8BAA8B;AAAA,MAC1C;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,gCAAgC;AAAA,QAC1C,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,UAAU;AAAA,MAC5C;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,uCAAuC;AAAA,MACnD;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,gCAAgC;AAAA,QAC1C,eAAe;AAAA,QACf,MAAM,CAAC,EAAE,SAAS,MAAM,oBAAC,eAAY,OAAO,QAAQ,SAAS,CAAC,GAAG;AAAA,MACnE;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,mCAAmC;AAAA,QAC7C,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,gBAAM,OAAO,IAAI,SAAS;AAC1B,iBAAO,OAAO,IAAI,KAAK,IAAI,EAAE,eAAe,IAAI;AAAA,QAClD;AAAA,MACF;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,mCAAmC;AAAA,QAC7C,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,gBAAM,OAAO,IAAI,SAAS;AAC1B,iBAAO,OAAO,IAAI,KAAK,IAAI,EAAE,eAAe,IAAI;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,aAAa,MAAM;AAAA,IACvB,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,kCAAkC;AAAA,QAC3C,MAAM;AAAA,MACR;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,gCAAgC;AAAA,QACzC,MAAM;AAAA,QACN,SAAS;AAAA,UACP,EAAE,OAAO,EAAE,6BAA6B,GAAG,OAAO,GAAG;AAAA,UACrD,EAAE,OAAO,EAAE,gCAAgC,GAAG,OAAO,OAAO;AAAA,UAC5D,EAAE,OAAO,EAAE,kCAAkC,GAAG,OAAO,QAAQ;AAAA,QACjE;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,SACE,qBAAC,QACC;AAAA,wBAAC,YACC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,uBAAuB;AAAA,QAChC;AAAA,QACA,MAAM;AAAA,QACN,aAAa;AAAA,QACb,gBAAgB,CAAC,UAAU;AACzB,oBAAU,KAAK;AACf,kBAAQ,CAAC;AAAA,QACX;AAAA,QACA,mBAAmB,EAAE,mCAAmC;AAAA,QACxD,SAAS;AAAA,QACT,cAAc;AAAA,QACd,gBAAgB,CAAC,WAAW;AAC1B,qBAAW,MAAM;AACjB,kBAAQ,CAAC;AAAA,QACX;AAAA,QACA,gBAAgB,MAAM;AACpB,qBAAW,CAAC,CAAC;AACb,kBAAQ,CAAC;AAAA,QACX;AAAA,QACA,SACE,oBAAC,UAAO,SAAO,MACb,+BAAC,QAAK,MAAK,8BACT;AAAA,8BAAC,QAAK,WAAU,gBAAe;AAAA,UAC9B,EAAE,gCAAgC;AAAA,WACrC,GACF;AAAA,QAEF,YAAY,CAAC,QACX;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL;AAAA,gBACE,IAAI;AAAA,gBACJ,OAAO,EAAE,aAAa;AAAA,gBACtB,MAAM,uBAAuB,IAAI,EAAE;AAAA,cACrC;AAAA,cACA,GAAI,CAAC,IAAI,SACL;AAAA,gBACE;AAAA,kBACE,IAAI;AAAA,kBACJ,OAAO,EAAE,iCAAiC;AAAA,kBAC1C,UAAU,MAAM,cAAc,GAAG;AAAA,gBACnC;AAAA,cACF,IACA,CAAC;AAAA,cACL,GAAI,CAAC,IAAI,SACL;AAAA,gBACE;AAAA,kBACE,IAAI;AAAA,kBACJ,OAAO,EAAE,eAAe;AAAA,kBACxB,aAAa;AAAA,kBACb,UAAU,MAAM,aAAa,GAAG;AAAA,gBAClC;AAAA,cACF,IACA,CAAC;AAAA,YACP;AAAA;AAAA,QACF;AAAA,QAEF,YAAY,EAAE,MAAM,UAAU,IAAI,OAAO,YAAY,cAAc,QAAQ;AAAA,QAC3E;AAAA,QACA,aAAa,EAAE,SAAS,kBAAkB;AAAA;AAAA,IAC5C,GACF;AAAA,IACC;AAAA,KACH;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -87,7 +87,10 @@ async function GET(req) {
|
|
|
87
87
|
isActive: url.searchParams.get("isActive") ?? void 0
|
|
88
88
|
});
|
|
89
89
|
if (!parsed.success) {
|
|
90
|
-
return NextResponse.json(
|
|
90
|
+
return NextResponse.json(
|
|
91
|
+
{ error: "Invalid query parameters", details: parsed.error.flatten() },
|
|
92
|
+
{ status: 400 }
|
|
93
|
+
);
|
|
91
94
|
}
|
|
92
95
|
const container = await createRequestContainer();
|
|
93
96
|
const em = container.resolve("em");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/directory/api/tenants/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { tenantCreateSchema, tenantUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport { loadCustomFieldValues, buildCustomFieldFiltersFromQuery } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { tenantCrudEvents, tenantCrudIndexer } from '@open-mercato/core/modules/directory/commands/tenants'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { directoryTag, directoryErrorSchema, directoryOkSchema, tenantListResponseSchema } from '../openapi'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst listQuerySchema = z.object({\n id: z.string().uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n sortField: z.enum(['name', 'createdAt', 'updatedAt']).optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n isActive: z.enum(['true', 'false']).optional(),\n}).passthrough()\n\ntype TenantRow = {\n id: string\n name: string\n isActive: boolean\n createdAt: string | null\n updatedAt: string | null\n} & Record<string, unknown>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['directory.tenants.view'] },\n POST: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst rawBodySchema = z.object({}).passthrough()\ntype CrudInput = Record<string, unknown>\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: Tenant,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: tenantCrudEvents,\n indexer: tenantCrudIndexer,\n actions: {\n create: {\n commandId: 'directory.tenants.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'directory.tenants.update',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'directory.tenants.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nconst toRow = (tenant: Tenant, cf: Record<string, unknown>): TenantRow => ({\n id: String(tenant.id),\n name: String(tenant.name),\n isActive: !!tenant.isActive,\n createdAt: tenant.createdAt ? tenant.createdAt.toISOString() : null,\n updatedAt: tenant.updatedAt ? tenant.updatedAt.toISOString() : null,\n ...cf,\n})\n\nconst matchesValue = (current: unknown, expected: unknown): boolean => {\n if (Array.isArray(current)) return current.some((item) => item === expected)\n return current === expected\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 50, totalPages: 1 }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listQuerySchema.safeParse({\n id: url.searchParams.get('id') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField: url.searchParams.get('sortField') ?? undefined,\n sortDir: url.searchParams.get('sortDir') ?? undefined,\n isActive: url.searchParams.get('isActive') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 50, totalPages: 1 }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { id, page, pageSize, search, sortField, sortDir, isActive } = parsed.data\n const where: FilterQuery<Tenant> = { deletedAt: null }\n if (id) where.id = id\n if (search) {\n Object.assign(where, { name: { $ilike: `%${escapeLikePattern(search)}%` } })\n }\n const active = parseBooleanToken(isActive)\n if (active !== null) where.isActive = active\n\n const fieldMap: Record<string, string> = { name: 'name', createdAt: 'createdAt', updatedAt: 'updatedAt' }\n const orderBy: Record<string, 'ASC' | 'DESC'> = {}\n if (sortField) {\n const mapped = fieldMap[sortField] || 'name'\n orderBy[mapped] = sortDir === 'desc' ? 'DESC' : 'ASC'\n } else {\n orderBy.name = 'ASC'\n }\n\n const all = await em.find(Tenant, where, { orderBy })\n const recordIds = all.map((tenant) => String(tenant.id))\n\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const rid of recordIds) {\n tenantIdByRecord[rid] = null\n organizationIdByRecord[rid] = null\n }\n\n const cfValues = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.tenant,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks: auth.tenantId ? [auth.tenantId] : [],\n })\n : {}\n\n const rawQuery = Array.from(url.searchParams.keys()).reduce<Record<string, unknown>>((acc, key) => {\n const values = url.searchParams.getAll(key)\n if (!values.length) return acc\n acc[key] = values.length === 1 ? values[0] : values\n return acc\n }, {})\n\n const cfFilters = await buildCustomFieldFiltersFromQuery({\n entityId: E.directory.tenant,\n query: rawQuery,\n em,\n tenantId: auth.tenantId ?? null,\n })\n const cfFilterEntries = Object.entries(cfFilters).map(([rawKey, condition]) => {\n const normalizedKey = rawKey.startsWith('cf:') ? rawKey.slice(3) : rawKey.replace(/^cf_/, '')\n return [normalizedKey, condition] as const\n })\n\n const filtered = cfFilterEntries.length\n ? all.filter((tenant) => {\n const rid = String(tenant.id)\n const payload = cfValues[rid] ?? {}\n return cfFilterEntries.every(([key, expected]) => {\n const value = payload[`cf_${key}`]\n if (expected && typeof expected === 'object' && !Array.isArray(expected)) {\n const maybeIn = (expected as { $in?: unknown[] }).$in\n if (Array.isArray(maybeIn)) {\n if (value === undefined || value === null) return false\n if (Array.isArray(value)) return value.some((val) => maybeIn.includes(val))\n return maybeIn.includes(value)\n }\n }\n return matchesValue(value, expected)\n })\n })\n : all\n\n const total = filtered.length\n const start = (page - 1) * pageSize\n const paged = filtered.slice(start, start + pageSize)\n const items = paged.map((tenant) => {\n const rid = String(tenant.id)\n const cf = cfValues[rid] ?? {}\n return toRow(tenant, cf)\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.tenant',\n organizationId: null,\n tenantId: auth.tenantId ?? null,\n query: rawQuery,\n accessType: id ? 'read:item' : undefined,\n })\n\n return NextResponse.json({ items, total, page, pageSize, totalPages })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst tenantCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst tenantDeleteRequestSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst tenantGetDoc: OpenApiMethodDoc = {\n summary: 'List tenants',\n description: 'Returns tenants visible to the current user with optional search and pagination.',\n tags: [directoryTag],\n query: listQuerySchema,\n responses: [\n { status: 200, description: 'Paged list of tenants.', schema: tenantListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantPostDoc: OpenApiMethodDoc = {\n summary: 'Create tenant',\n description: 'Creates a new tenant and returns its identifier.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantCreateSchema,\n description: 'Tenant name and optional activation flag.',\n },\n responses: [\n { status: 201, description: 'Tenant created.', schema: tenantCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantPutDoc: OpenApiMethodDoc = {\n summary: 'Update tenant',\n description: 'Updates tenant properties such as name or activation state.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantUpdateSchema,\n description: 'Tenant identifier with fields to update.',\n },\n responses: [\n { status: 200, description: 'Tenant updated.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete tenant',\n description: 'Soft deletes the tenant identified by id.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantDeleteRequestSchema,\n description: 'Identifier of the tenant to remove.',\n },\n responses: [\n { status: 200, description: 'Tenant removed.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Manage tenants',\n methods: {\n GET: tenantGetDoc,\n POST: tenantPostDoc,\n PUT: tenantPutDoc,\n DELETE: tenantDeleteDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { tenantCreateSchema, tenantUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport { loadCustomFieldValues, buildCustomFieldFiltersFromQuery } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { tenantCrudEvents, tenantCrudIndexer } from '@open-mercato/core/modules/directory/commands/tenants'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { directoryTag, directoryErrorSchema, directoryOkSchema, tenantListResponseSchema } from '../openapi'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst listQuerySchema = z.object({\n id: z.string().uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n sortField: z.enum(['name', 'createdAt', 'updatedAt']).optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n isActive: z.enum(['true', 'false']).optional(),\n}).passthrough()\n\ntype TenantRow = {\n id: string\n name: string\n isActive: boolean\n createdAt: string | null\n updatedAt: string | null\n} & Record<string, unknown>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['directory.tenants.view'] },\n POST: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst rawBodySchema = z.object({}).passthrough()\ntype CrudInput = Record<string, unknown>\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: Tenant,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: tenantCrudEvents,\n indexer: tenantCrudIndexer,\n actions: {\n create: {\n commandId: 'directory.tenants.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'directory.tenants.update',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'directory.tenants.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nconst toRow = (tenant: Tenant, cf: Record<string, unknown>): TenantRow => ({\n id: String(tenant.id),\n name: String(tenant.name),\n isActive: !!tenant.isActive,\n createdAt: tenant.createdAt ? tenant.createdAt.toISOString() : null,\n updatedAt: tenant.updatedAt ? tenant.updatedAt.toISOString() : null,\n ...cf,\n})\n\nconst matchesValue = (current: unknown, expected: unknown): boolean => {\n if (Array.isArray(current)) return current.some((item) => item === expected)\n return current === expected\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 50, totalPages: 1 }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listQuerySchema.safeParse({\n id: url.searchParams.get('id') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField: url.searchParams.get('sortField') ?? undefined,\n sortDir: url.searchParams.get('sortDir') ?? undefined,\n isActive: url.searchParams.get('isActive') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json(\n { error: 'Invalid query parameters', details: parsed.error.flatten() },\n { status: 400 },\n )\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { id, page, pageSize, search, sortField, sortDir, isActive } = parsed.data\n const where: FilterQuery<Tenant> = { deletedAt: null }\n if (id) where.id = id\n if (search) {\n Object.assign(where, { name: { $ilike: `%${escapeLikePattern(search)}%` } })\n }\n const active = parseBooleanToken(isActive)\n if (active !== null) where.isActive = active\n\n const fieldMap: Record<string, string> = { name: 'name', createdAt: 'createdAt', updatedAt: 'updatedAt' }\n const orderBy: Record<string, 'ASC' | 'DESC'> = {}\n if (sortField) {\n const mapped = fieldMap[sortField] || 'name'\n orderBy[mapped] = sortDir === 'desc' ? 'DESC' : 'ASC'\n } else {\n orderBy.name = 'ASC'\n }\n\n const all = await em.find(Tenant, where, { orderBy })\n const recordIds = all.map((tenant) => String(tenant.id))\n\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const rid of recordIds) {\n tenantIdByRecord[rid] = null\n organizationIdByRecord[rid] = null\n }\n\n const cfValues = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.tenant,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks: auth.tenantId ? [auth.tenantId] : [],\n })\n : {}\n\n const rawQuery = Array.from(url.searchParams.keys()).reduce<Record<string, unknown>>((acc, key) => {\n const values = url.searchParams.getAll(key)\n if (!values.length) return acc\n acc[key] = values.length === 1 ? values[0] : values\n return acc\n }, {})\n\n const cfFilters = await buildCustomFieldFiltersFromQuery({\n entityId: E.directory.tenant,\n query: rawQuery,\n em,\n tenantId: auth.tenantId ?? null,\n })\n const cfFilterEntries = Object.entries(cfFilters).map(([rawKey, condition]) => {\n const normalizedKey = rawKey.startsWith('cf:') ? rawKey.slice(3) : rawKey.replace(/^cf_/, '')\n return [normalizedKey, condition] as const\n })\n\n const filtered = cfFilterEntries.length\n ? all.filter((tenant) => {\n const rid = String(tenant.id)\n const payload = cfValues[rid] ?? {}\n return cfFilterEntries.every(([key, expected]) => {\n const value = payload[`cf_${key}`]\n if (expected && typeof expected === 'object' && !Array.isArray(expected)) {\n const maybeIn = (expected as { $in?: unknown[] }).$in\n if (Array.isArray(maybeIn)) {\n if (value === undefined || value === null) return false\n if (Array.isArray(value)) return value.some((val) => maybeIn.includes(val))\n return maybeIn.includes(value)\n }\n }\n return matchesValue(value, expected)\n })\n })\n : all\n\n const total = filtered.length\n const start = (page - 1) * pageSize\n const paged = filtered.slice(start, start + pageSize)\n const items = paged.map((tenant) => {\n const rid = String(tenant.id)\n const cf = cfValues[rid] ?? {}\n return toRow(tenant, cf)\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.tenant',\n organizationId: null,\n tenantId: auth.tenantId ?? null,\n query: rawQuery,\n accessType: id ? 'read:item' : undefined,\n })\n\n return NextResponse.json({ items, total, page, pageSize, totalPages })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst tenantCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst tenantDeleteRequestSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst tenantGetDoc: OpenApiMethodDoc = {\n summary: 'List tenants',\n description: 'Returns tenants visible to the current user with optional search and pagination.',\n tags: [directoryTag],\n query: listQuerySchema,\n responses: [\n { status: 200, description: 'Paged list of tenants.', schema: tenantListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantPostDoc: OpenApiMethodDoc = {\n summary: 'Create tenant',\n description: 'Creates a new tenant and returns its identifier.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantCreateSchema,\n description: 'Tenant name and optional activation flag.',\n },\n responses: [\n { status: 201, description: 'Tenant created.', schema: tenantCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantPutDoc: OpenApiMethodDoc = {\n summary: 'Update tenant',\n description: 'Updates tenant properties such as name or activation state.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantUpdateSchema,\n description: 'Tenant identifier with fields to update.',\n },\n responses: [\n { status: 200, description: 'Tenant updated.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete tenant',\n description: 'Soft deletes the tenant identified by id.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantDeleteRequestSchema,\n description: 'Identifier of the tenant to remove.',\n },\n responses: [\n { status: 200, description: 'Tenant removed.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Manage tenants',\n methods: {\n GET: tenantGetDoc,\n POST: tenantPostDoc,\n PUT: tenantPutDoc,\n DELETE: tenantDeleteDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -50,7 +50,7 @@ function filterTenantsByStatus(list, status) {
|
|
|
50
50
|
async function fetchDirectoryTenants(status, errorMessage) {
|
|
51
51
|
const search = new URLSearchParams();
|
|
52
52
|
search.set("page", "1");
|
|
53
|
-
search.set("pageSize", "
|
|
53
|
+
search.set("pageSize", "100");
|
|
54
54
|
search.set("sortField", "name");
|
|
55
55
|
search.set("sortDir", "asc");
|
|
56
56
|
if (status === "active") search.set("isActive", "true");
|
|
@@ -112,10 +112,16 @@ const TenantSelect = React.forwardRef(function TenantSelect2({
|
|
|
112
112
|
let cancelled = false;
|
|
113
113
|
const loadTenants = async () => {
|
|
114
114
|
setFetchStatus("loading");
|
|
115
|
+
const autoSelect = (tenants) => {
|
|
116
|
+
if (!value && !includeEmptyOption && tenants.length > 0) {
|
|
117
|
+
onChange?.(tenants[0].id);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
115
120
|
try {
|
|
116
121
|
const tenants = await fetchDirectoryTenants(status, fetchErrorMessage);
|
|
117
122
|
if (cancelled) return;
|
|
118
123
|
setRemoteTenants(tenants);
|
|
124
|
+
autoSelect(tenants);
|
|
119
125
|
setFetchStatus("success");
|
|
120
126
|
return;
|
|
121
127
|
} catch {
|
|
@@ -123,6 +129,7 @@ const TenantSelect = React.forwardRef(function TenantSelect2({
|
|
|
123
129
|
const fallbackTenants = await fetchTenantsFromOrganizationSwitcher(status, fetchErrorMessage);
|
|
124
130
|
if (cancelled) return;
|
|
125
131
|
setRemoteTenants(fallbackTenants);
|
|
132
|
+
autoSelect(fallbackTenants);
|
|
126
133
|
setFetchStatus("success");
|
|
127
134
|
return;
|
|
128
135
|
} catch {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/directory/components/TenantSelect.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type TenantRecord = {\n id: string\n name: string\n isActive: boolean\n}\n\nfunction normalizeTenant(tenant: TenantRecord | null | undefined): TenantRecord | null {\n if (!tenant || typeof tenant.id !== 'string' || tenant.id.trim().length === 0) return null\n const id = tenant.id.trim()\n const name = typeof tenant.name === 'string' && tenant.name.trim().length > 0 ? tenant.name.trim() : id\n const isActive = tenant.isActive !== false\n return { id, name, isActive }\n}\n\nfunction sanitizeTenantList(list?: TenantRecord[] | null): TenantRecord[] {\n if (!Array.isArray(list)) return []\n const seen = new Map<string, TenantRecord>()\n for (const tenant of list) {\n const normalized = normalizeTenant(tenant)\n if (!normalized) continue\n seen.set(normalized.id, normalized)\n }\n return Array.from(seen.values())\n}\n\nfunction mergeTenantLists(...lists: TenantRecord[][]): TenantRecord[] {\n const map = new Map<string, TenantRecord>()\n for (const list of lists) {\n for (const tenant of list) {\n if (!tenant || typeof tenant.id !== 'string' || tenant.id.trim().length === 0) continue\n const id = tenant.id.trim()\n const existing = map.get(id)\n if (!existing) {\n map.set(id, {\n id,\n name: typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : id,\n isActive: tenant.isActive !== false,\n })\n } else {\n const name = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : existing.name\n const isActive = tenant.isActive !== undefined ? tenant.isActive !== false : existing.isActive\n map.set(id, { id, name, isActive })\n }\n }\n }\n return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name))\n}\n\nfunction filterTenantsByStatus(list: TenantRecord[], status: 'all' | 'active' | 'inactive'): TenantRecord[] {\n if (status === 'active') return list.filter((tenant: TenantRecord) => tenant.isActive)\n if (status === 'inactive') return list.filter((tenant: TenantRecord) => !tenant.isActive)\n return list\n}\n\nasync function fetchDirectoryTenants(status: 'all' | 'active' | 'inactive', errorMessage: string): Promise<TenantRecord[]> {\n const search = new URLSearchParams()\n search.set('page', '1')\n search.set('pageSize', '
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type TenantRecord = {\n id: string\n name: string\n isActive: boolean\n}\n\nfunction normalizeTenant(tenant: TenantRecord | null | undefined): TenantRecord | null {\n if (!tenant || typeof tenant.id !== 'string' || tenant.id.trim().length === 0) return null\n const id = tenant.id.trim()\n const name = typeof tenant.name === 'string' && tenant.name.trim().length > 0 ? tenant.name.trim() : id\n const isActive = tenant.isActive !== false\n return { id, name, isActive }\n}\n\nfunction sanitizeTenantList(list?: TenantRecord[] | null): TenantRecord[] {\n if (!Array.isArray(list)) return []\n const seen = new Map<string, TenantRecord>()\n for (const tenant of list) {\n const normalized = normalizeTenant(tenant)\n if (!normalized) continue\n seen.set(normalized.id, normalized)\n }\n return Array.from(seen.values())\n}\n\nfunction mergeTenantLists(...lists: TenantRecord[][]): TenantRecord[] {\n const map = new Map<string, TenantRecord>()\n for (const list of lists) {\n for (const tenant of list) {\n if (!tenant || typeof tenant.id !== 'string' || tenant.id.trim().length === 0) continue\n const id = tenant.id.trim()\n const existing = map.get(id)\n if (!existing) {\n map.set(id, {\n id,\n name: typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : id,\n isActive: tenant.isActive !== false,\n })\n } else {\n const name = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : existing.name\n const isActive = tenant.isActive !== undefined ? tenant.isActive !== false : existing.isActive\n map.set(id, { id, name, isActive })\n }\n }\n }\n return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name))\n}\n\nfunction filterTenantsByStatus(list: TenantRecord[], status: 'all' | 'active' | 'inactive'): TenantRecord[] {\n if (status === 'active') return list.filter((tenant: TenantRecord) => tenant.isActive)\n if (status === 'inactive') return list.filter((tenant: TenantRecord) => !tenant.isActive)\n return list\n}\n\nasync function fetchDirectoryTenants(status: 'all' | 'active' | 'inactive', errorMessage: string): Promise<TenantRecord[]> {\n const search = new URLSearchParams()\n search.set('page', '1')\n search.set('pageSize', '100')\n search.set('sortField', 'name')\n search.set('sortDir', 'asc')\n if (status === 'active') search.set('isActive', 'true')\n if (status === 'inactive') search.set('isActive', 'false')\n const json = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/directory/tenants?${search.toString()}`,\n undefined,\n { errorMessage, allowNullResult: true },\n )\n const items = Array.isArray(json?.items) ? json.items : []\n const normalized = items\n .map((item: unknown): TenantRecord | null => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const rawId = entry.id\n const id = typeof rawId === 'string' ? rawId : null\n if (!id || !id.length) return null\n const rawName = entry.name\n const name = typeof rawName === 'string' && rawName.length > 0 ? rawName : id\n const isActive = entry.isActive !== false\n return { id, name, isActive }\n })\n .filter((tenant: TenantRecord | null): tenant is TenantRecord => tenant !== null)\n return mergeTenantLists(filterTenantsByStatus(normalized, status))\n}\n\nasync function fetchTenantsFromOrganizationSwitcher(status: 'all' | 'active' | 'inactive', errorMessage: string): Promise<TenantRecord[]> {\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n '/api/directory/organization-switcher',\n undefined,\n { errorMessage, allowNullResult: true },\n )\n const json = payload ?? {}\n const rawTenants = Array.isArray(json.tenants) ? json.tenants : []\n const sanitized = sanitizeTenantList(rawTenants as TenantRecord[] | null)\n return mergeTenantLists(filterTenantsByStatus(sanitized, status))\n}\n\nexport type TenantSelectProps = {\n value?: string | null\n onChange?: (value: string | null) => void\n disabled?: boolean\n required?: boolean\n className?: string\n id?: string\n name?: string\n includeEmptyOption?: boolean\n emptyOptionLabel?: string\n fetchOnMount?: boolean\n status?: 'all' | 'active' | 'inactive'\n tenants?: TenantRecord[] | null\n}\n\nexport const TenantSelect = React.forwardRef<HTMLSelectElement, TenantSelectProps>(function TenantSelect(\n {\n value,\n onChange,\n disabled = false,\n required = false,\n className,\n id,\n name,\n includeEmptyOption = false,\n emptyOptionLabel,\n fetchOnMount = true,\n status = 'all',\n tenants: providedTenantsInput = null,\n },\n ref,\n) {\n const t = useT()\n const providedTenants = React.useMemo(() => sanitizeTenantList(providedTenantsInput), [providedTenantsInput])\n const [remoteTenants, setRemoteTenants] = React.useState<TenantRecord[]>([])\n const [fetchStatus, setFetchStatus] = React.useState<'idle' | 'loading' | 'success' | 'error'>(fetchOnMount ? 'loading' : 'idle')\n\n React.useEffect(() => {\n const fetchErrorMessage = t('tenantSelect.error', 'Failed to load tenants')\n if (!fetchOnMount) {\n setFetchStatus((prev) => (prev === 'loading' ? 'idle' : prev))\n return\n }\n let cancelled = false\n const loadTenants = async () => {\n setFetchStatus('loading')\n const autoSelect = (tenants: TenantRecord[]) => {\n if (!value && !includeEmptyOption && tenants.length > 0) {\n onChange?.(tenants[0].id)\n }\n }\n try {\n const tenants = await fetchDirectoryTenants(status, fetchErrorMessage)\n if (cancelled) return\n setRemoteTenants(tenants)\n autoSelect(tenants)\n setFetchStatus('success')\n return\n } catch {\n try {\n const fallbackTenants = await fetchTenantsFromOrganizationSwitcher(status, fetchErrorMessage)\n if (cancelled) return\n setRemoteTenants(fallbackTenants)\n autoSelect(fallbackTenants)\n setFetchStatus('success')\n return\n } catch {\n if (cancelled) return\n setFetchStatus('error')\n }\n }\n }\n void loadTenants()\n return () => { cancelled = true }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- value/onChange/includeEmptyOption are read once at fetch time for auto-select\n }, [fetchOnMount, status, t])\n\n const mergedTenants = React.useMemo(\n () => mergeTenantLists(providedTenants, remoteTenants),\n [providedTenants, remoteTenants],\n )\n\n const fallbackTenant = React.useMemo(() => {\n const tenantId = typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n if (!tenantId) return null\n if (mergedTenants.some((tenant) => tenant.id === tenantId)) return null\n return { id: tenantId, name: tenantId, isActive: true }\n }, [mergedTenants, value])\n\n const tenantsForRender = React.useMemo(\n () => (fallbackTenant ? mergeTenantLists(mergedTenants, [fallbackTenant]) : mergedTenants),\n [mergedTenants, fallbackTenant],\n )\n\n const showLoading = fetchStatus === 'loading' && tenantsForRender.length === 0\n const showError = fetchStatus === 'error' && tenantsForRender.length === 0\n\n const handleChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {\n if (!onChange) return\n const next = event.target.value\n onChange(next ? next : null)\n }, [onChange])\n\n const selectValue = value ?? ''\n const resolvedEmptyOptionLabel = emptyOptionLabel ?? t('tenantSelect.empty')\n const loadingLabel = t('tenantSelect.loading')\n const errorLabel = t('tenantSelect.error')\n const inactiveSuffix = ` (${t('tenantSelect.inactive')})`\n\n return (\n <select\n ref={ref}\n id={id}\n name={name}\n value={selectValue}\n onChange={handleChange}\n disabled={disabled || (showError && tenantsForRender.length === 0)}\n required={required}\n className={className}\n >\n {includeEmptyOption ? (\n <option value=\"\">\n {resolvedEmptyOptionLabel}\n </option>\n ) : null}\n {showLoading ? (\n <option value=\"\" disabled>{loadingLabel}</option>\n ) : null}\n {showError ? (\n <option value=\"\" disabled>{errorLabel}</option>\n ) : null}\n {!showLoading && tenantsForRender.length > 0\n ? tenantsForRender.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name}{tenant.isActive ? '' : inactiveSuffix}\n </option>\n ))\n : null}\n </select>\n )\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["TenantSelect"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.6-develop-
|
|
3
|
+
"version": "0.4.6-develop-bbfd75b1c8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.6-develop-
|
|
210
|
+
"@open-mercato/shared": "0.4.6-develop-bbfd75b1c8",
|
|
211
211
|
"@types/html-to-text": "^9.0.4",
|
|
212
212
|
"@types/semver": "^7.5.8",
|
|
213
213
|
"@xyflow/react": "^12.6.0",
|
|
@@ -99,7 +99,7 @@ export default function CurrenciesPage() {
|
|
|
99
99
|
const handleSetBase = React.useCallback(
|
|
100
100
|
async (row: CurrencyRow) => {
|
|
101
101
|
try {
|
|
102
|
-
const call = await apiCall('/api/currencies', {
|
|
102
|
+
const call = await apiCall('/api/currencies/currencies', {
|
|
103
103
|
method: 'PUT',
|
|
104
104
|
headers: { 'Content-Type': 'application/json' },
|
|
105
105
|
body: JSON.stringify({ id: row.id, isBase: true }),
|
|
@@ -109,7 +109,10 @@ export async function GET(req: Request) {
|
|
|
109
109
|
isActive: url.searchParams.get('isActive') ?? undefined,
|
|
110
110
|
})
|
|
111
111
|
if (!parsed.success) {
|
|
112
|
-
return NextResponse.json(
|
|
112
|
+
return NextResponse.json(
|
|
113
|
+
{ error: 'Invalid query parameters', details: parsed.error.flatten() },
|
|
114
|
+
{ status: 400 },
|
|
115
|
+
)
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
const container = await createRequestContainer()
|
|
@@ -60,7 +60,7 @@ function filterTenantsByStatus(list: TenantRecord[], status: 'all' | 'active' |
|
|
|
60
60
|
async function fetchDirectoryTenants(status: 'all' | 'active' | 'inactive', errorMessage: string): Promise<TenantRecord[]> {
|
|
61
61
|
const search = new URLSearchParams()
|
|
62
62
|
search.set('page', '1')
|
|
63
|
-
search.set('pageSize', '
|
|
63
|
+
search.set('pageSize', '100')
|
|
64
64
|
search.set('sortField', 'name')
|
|
65
65
|
search.set('sortDir', 'asc')
|
|
66
66
|
if (status === 'active') search.set('isActive', 'true')
|
|
@@ -145,10 +145,16 @@ export const TenantSelect = React.forwardRef<HTMLSelectElement, TenantSelectProp
|
|
|
145
145
|
let cancelled = false
|
|
146
146
|
const loadTenants = async () => {
|
|
147
147
|
setFetchStatus('loading')
|
|
148
|
+
const autoSelect = (tenants: TenantRecord[]) => {
|
|
149
|
+
if (!value && !includeEmptyOption && tenants.length > 0) {
|
|
150
|
+
onChange?.(tenants[0].id)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
148
153
|
try {
|
|
149
154
|
const tenants = await fetchDirectoryTenants(status, fetchErrorMessage)
|
|
150
155
|
if (cancelled) return
|
|
151
156
|
setRemoteTenants(tenants)
|
|
157
|
+
autoSelect(tenants)
|
|
152
158
|
setFetchStatus('success')
|
|
153
159
|
return
|
|
154
160
|
} catch {
|
|
@@ -156,6 +162,7 @@ export const TenantSelect = React.forwardRef<HTMLSelectElement, TenantSelectProp
|
|
|
156
162
|
const fallbackTenants = await fetchTenantsFromOrganizationSwitcher(status, fetchErrorMessage)
|
|
157
163
|
if (cancelled) return
|
|
158
164
|
setRemoteTenants(fallbackTenants)
|
|
165
|
+
autoSelect(fallbackTenants)
|
|
159
166
|
setFetchStatus('success')
|
|
160
167
|
return
|
|
161
168
|
} catch {
|
|
@@ -166,6 +173,7 @@ export const TenantSelect = React.forwardRef<HTMLSelectElement, TenantSelectProp
|
|
|
166
173
|
}
|
|
167
174
|
void loadTenants()
|
|
168
175
|
return () => { cancelled = true }
|
|
176
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- value/onChange/includeEmptyOption are read once at fetch time for auto-select
|
|
169
177
|
}, [fetchOnMount, status, t])
|
|
170
178
|
|
|
171
179
|
const mergedTenants = React.useMemo(
|