feat: entry list at /content/:key

This commit is contained in:
Florian Sylvain committed 2026-10-20 21:05:03 +02:00
1 parent b4cbcb0395
commit 91d916fb74
44 files changed
+4984 -32

No files matched your search

+3
View File
@@ -646,6 +646,9 @@ const boardGroupLabels: Record<string, Record<string, string>> = {
'Review and delete': '3 Review and delete',
'Field types': '4 Field types',
},
content: {
'Content and entry list': '5 Content and entry list',
},
}
const referenceGroups: Record<string, string> = {
+239
View File
@@ -6988,9 +6988,248 @@ const schemaStates: Record<string, VisualState> = {
},
}
const contentRows = [
[
42,
'Lime plaster workshop',
'2026-11-21',
'2026-11-22',
false,
18,
'Workshop',
'Two days of hands-on work with lime putty and old stone.',
'2026-10-12',
'2026-10-15',
3,
],
[
41,
'Heat pumps: what the numbers say',
'2026-11-03',
'',
true,
500,
'Talk',
'A look at running costs after two winters.',
'2026-10-08',
'2026-10-14',
5,
],
[
40,
'Open day at the Rue Verte site',
'2026-11-12',
'2026-11-12',
false,
120,
'Meetup',
'',
'2026-10-02',
'2026-10-12',
4,
],
[
39,
'Winter windows clinic',
'2026-12-05',
'',
false,
null,
'Workshop',
'Bring a window, leave with a plan.',
'2026-09-30',
'2026-10-09',
3,
],
[
38,
'Timber joinery for beginners',
'2027-01-14',
'2027-01-15',
false,
12,
'Workshop',
'Mortise, tenon and a lot of patience.',
'2026-09-28',
'2026-10-07',
4,
],
[
37,
'Q&A with the site managers',
'2026-10-28',
'',
true,
null,
'Meetup',
'',
'2026-09-21',
'2026-10-02',
5,
],
[
36,
'Reclaimed floorboards talk',
'2026-10-17',
'',
false,
60,
'Talk',
'Where to find them and how to lay them.',
'2026-09-14',
'2026-09-29',
3,
],
].map(
([
id,
title,
startsOn,
endsOn,
online,
capacity,
category,
description,
created,
updated,
by,
]) => ({
id,
content_type_id: 1,
data: {
title,
starts_on: startsOn,
ends_on: endsOn,
online,
capacity,
category,
description,
},
created_by: by,
updated_by: by,
created_at: `${created}T09:14:00Z`,
updated_at: `${updated}T11:02:00Z`,
}),
)
const contentTypeNames = [
'Events',
'Recipes',
'Press mentions',
'Frequently asked questions and answers',
'Job openings',
'Testimonials',
'Suppliers',
'Certifications',
'Case studies',
'Glossary terms',
'Partners',
'Downloads',
'Locations',
'Awards',
]
const contentManyTypes = contentTypeNames.map((name, index) => ({
id: index + 1,
key: name === 'Events' ? 'events' : name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
name,
icon: 'files',
fields: index === 0 ? schemaEventsFields : [],
created_at: '2026-09-01T09:30:00Z',
updated_at: '2026-10-15T09:30:00Z',
}))
const contentUsers = [
{ id: 3, display_name: 'Marta Alves' },
{ id: 4, display_name: 'Inès Duarte' },
{ id: 5, display_name: 'Jonas Pereira' },
]
const contentEventsType = schemaCustomTypes[0]!
function contentEntries(url: URL) {
const size = Number(url.searchParams.get('size'))
if (size === 5) {
return {
items: contentRows.slice(0, 5),
total: 37,
page: 1,
size: 5,
sort: 'updated_at:desc',
}
}
return { items: contentRows, total: 7, page: 1, size: 25, sort: 'updated_at:desc' }
}
function contentApi(overrides: Record<string, ApiMock> = {}): Record<string, ApiMock> {
return {
'GET /v1/auth/me': schemaSession('owner'),
'GET /v1/content-type': { status: 200, body: schemaCustomTypes },
'GET /v1/content-type/events': { status: 200, body: contentEventsType },
'GET /v1/content-type/events/entry': { status: 200, bodyFor: contentEntries },
'GET /v1/user/lookup': { status: 200, body: contentUsers },
...overrides,
}
}
const contentStates: Record<string, VisualState> = {
Populated: { path: '/content/events', api: contentApi() },
'Paginated, 5 per page': { path: '/content/events?size=5', api: contentApi() },
'Columns menu': {
path: '/content/events',
api: contentApi(),
steps: async (page) => {
await page.getByRole('button', { name: 'Columns' }).click()
await page.getByText('Created and updated dates always show').waitFor()
await schemaSettle(page)
},
},
'Many types in the sidebar': {
path: '/content/events',
api: contentApi({ 'GET /v1/content-type': { status: 200, body: contentManyTypes } }),
steps: async (page) => {
if (schemaIsMobile(page)) {
await page.locator('[data-slot="toggle"]').first().click()
await page.getByRole('button', { name: 'All content types' }).click()
await page.getByText('Frequently asked questions and answers').first().waitFor()
}
await schemaSettle(page)
},
},
Loading: {
path: '/content/events',
api: contentApi({ 'GET /v1/content-type/events/entry': { status: 200, hang: true } }),
},
'Empty for this type': {
path: '/content/events',
api: contentApi({
'GET /v1/content-type/events/entry': {
status: 200,
body: { items: [], total: 0, page: 1, size: 25, sort: 'updated_at:desc' },
},
}),
},
Error: {
path: '/content/events',
api: contentApi({ 'GET /v1/content-type/events/entry': { status: 500 } }),
},
'Forbidden (403)': {
path: '/content/events',
api: contentApi({ 'GET /v1/content-type/events/entry': { status: 403 } }),
},
'Type not found': {
path: '/content/unknown',
api: contentApi({
'GET /v1/content-type/unknown': { status: 404, body: 'content_type_not_found' },
'GET /v1/content-type/unknown/entry': { status: 404, body: 'content_type_not_found' },
}),
},
}
export const visualStates: Record<string, Record<string, VisualState>> = {
sites: sitesStates,
schema: schemaStates,
content: contentStates,
authors: authorsStates,
activity: activityStates,
profile: {