feat(projects): sektioner kan trækkes op og ned, og båndet står ikke længere skævt

Rækkefølgen af sektioner *er* planen — "Planlægning" står før "I gang", fordi det
er den vej arbejdet går — og indtil nu kunne den kun laves om ved at slette en
sektion og lave den igen, hvilket smider dens opgaver ud i det usektionerede
område på vejen.

Hele overskriften er håndtaget. En sektionsoverskrift indeholder ikke noget, der
selv bliver trukket, så der er ingen geste at stjæle — hvilket er grunden til, at
sidebjælkens rækker skulle have et greb, og grunden til at disse ikke skal. Ikke
draggable mens den omdøbes: et draggable element lader ikke markøren vælge tekst
i det felt, det indeholder.

Serversiden er hele listen på én gang som projekter og grupper, og af samme
grund: et projekt har en håndfuld sektioner, så 0, 1, 2 … er én lille transaktion
uden præcision at løbe tør for, og en liste kan ikke ende halvt anvendt.
`ReorderSections` er scopet til projektet og ikke til en ejer — en sektion har
ingen ejer, den hører til et projekt, og om du må rykke rundt er et spørgsmål om
det projekt, som handleren allerede har stillet. Id'er fra et andet projekt
springes over af WHERE-klausulen; der er en prøve på præcis det, fordi ingenting
ellers kontrollerer id'erne enkeltvis.

Båndet stod fire pixels skævt. Det var trukket ud med --s2 og polstret med --s3 —
samme idé gjort med to forskellige tal — så det blev otte pixels bredere end
listen i hver side, og teksten landede fire pixels uden for den kolonne, rækkerne
holder. Til venstre var det usynligt. Til højre var det Omdøb og Slet, der stod
fire pixels forbi alt over og under sig, hvilket er præcis langt nok til at ligne
en fejl og ikke langt nok til at se hvorfor. En række er `.row` med
`padding: var(--s2)` inde i en boks uden, så båndet har nu samme polstring og
ingen margen.

Board-visningen er ikke rørt: der er kolonnerne vandrette, og det er en anden
geste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kristian Wind
2026-08-20 14:56:26 +02:00
co-authored by Claude Opus 5
parent 98bfa216d6
commit e46aaa462a
9 changed files with 373 additions and 7 deletions
+70
View File
@@ -1973,6 +1973,76 @@ test('et projekt kan have flere sektioner @forms', async ({ page }) => {
expect(trouble).toEqual([]);
});
/**
* Sektioner kan trækkes op og ned.
*
* Rækkefølgen af sektioner *er* planen — "Planlægning" står før "I gang", fordi
* det er den vej arbejdet går — og indtil nu kunne den kun laves om ved at slette
* en sektion og lave den igen, hvilket smider dens opgaver ud i det usektionerede
* område på vejen.
*
* Målt på rækkefølgen af overskrifterne på siden og bagefter på, hvad serveren
* svarer efter en genindlæsning: den første halvdel kan bestå af, at fladen
* flyttede et element, den anden kan kun bestå, hvis skrivningen gik igennem.
*/
test('sektioner kan trækkes i en anden rækkefølge, og den overlever en genindlæsning', async ({
page
}) => {
const trouble = watchForTrouble(page);
await page.goto('/');
const sidebar = page.getByRole('navigation', { name: 'Hovedmenu' });
await sidebar.getByRole('button', { name: 'Nyt projekt' }).click();
await sidebar.getByLabel('Projektnavn').fill('Havehus');
await sidebar.getByLabel('Projektnavn').press('Enter');
await expect(page.getByRole('heading', { name: 'Havehus' })).toBeVisible();
for (const name of ['Fundament', 'Rejsning', 'Tag']) {
await page.getByRole('button', { name: '+ Tilføj sektion' }).click();
const field = page.getByLabel('Ny sektion');
await field.fill(name);
await field.press('Enter');
await expect(page.getByRole('heading', { name, exact: true })).toBeVisible();
}
const order = () =>
page.locator('.section-head h2').evaluateAll((els) => els.map((e) => e.textContent.trim()));
expect(await order()).toEqual(['Fundament', 'Rejsning', 'Tag']);
// Tag trækkes helt op. Sluppet i den øverste halvdel af "Fundament", som er
// gaben over den — hvilken halvdel man rammer, er hele forskellen på over og
// under, så positionen er skrevet ud og ikke overladt til midten.
await page
.locator('.section-head')
.filter({ hasText: 'Tag' })
.dragTo(page.locator('.section-head').filter({ hasText: 'Fundament' }), {
targetPosition: { x: 40, y: 3 }
});
await expect.poll(order).toEqual(['Tag', 'Fundament', 'Rejsning']);
await page.reload();
await expect(page.getByRole('heading', { name: 'Tag', exact: true })).toBeVisible();
expect(await order(), 'serveren tog ikke rækkefølgen').toEqual([
'Tag',
'Fundament',
'Rejsning'
]);
// Og den anden vej: sluppet i den nederste halvdel lander den under.
await page
.locator('.section-head')
.filter({ hasText: 'Tag' })
.dragTo(page.locator('.section-head').filter({ hasText: 'Rejsning' }), {
targetPosition: { x: 40, y: 28 }
});
await expect.poll(order).toEqual(['Fundament', 'Rejsning', 'Tag']);
expect(trouble).toEqual([]);
});
/**
* Form fields on a touch device are at least 16px.
*