Move new-project form to the dashboard; sidebar project list becomes a switcher; Kanban is the default project tab
- DashboardView: a "New project title" form now sits directly below the project grid (creating one stays on the dashboard -- the grid and the sidebar dropdown pick it up via the shared projects store, no navigation). - AppSidebar: the per-project RouterLink list is replaced by a <select>. It's a v-model-bound writable computed (selectedProjectId): the getter tracks route.params.id so it reflects whichever project is open, the setter router.pushes to the chosen one -- so it doubles as a project switcher from anywhere, not just a picker from the dashboard. Removed the now-unused .sidebar__link--project/.sidebar__projects/.sidebar__form CSS; added .sidebar__select, and .form--new-project back for the dashboard form. - ProjectView: activeTab now initialises to 'kanban' instead of 'all'. The tabs array (and so the tab bar's DOM order, All tasks first) is untouched -- only the default selection changed. Verified via headless Chrome: sidebar has no per-project links (a <select> listing both projects instead); the new-project form follows the grid and creating from it stays on /dashboard while the grid and dropdown both update; selecting a project in the dropdown navigates there and the dropdown reflects it once there; a project opens on Kanban with the tab bar still reading "All tasks, Kanban" in that order. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { ApiError } from '../lib/api'
|
||||
import { useProjectsStore } from '../stores/projects'
|
||||
import { MAX_PROJECTS, useProjectsStore } from '../stores/projects'
|
||||
|
||||
const projects = useProjectsStore()
|
||||
|
||||
const loadError = ref<string | null>(null)
|
||||
|
||||
const title = ref('')
|
||||
const createError = ref<ApiError | null>(null)
|
||||
const submitting = ref(false)
|
||||
|
||||
const atLimit = computed(() => projects.projects.length >= MAX_PROJECTS)
|
||||
|
||||
onMounted(load)
|
||||
|
||||
async function load() {
|
||||
@@ -18,6 +24,19 @@ async function load() {
|
||||
loadError.value = e instanceof ApiError ? e.message : 'Could not load the dashboard.'
|
||||
}
|
||||
}
|
||||
|
||||
async function onCreate() {
|
||||
submitting.value = true
|
||||
createError.value = null
|
||||
try {
|
||||
await projects.createProject(title.value)
|
||||
title.value = ''
|
||||
} catch (e) {
|
||||
createError.value = e instanceof ApiError ? e : new ApiError('Could not create the project.', 0)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -25,8 +44,8 @@ async function load() {
|
||||
<p v-if="loadError" class="form-error">{{ loadError }}</p>
|
||||
<p v-else-if="projects.loading && !projects.loaded" class="muted">Loading…</p>
|
||||
<p v-else-if="projects.projects.length === 0" class="muted">
|
||||
No projects yet — create one from the sidebar. Anything uncategorised
|
||||
lives in the inbox, also in the sidebar.
|
||||
No projects yet — create one below. Anything uncategorised lives in the
|
||||
inbox, in the sidebar.
|
||||
</p>
|
||||
|
||||
<div v-else class="dashboard__grid">
|
||||
@@ -42,5 +61,27 @@ async function load() {
|
||||
</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<form class="form form--new-project" @submit.prevent="onCreate">
|
||||
<label>
|
||||
<span>New project title</span>
|
||||
<input v-model="title" type="text" maxlength="255" required :disabled="atLimit" />
|
||||
<small v-if="createError?.fieldError('title')" class="field-error">
|
||||
{{ createError.fieldError('title') }}
|
||||
</small>
|
||||
</label>
|
||||
|
||||
<p v-if="createError && Object.keys(createError.details).length === 0" class="form-error">
|
||||
{{ createError.message }}
|
||||
</p>
|
||||
|
||||
<button type="submit" :disabled="submitting || atLimit">
|
||||
{{ submitting ? 'Creating…' : 'Create project' }}
|
||||
</button>
|
||||
|
||||
<p v-if="atLimit" class="hint">
|
||||
You have reached the maximum of {{ MAX_PROJECTS }} projects.
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -23,11 +23,12 @@ const statuses = ref<CardStatus[]>([])
|
||||
const loadError = ref<string | null>(null)
|
||||
const actionError = ref<string | null>(null)
|
||||
|
||||
// Tab order is unchanged (All tasks first); Kanban is just the default view.
|
||||
const tabs = [
|
||||
{ key: 'all', label: 'All tasks' },
|
||||
{ key: 'kanban', label: 'Kanban' },
|
||||
] as const
|
||||
const activeTab = ref<(typeof tabs)[number]['key']>('all')
|
||||
const activeTab = ref<(typeof tabs)[number]['key']>('kanban')
|
||||
|
||||
const titleDraft = ref('')
|
||||
const descriptionDraft = ref('')
|
||||
|
||||
Reference in New Issue
Block a user