Add relation manager: link/unlink records directly in modal
- RelationManager component: shows linked items as removable tags, live search to find and add new links (×-button to unlink) - tables.js: full fetchRelated config with linkEndpoint + searchEndpoint for words↔pictures, words↔categories, objects↔words, objects↔pictures - api.js: add apiLink, apiUnlink, apiDelete helpers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,18 @@ export async function apiFetchOne(path) {
|
||||
return apiFetch(path);
|
||||
}
|
||||
|
||||
export async function apiDelete(endpoint, id) {
|
||||
return apiFetch(`${endpoint}/${id}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export async function apiLink(path) {
|
||||
return apiFetch(path, { method: 'POST', body: '{}' });
|
||||
}
|
||||
|
||||
export async function apiUnlink(path) {
|
||||
return apiFetch(path, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
// Multipart upload — does NOT set Content-Type (browser sets boundary automatically)
|
||||
export async function apiUpload(path, formData) {
|
||||
const token = getToken();
|
||||
|
||||
@@ -10,10 +10,32 @@ export const TABLES = {
|
||||
editableFields: {
|
||||
titel_de: { type: 'text' },
|
||||
titel_en: { type: 'text' },
|
||||
titel_sv: { type: 'text' },
|
||||
status: { type: 'select', options: ['published', 'blocked', 'draft', 'requested', 'translated'] },
|
||||
difficulty_level:{ type: 'number', min: 1, max: 10 },
|
||||
},
|
||||
fetchRelated: [],
|
||||
fetchRelated: [
|
||||
{
|
||||
key: 'pictures',
|
||||
label: 'Bilder',
|
||||
endpoint: id => `/words/${id}/pictures`,
|
||||
display: p => p.design || p.id,
|
||||
targetTable: 'pictures',
|
||||
linkEndpoint: (id, targetId) => `/words/${id}/pictures/${targetId}`,
|
||||
searchEndpoint: '/pictures',
|
||||
searchLabel: p => p.design || p.id,
|
||||
},
|
||||
{
|
||||
key: 'categories',
|
||||
label: 'Kategorien',
|
||||
endpoint: id => `/words/${id}/categories`,
|
||||
display: c => c.name_de || c.id,
|
||||
targetTable: 'categories',
|
||||
linkEndpoint: (id, targetId) => `/words/${id}/categories/${targetId}`,
|
||||
searchEndpoint: '/categories',
|
||||
searchLabel: c => c.name_de || c.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
pictures: {
|
||||
label: 'Bilder',
|
||||
@@ -30,7 +52,16 @@ export const TABLES = {
|
||||
blurhash: { type: 'text' },
|
||||
},
|
||||
fetchRelated: [
|
||||
{ key: 'words', label: 'Wörter', endpoint: id => `/pictures/${id}/words`, display: w => w.titel_de || w.id },
|
||||
{
|
||||
key: 'words',
|
||||
label: 'Wörter',
|
||||
endpoint: id => `/pictures/${id}/words`,
|
||||
display: w => w.titel_de || w.id,
|
||||
targetTable: 'words',
|
||||
linkEndpoint: (id, targetId) => `/pictures/${id}/words/${targetId}`,
|
||||
searchEndpoint: '/words',
|
||||
searchLabel: w => w.titel_de || w.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
objects: {
|
||||
@@ -45,7 +76,28 @@ export const TABLES = {
|
||||
notes: { type: 'textarea' },
|
||||
status: { type: 'select', options: ['published', 'blocked', 'draft'] },
|
||||
},
|
||||
fetchRelated: [],
|
||||
fetchRelated: [
|
||||
{
|
||||
key: 'words',
|
||||
label: 'Wörter',
|
||||
endpoint: id => `/objects/${id}/words`,
|
||||
display: w => w.titel_de || w.id,
|
||||
targetTable: 'words',
|
||||
linkEndpoint: (id, targetId) => `/objects/${id}/words/${targetId}`,
|
||||
searchEndpoint: '/words',
|
||||
searchLabel: w => w.titel_de || w.id,
|
||||
},
|
||||
{
|
||||
key: 'pictures',
|
||||
label: 'Bilder',
|
||||
endpoint: id => `/objects/${id}/pictures`,
|
||||
display: p => p.design || p.id,
|
||||
targetTable: 'pictures',
|
||||
linkEndpoint: (id, targetId) => `/objects/${id}/pictures/${targetId}`,
|
||||
searchEndpoint: '/pictures',
|
||||
searchLabel: p => p.design || p.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
pairs: {
|
||||
label: 'Pairs',
|
||||
|
||||
Reference in New Issue
Block a user