create-davepi-ui 0.1.0 → 0.3.0
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/README.md +4 -4
- package/bin/index.js +10 -8
- package/bin/sync-templates.js +8 -0
- package/package.json +1 -1
- package/templates/default/index.html +17 -1
- package/templates/default/src/components/AppShell.tsx +2 -0
- package/templates/default/src/components/ThemeToggle.tsx +78 -0
- package/templates/default/src/index.css +39 -31
- package/templates/default/src/pages/ResourceDetailPage.tsx +29 -4
- package/templates/default/src/resources/.gitkeep +0 -0
- package/templates/pinned-versions.json +1 -1
- package/templates/default/src/resources/account.ts +0 -25
- package/templates/default/src/resources/category.ts +0 -7
- package/templates/default/src/resources/contact.ts +0 -40
- package/templates/default/src/resources/product.ts +0 -7
- package/templates/default/src/resources/project.ts +0 -7
- package/templates/default/src/resources/quote.ts +0 -12
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Scaffolder for new [davepi-ui](https://github.com/projik/davepi-ui) admin projec
|
|
|
7
7
|
```bash
|
|
8
8
|
npx create-davepi-ui my-admin --api-url http://localhost:4001
|
|
9
9
|
cd my-admin
|
|
10
|
-
|
|
10
|
+
npm run dev
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Flags
|
|
@@ -15,14 +15,14 @@ pnpm dev
|
|
|
15
15
|
| Flag | Default | Purpose |
|
|
16
16
|
|---|---|---|
|
|
17
17
|
| `--api-url <url>` | `http://localhost:4001` | davepi backend base URL written to `.env` |
|
|
18
|
-
| `--no-install` | off | Skip post-scaffold `
|
|
18
|
+
| `--no-install` | off | Skip post-scaffold `npm install` |
|
|
19
19
|
|
|
20
20
|
## What it does
|
|
21
21
|
|
|
22
|
-
1. Copies the bundled template (Vite + React Router + shadcn + `@davepi/ui-react`).
|
|
22
|
+
1. Copies the bundled template (Vite + React Router + shadcn + `@davepi/ui-react`). Lock files are filtered out so the scaffolded project picks its own package manager — npm by default, matching the davepi backend.
|
|
23
23
|
2. Rewrites `package.json` — pins `@davepi/ui-*` deps to the published versions matching this scaffolder, sets `private: true`, drops upstream repo metadata.
|
|
24
24
|
3. Writes `.env` with `VITE_API_URL`.
|
|
25
|
-
4. Runs `
|
|
25
|
+
4. Runs `npm install` (unless `--no-install`).
|
|
26
26
|
|
|
27
27
|
## License
|
|
28
28
|
|
package/bin/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - rewrites `package.json` with the new project name + pinned
|
|
10
10
|
* published versions of @davepi/ui-* (no workspace: protocol)
|
|
11
11
|
* - writes `.env` carrying VITE_API_URL
|
|
12
|
-
* - runs `
|
|
12
|
+
* - runs `npm install` (skip with --no-install)
|
|
13
13
|
* - prints the next three commands
|
|
14
14
|
*
|
|
15
15
|
* No `inquirer`, no progress bars — keeps the failure surface small.
|
|
@@ -58,7 +58,7 @@ function usage() {
|
|
|
58
58
|
out('');
|
|
59
59
|
out('Flags:');
|
|
60
60
|
out(' --api-url <url> davepi backend base URL (default: http://localhost:4001)');
|
|
61
|
-
out(' --no-install skip the post-scaffold `
|
|
61
|
+
out(' --no-install skip the post-scaffold `npm install`');
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function copyTree(src, dst, skip) {
|
|
@@ -198,16 +198,18 @@ function main() {
|
|
|
198
198
|
rewritePackageJson(pkgPath, name, resolvePinnedVersions());
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
// Write a starter `.env` so the user can run `
|
|
201
|
+
// Write a starter `.env` so the user can run `npm run dev` immediately.
|
|
202
202
|
fs.writeFileSync(path.join(target, '.env'), `VITE_API_URL=${apiUrl}\n`);
|
|
203
203
|
|
|
204
204
|
if (!skipInstall) {
|
|
205
205
|
out('Installing dependencies…');
|
|
206
206
|
// spawnSync with an argument array — no shell, no interpolation,
|
|
207
|
-
// so the only program ever invoked is the literal `
|
|
208
|
-
|
|
207
|
+
// so the only program ever invoked is the literal `npm`. npm ships
|
|
208
|
+
// as `.cmd` shim on Windows, hence the platform-specific name.
|
|
209
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
210
|
+
const r = spawnSync(npmCmd, ['install'], { cwd: target, stdio: 'inherit' });
|
|
209
211
|
if (r.status !== 0) {
|
|
210
|
-
err('
|
|
212
|
+
err('npm install failed. You can re-run it from the project directory.');
|
|
211
213
|
}
|
|
212
214
|
}
|
|
213
215
|
|
|
@@ -215,8 +217,8 @@ function main() {
|
|
|
215
217
|
out('Done.');
|
|
216
218
|
out('');
|
|
217
219
|
out(` cd ${name}`);
|
|
218
|
-
if (skipInstall) out('
|
|
219
|
-
out('
|
|
220
|
+
if (skipInstall) out(' npm install');
|
|
221
|
+
out(' npm run dev');
|
|
220
222
|
out('');
|
|
221
223
|
out(`Backend expected at ${apiUrl}. Edit .env to point elsewhere.`);
|
|
222
224
|
}
|
package/bin/sync-templates.js
CHANGED
|
@@ -45,6 +45,14 @@ const SKIP = new Set([
|
|
|
45
45
|
'.astro',
|
|
46
46
|
'.env',
|
|
47
47
|
'.env.local',
|
|
48
|
+
// Skip lock files — davepi-ui itself uses pnpm because it's a
|
|
49
|
+
// monorepo, but the scaffolded admin is a plain Vite app that uses
|
|
50
|
+
// npm. Keeps the package-manager surface aligned with the davepi
|
|
51
|
+
// backend the user already runs via `npm install`. The scaffolded
|
|
52
|
+
// `npm install` writes a fresh package-lock.json.
|
|
53
|
+
'pnpm-lock.yaml',
|
|
54
|
+
'yarn.lock',
|
|
55
|
+
'package-lock.json',
|
|
48
56
|
]);
|
|
49
57
|
|
|
50
58
|
function copyTree(src, dst) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en"
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>davepi-ui</title>
|
|
8
|
+
<script>
|
|
9
|
+
// FOUC guard: read theme preference + apply class before paint
|
|
10
|
+
// so the very first frame already matches the user's choice.
|
|
11
|
+
// Lookup order: explicit localStorage choice → system preference.
|
|
12
|
+
// Values: 'light' | 'dark' | 'system' (default).
|
|
13
|
+
(function () {
|
|
14
|
+
try {
|
|
15
|
+
var pref = localStorage.getItem('davepi-theme') || 'system';
|
|
16
|
+
var prefersDark =
|
|
17
|
+
pref === 'dark' ||
|
|
18
|
+
(pref === 'system' &&
|
|
19
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
20
|
+
document.documentElement.classList.toggle('dark', prefersDark);
|
|
21
|
+
} catch (e) {}
|
|
22
|
+
})();
|
|
23
|
+
</script>
|
|
8
24
|
</head>
|
|
9
25
|
<body class="bg-background text-foreground">
|
|
10
26
|
<div id="root"></div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Outlet } from 'react-router-dom';
|
|
2
2
|
import { UserMenu } from '@davepi/ui-react';
|
|
3
3
|
import { Sidebar } from './Sidebar';
|
|
4
|
+
import { ThemeToggle } from './ThemeToggle';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Two-column shell: sidebar nav (resource list from describe) + main outlet.
|
|
@@ -12,6 +13,7 @@ export function AppShell() {
|
|
|
12
13
|
<Sidebar />
|
|
13
14
|
<div className="flex flex-1 flex-col overflow-hidden">
|
|
14
15
|
<header className="flex h-14 items-center justify-end gap-3 border-b border-border px-6">
|
|
16
|
+
<ThemeToggle />
|
|
15
17
|
<UserMenu className="flex items-center gap-3 text-sm" />
|
|
16
18
|
</header>
|
|
17
19
|
<main className="flex-1 overflow-auto p-6">
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Moon, Sun, Laptop } from 'lucide-react';
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
import {
|
|
5
|
+
DropdownMenu,
|
|
6
|
+
DropdownMenuContent,
|
|
7
|
+
DropdownMenuItem,
|
|
8
|
+
DropdownMenuTrigger,
|
|
9
|
+
} from '@/components/ui/dropdown-menu';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Light / dark / system-preference toggle. Persisted in localStorage
|
|
13
|
+
* under `davepi-theme`. The index.html FOUC-guard script reads the
|
|
14
|
+
* same key on first paint, so the user's choice survives reload
|
|
15
|
+
* without a flash of the wrong theme.
|
|
16
|
+
*/
|
|
17
|
+
type Theme = 'light' | 'dark' | 'system';
|
|
18
|
+
|
|
19
|
+
const STORAGE_KEY = 'davepi-theme';
|
|
20
|
+
|
|
21
|
+
function applyTheme(theme: Theme) {
|
|
22
|
+
const prefersDark =
|
|
23
|
+
theme === 'dark' ||
|
|
24
|
+
(theme === 'system' &&
|
|
25
|
+
typeof window !== 'undefined' &&
|
|
26
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
27
|
+
document.documentElement.classList.toggle('dark', prefersDark);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readTheme(): Theme {
|
|
31
|
+
if (typeof window === 'undefined') return 'system';
|
|
32
|
+
const v = window.localStorage.getItem(STORAGE_KEY);
|
|
33
|
+
if (v === 'light' || v === 'dark' || v === 'system') return v;
|
|
34
|
+
return 'system';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function ThemeToggle() {
|
|
38
|
+
const [theme, setTheme] = useState<Theme>(() => readTheme());
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
applyTheme(theme);
|
|
42
|
+
if (typeof window !== 'undefined') {
|
|
43
|
+
window.localStorage.setItem(STORAGE_KEY, theme);
|
|
44
|
+
}
|
|
45
|
+
}, [theme]);
|
|
46
|
+
|
|
47
|
+
// Listen for OS-level preference changes when in system mode so the
|
|
48
|
+
// UI tracks the user's day-night transition.
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (theme !== 'system' || typeof window === 'undefined') return;
|
|
51
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
52
|
+
const handler = () => applyTheme('system');
|
|
53
|
+
mq.addEventListener('change', handler);
|
|
54
|
+
return () => mq.removeEventListener('change', handler);
|
|
55
|
+
}, [theme]);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<DropdownMenu>
|
|
59
|
+
<DropdownMenuTrigger asChild>
|
|
60
|
+
<Button variant="ghost" size="icon" aria-label="Toggle theme">
|
|
61
|
+
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
62
|
+
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
63
|
+
</Button>
|
|
64
|
+
</DropdownMenuTrigger>
|
|
65
|
+
<DropdownMenuContent align="end">
|
|
66
|
+
<DropdownMenuItem onClick={() => setTheme('light')}>
|
|
67
|
+
<Sun className="mr-2 h-4 w-4" /> Light
|
|
68
|
+
</DropdownMenuItem>
|
|
69
|
+
<DropdownMenuItem onClick={() => setTheme('dark')}>
|
|
70
|
+
<Moon className="mr-2 h-4 w-4" /> Dark
|
|
71
|
+
</DropdownMenuItem>
|
|
72
|
+
<DropdownMenuItem onClick={() => setTheme('system')}>
|
|
73
|
+
<Laptop className="mr-2 h-4 w-4" /> System
|
|
74
|
+
</DropdownMenuItem>
|
|
75
|
+
</DropdownMenuContent>
|
|
76
|
+
</DropdownMenu>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -4,44 +4,52 @@
|
|
|
4
4
|
|
|
5
5
|
@layer base {
|
|
6
6
|
:root {
|
|
7
|
+
/* Light theme — slate neutrals + indigo accent.
|
|
8
|
+
Friendlier than pure black-on-white. */
|
|
7
9
|
--background: 0 0% 100%;
|
|
8
|
-
--foreground:
|
|
10
|
+
--foreground: 222 47% 11%;
|
|
9
11
|
--card: 0 0% 100%;
|
|
10
|
-
--card-foreground:
|
|
11
|
-
--
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
12
|
+
--card-foreground: 222 47% 11%;
|
|
13
|
+
--popover: 0 0% 100%;
|
|
14
|
+
--popover-foreground: 222 47% 11%;
|
|
15
|
+
--primary: 224 76% 48%;
|
|
16
|
+
--primary-foreground: 0 0% 100%;
|
|
17
|
+
--secondary: 210 40% 96%;
|
|
18
|
+
--secondary-foreground: 222 47% 11%;
|
|
19
|
+
--muted: 210 40% 96%;
|
|
20
|
+
--muted-foreground: 215 16% 47%;
|
|
21
|
+
--accent: 210 40% 96%;
|
|
22
|
+
--accent-foreground: 222 47% 11%;
|
|
23
|
+
--destructive: 0 72% 51%;
|
|
20
24
|
--destructive-foreground: 0 0% 98%;
|
|
21
|
-
--border:
|
|
22
|
-
--input:
|
|
23
|
-
--ring:
|
|
25
|
+
--border: 214 32% 91%;
|
|
26
|
+
--input: 214 32% 91%;
|
|
27
|
+
--ring: 224 76% 48%;
|
|
24
28
|
--radius: 0.5rem;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
.dark {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
--
|
|
31
|
-
--
|
|
32
|
-
--
|
|
33
|
-
--
|
|
34
|
-
--
|
|
35
|
-
--
|
|
36
|
-
--
|
|
37
|
-
--
|
|
38
|
-
--
|
|
39
|
-
--
|
|
40
|
-
--
|
|
41
|
-
--
|
|
42
|
-
--
|
|
43
|
-
--
|
|
44
|
-
--
|
|
32
|
+
/* Dark theme — slate base instead of near-black, indigo accent
|
|
33
|
+
carried over. Less screen-bleach than pure-monochrome. */
|
|
34
|
+
--background: 222 47% 11%;
|
|
35
|
+
--foreground: 210 40% 98%;
|
|
36
|
+
--card: 222 47% 13%;
|
|
37
|
+
--card-foreground: 210 40% 98%;
|
|
38
|
+
--popover: 222 47% 13%;
|
|
39
|
+
--popover-foreground: 210 40% 98%;
|
|
40
|
+
--primary: 224 76% 60%;
|
|
41
|
+
--primary-foreground: 222 47% 11%;
|
|
42
|
+
--secondary: 217 33% 18%;
|
|
43
|
+
--secondary-foreground: 210 40% 98%;
|
|
44
|
+
--muted: 217 33% 18%;
|
|
45
|
+
--muted-foreground: 215 20% 65%;
|
|
46
|
+
--accent: 217 33% 18%;
|
|
47
|
+
--accent-foreground: 210 40% 98%;
|
|
48
|
+
--destructive: 0 63% 51%;
|
|
49
|
+
--destructive-foreground: 210 40% 98%;
|
|
50
|
+
--border: 217 33% 22%;
|
|
51
|
+
--input: 217 33% 22%;
|
|
52
|
+
--ring: 224 76% 60%;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
* {
|
|
@@ -51,9 +51,24 @@ export function ResourceDetailPage() {
|
|
|
51
51
|
|
|
52
52
|
const preview = describe.registry.preview(path, record.data);
|
|
53
53
|
const visibleFields = entry.fields.filter((f) => !SERVER_STAMPED.has(f.name));
|
|
54
|
-
|
|
54
|
+
// Suppress redundant `hasOne` tabs: when a parent declares both
|
|
55
|
+
// `hasMany: contact` and `hasOne: contact` against the same FK
|
|
56
|
+
// (e.g. `primaryContact` for "the one flagged as primary"), the
|
|
57
|
+
// hasMany tab already lists every contact including the primary —
|
|
58
|
+
// a separate Primary Contact tab is UX clutter, not information.
|
|
59
|
+
// Skip the hasOne when an equivalent hasMany exists; the user can
|
|
60
|
+
// sort / filter the main list to find the primary.
|
|
61
|
+
const allChildRelations = describe.registry
|
|
55
62
|
.relations(path)
|
|
56
63
|
.filter((r) => r.kind === 'hasMany' || r.kind === 'hasOne');
|
|
64
|
+
const hasManyKeys = new Set(
|
|
65
|
+
allChildRelations
|
|
66
|
+
.filter((r) => r.kind === 'hasMany')
|
|
67
|
+
.map((r) => `${r.target}:${r.foreignKey}`)
|
|
68
|
+
);
|
|
69
|
+
const childRelations = allChildRelations.filter(
|
|
70
|
+
(r) => r.kind !== 'hasOne' || !hasManyKeys.has(`${r.target}:${r.foreignKey}`)
|
|
71
|
+
);
|
|
57
72
|
|
|
58
73
|
const detailsBlock = (
|
|
59
74
|
<section className="rounded-md border border-border bg-card">
|
|
@@ -104,10 +119,20 @@ export function ResourceDetailPage() {
|
|
|
104
119
|
<TabsTrigger value="details">Details</TabsTrigger>
|
|
105
120
|
{childRelations.map((rel) => {
|
|
106
121
|
const target = describe.registry.display(rel.target);
|
|
107
|
-
|
|
122
|
+
// Include `rel.name` in the key — without it, a parent
|
|
123
|
+
// that declares both `contacts: hasMany` and
|
|
124
|
+
// `primaryContact: hasOne` on the same target/FK pair
|
|
125
|
+
// would collide on a `target:foreignKey`-only key.
|
|
126
|
+
// The label uses the relation name (humanised) instead
|
|
127
|
+
// of the bare target plural so the tabs read distinctly.
|
|
128
|
+
const key = `${rel.name}:${rel.target}:${rel.foreignKey}`;
|
|
129
|
+
const tabLabel =
|
|
130
|
+
rel.kind === 'hasOne'
|
|
131
|
+
? labelize(rel.name)
|
|
132
|
+
: target.pluralLabel;
|
|
108
133
|
return (
|
|
109
134
|
<TabsTrigger key={key} value={key}>
|
|
110
|
-
{
|
|
135
|
+
{tabLabel}
|
|
111
136
|
</TabsTrigger>
|
|
112
137
|
);
|
|
113
138
|
})}
|
|
@@ -116,7 +141,7 @@ export function ResourceDetailPage() {
|
|
|
116
141
|
{detailsBlock}
|
|
117
142
|
</TabsContent>
|
|
118
143
|
{childRelations.map((rel) => {
|
|
119
|
-
const key = `${rel.target}:${rel.foreignKey}`;
|
|
144
|
+
const key = `${rel.name}:${rel.target}:${rel.foreignKey}`;
|
|
120
145
|
return (
|
|
121
146
|
<TabsContent key={key} value={key} className="mt-4">
|
|
122
147
|
<RelatedList
|
|
File without changes
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { ResourceConfig } from '@davepi/ui-core';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Account override. Backend (`/_describe`) supplies label / pluralLabel
|
|
5
|
-
* / displayField, so this file only carries the bits the backend can't
|
|
6
|
-
* know about: sidebar category, table columns, bulk actions.
|
|
7
|
-
*/
|
|
8
|
-
const config: ResourceConfig = {
|
|
9
|
-
category: 'CRM',
|
|
10
|
-
listColumns: [
|
|
11
|
-
{ field: 'accountName', label: 'Account name' },
|
|
12
|
-
{ field: 'description', label: 'Notes' },
|
|
13
|
-
],
|
|
14
|
-
actions: {
|
|
15
|
-
bulk: [
|
|
16
|
-
{
|
|
17
|
-
id: 'bulk-delete',
|
|
18
|
-
label: 'Delete selected',
|
|
19
|
-
kind: 'bulkDelete',
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export default config;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { ResourceConfig } from '@davepi/ui-core';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Contact override. Form sections and explicit list columns are the
|
|
5
|
-
* UI-only bits — labels / pluralLabel / displayField come straight
|
|
6
|
-
* from the backend manifest.
|
|
7
|
-
*/
|
|
8
|
-
const config: ResourceConfig = {
|
|
9
|
-
category: 'CRM',
|
|
10
|
-
listColumns: [
|
|
11
|
-
{ field: 'first_name' },
|
|
12
|
-
{ field: 'last_name' },
|
|
13
|
-
{ field: 'email' },
|
|
14
|
-
{ field: 'phone' },
|
|
15
|
-
],
|
|
16
|
-
formSections: [
|
|
17
|
-
{
|
|
18
|
-
title: 'Identity',
|
|
19
|
-
fields: [{ field: 'first_name' }, { field: 'last_name' }, { field: 'email' }],
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
title: 'Contact',
|
|
23
|
-
fields: [{ field: 'phone' }, { field: 'mobile' }, { field: 'company' }],
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
title: 'Address',
|
|
27
|
-
description: 'Postal address used for invoices and correspondence.',
|
|
28
|
-
fields: [
|
|
29
|
-
{ field: 'address1' },
|
|
30
|
-
{ field: 'address2' },
|
|
31
|
-
{ field: 'suburb' },
|
|
32
|
-
{ field: 'state' },
|
|
33
|
-
{ field: 'postcode' },
|
|
34
|
-
{ field: 'country' },
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export default config;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ResourceConfig } from '@davepi/ui-core';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Quote override. Only the sidebar category is consumer-supplied —
|
|
5
|
-
* everything else (labels, displayField, contactId relation, etc.)
|
|
6
|
-
* comes from the backend manifest.
|
|
7
|
-
*/
|
|
8
|
-
const config: ResourceConfig = {
|
|
9
|
-
category: 'CRM',
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default config;
|