only allow one one-to-one record
This commit is contained in:
parent
85af8a5859
commit
7e0ef77158
@ -8,6 +8,7 @@
|
||||
name: string
|
||||
identifier: string
|
||||
type: string
|
||||
relationshipType?: 'ONE_TO_ONE' | 'ONE_TO_MANY'
|
||||
}
|
||||
interface LinkedEntity {
|
||||
entity: { id: number, name: string, identifier: string, fields: Field[] }
|
||||
@ -36,6 +37,14 @@
|
||||
return linked.entity.fields.filter(field => field.id !== linked.relationshipField.id)
|
||||
}
|
||||
|
||||
function isOneToOne (linked: LinkedEntity) {
|
||||
return linked.relationshipField.relationshipType === 'ONE_TO_ONE'
|
||||
}
|
||||
|
||||
function createLabel (linked: LinkedEntity) {
|
||||
return `${isOneToOne(linked) ? 'Create' : 'Add'} ${linked.entity.name}`
|
||||
}
|
||||
|
||||
function inputType (field: Field) {
|
||||
if (['NUMBER', 'RELATIONSHIP', 'USER'].includes(field.type)) return 'number'
|
||||
if (field.type === 'DATE') return 'date'
|
||||
@ -136,15 +145,22 @@
|
||||
<div class="entity-heading">
|
||||
<div>
|
||||
<h2>{{ linked.entity.name }}</h2>
|
||||
<p>{{ linked.records.length }} linked {{ linked.records.length === 1 ? 'record' : 'records' }}</p>
|
||||
<p v-if="isOneToOne(linked)">{{ linked.records.length === 0 ? 'Not created' : 'Created' }}</p>
|
||||
<p v-else>{{ linked.records.length }} linked {{ linked.records.length === 1 ? 'record' : 'records' }}</p>
|
||||
</div>
|
||||
|
||||
<button type="button" @click="startAddingChild(linked)">Add {{ linked.entity.name }}</button>
|
||||
<button
|
||||
v-if="!isOneToOne(linked) || linked.records.length === 0"
|
||||
type="button"
|
||||
@click="startAddingChild(linked)"
|
||||
>
|
||||
{{ createLabel(linked) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
v-if="addingChildKey === childKey(linked)"
|
||||
:aria-label="`Add ${linked.entity.name}`"
|
||||
:aria-label="createLabel(linked)"
|
||||
class="child-form"
|
||||
@submit.prevent="addChild(linked)"
|
||||
>
|
||||
@ -157,12 +173,21 @@
|
||||
</label>
|
||||
|
||||
<div class="form-actions">
|
||||
<button :disabled="saving" type="submit">{{ saving ? 'Adding…' : `Add ${linked.entity.name}` }}</button>
|
||||
<button :disabled="saving" type="submit">{{ saving ? 'Saving…' : createLabel(linked) }}</button>
|
||||
<button :disabled="saving" type="button" @click="addingChildKey = ''">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p v-if="linked.records.length === 0" class="no-records">No records linked to this user.</p>
|
||||
<p v-if="linked.records.length === 0" class="no-records">
|
||||
{{ isOneToOne(linked) ? `No ${linked.entity.name} has been created.` : 'No records linked to this user.' }}
|
||||
</p>
|
||||
|
||||
<dl v-else-if="isOneToOne(linked)" class="record-details">
|
||||
<template v-for="field in linked.entity.fields" :key="field.id">
|
||||
<dt>{{ field.name }}</dt>
|
||||
<dd>{{ displayValue(linked.records[0][field.identifier]) }}</dd>
|
||||
</template>
|
||||
</dl>
|
||||
|
||||
<div v-else class="table-scroll">
|
||||
<table>
|
||||
@ -217,6 +242,9 @@
|
||||
.entity-heading p { margin: 0.35rem 0 0; font-size: 0.9rem; }
|
||||
.empty-state, .no-records { padding: 1.5rem; }
|
||||
.table-scroll { overflow-x: auto; }
|
||||
.record-details { display: grid; grid-template-columns: minmax(8rem, 12rem) 1fr; margin: 0; padding: 1.25rem; gap: 0.75rem 1rem; }
|
||||
.record-details dt { color: var(--v0-on-surface-variant); font-size: 0.8rem; font-weight: 700; text-transform: uppercase; }
|
||||
.record-details dd { margin: 0; }
|
||||
table { width: 100%; border-collapse: collapse; text-align: left; white-space: nowrap; }
|
||||
th, td { padding: 0.85rem 1rem; border-bottom: 1px solid var(--v0-divider); }
|
||||
th { color: var(--v0-on-surface-variant); font-size: 0.75rem; text-transform: uppercase; }
|
||||
|
||||
@ -16,6 +16,7 @@ test('admin opens a user file and views a linked entity record', async ({ page }
|
||||
const tasks: Record<string, unknown>[] = []
|
||||
const notes: Record<string, unknown>[] = []
|
||||
const milestones: Record<string, unknown>[] = []
|
||||
const profiles: Record<string, unknown>[] = []
|
||||
const projectResponse = () => ({
|
||||
entity: {
|
||||
id: 3,
|
||||
@ -107,6 +108,24 @@ test('admin opens a user file and views a linked entity record', async ({ page }
|
||||
},
|
||||
relationshipField: { id: 12, name: 'Owner', identifier: 'owner', type: 'USER' },
|
||||
records: projects,
|
||||
}, {
|
||||
entity: {
|
||||
id: 8,
|
||||
name: 'Profile',
|
||||
identifier: 'profile',
|
||||
fields: [
|
||||
{ id: 50, name: 'Biography', identifier: 'biography', type: 'TEXT' },
|
||||
{ id: 51, name: 'User', identifier: 'user', type: 'USER', relationshipType: 'ONE_TO_ONE' },
|
||||
],
|
||||
},
|
||||
relationshipField: {
|
||||
id: 51,
|
||||
name: 'User',
|
||||
identifier: 'user',
|
||||
type: 'USER',
|
||||
relationshipType: 'ONE_TO_ONE',
|
||||
},
|
||||
records: profiles,
|
||||
}],
|
||||
},
|
||||
}))
|
||||
@ -119,6 +138,15 @@ test('admin opens a user file and views a linked entity record', async ({ page }
|
||||
json: { entity: projectResponse().entity, values: created, children: [] },
|
||||
})
|
||||
})
|
||||
await page.route('**/api/users/7/children/Profile/51', async route => {
|
||||
const values = new URLSearchParams(route.request().postData() ?? '')
|
||||
const created = { id: 70, biography: values.get('biography'), user: 7 }
|
||||
profiles.push(created)
|
||||
await route.fulfill({
|
||||
status: 201,
|
||||
json: { entity: { id: 8, name: 'Profile' }, values: created, children: [] },
|
||||
})
|
||||
})
|
||||
await page.route('**/api/users/7/entities/Project/records/42', async route => {
|
||||
if (route.request().method() === 'PATCH') {
|
||||
const values = new URLSearchParams(route.request().postData() ?? '')
|
||||
@ -190,6 +218,17 @@ test('admin opens a user file and views a linked entity record', async ({ page }
|
||||
await expect(page.getByRole('heading', { name: 'Project' })).toBeVisible()
|
||||
await expect(page.getByRole('row').filter({ hasText: 'Website refresh' })).toContainText('7')
|
||||
|
||||
const profile = page.locator('.entity-card').filter({ has: page.getByRole('heading', { name: 'Profile' }) })
|
||||
await expect(profile.getByText('No Profile has been created.')).toBeVisible()
|
||||
await expect(profile.locator('table')).not.toBeVisible()
|
||||
await profile.getByRole('button', { name: 'Create Profile' }).click()
|
||||
const profileForm = profile.getByRole('form', { name: 'Create Profile' })
|
||||
await profileForm.getByLabel('Biography').fill('Product specialist')
|
||||
await profileForm.getByRole('button', { name: 'Create Profile' }).click()
|
||||
await expect(profile.getByText('Product specialist')).toBeVisible()
|
||||
await expect(profile.getByRole('button', { name: 'Create Profile' })).not.toBeVisible()
|
||||
await expect(profile.locator('table')).not.toBeVisible()
|
||||
|
||||
await page.getByRole('button', { name: 'Add Project' }).click()
|
||||
const projectForm = page.getByRole('form', { name: 'Add Project' })
|
||||
await projectForm.getByLabel('Title').fill('Mobile application')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user