Project configuration view: manage a project's statuses
New /projects/:id/configure view, linked from a new 'Configure' item on
the project view's Manage menu.
Backend:
- CardStatusRepository/CardStatusController gain full CRUD: create
(appended at the end), reorder (dense positions, like card
ordering), and delete.
- Deleting a status with cards attached is rejected with 409 and
error.details.card_count, rather than hitting the existing FK
RESTRICT constraint -- retrying with { reassign_to: <status id> }
moves those cards to that status first (CardRepository::
reassignStatus, appended after the destination's existing cards)
and deletes in one transaction (CardStatusRepository::transaction,
shared PDO connection across repositories).
- The last status in a project can't be deleted, since a project card
is required to have one.
- Routes: POST/DELETE .../statuses(/:id), PUT .../statuses/order.
- 14 new CardStatusTest cases covering all of the above.
Frontend:
- ProjectConfigureView.vue: header (title, back-to-project link, the
shared Manage menu) + a vuedraggable status list (reorder persists
the whole new order) with a delete button per row and an add-status
form. A row's plain delete either succeeds immediately or, on 409,
opens a modal to choose a different status before retrying the
delete with reassign_to.
- Extracted ProjectManageMenu.vue (the Manage dropdown + delete-project
modal) out of ProjectView so both views share it; it now also has a
Configure link (hidden on the configure page itself).
- ApiError gains a cardCount getter (details.card_count), mirroring
the existing retryAfter getter.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -508,6 +508,13 @@ h1 {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.project-head__back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: relative;
|
||||
flex: none;
|
||||
@@ -553,6 +560,7 @@ h1 {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text);
|
||||
@@ -604,6 +612,108 @@ h1 {
|
||||
max-width: 42rem;
|
||||
}
|
||||
|
||||
/* --- project configuration: status list ------------------------------- */
|
||||
|
||||
.config-section {
|
||||
max-width: 32rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.config-section h2 {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.status-list {
|
||||
list-style: none;
|
||||
margin: 1rem 0 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem 0.6rem;
|
||||
background: var(--surface);
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.status-row--ghost {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.status-row__name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.status-row__delete {
|
||||
flex: none;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.status-row__delete:hover:not(:disabled) {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.status-row__delete:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.status-list__new {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.status-list__new input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.status-list__new button[type='submit'] {
|
||||
flex: none;
|
||||
padding: 0.4rem 0.7rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.modal__field {
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.modal__field select {
|
||||
padding: 0.5rem 0.6rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
/* --- kanban board ---------------------------------------------------- */
|
||||
|
||||
.kanban {
|
||||
|
||||
Reference in New Issue
Block a user