gui changes
This commit is contained in:
@@ -1,16 +1,40 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import AppMenubar from './components/AppMenubar.vue'
|
import AppMenubar from './components/AppMenubar.vue'
|
||||||
import AppToolbar from './components/AppToolbar.vue'
|
import AppToolbar from './components/AppToolbar.vue'
|
||||||
import AppStatusbar from './components/AppStatusbar.vue'
|
import AppStatusbar from './components/AppStatusbar.vue'
|
||||||
import RequirementsView from './views/RequirementsView.vue'
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const activeView = computed<'home' | 'requirements'>(() => {
|
||||||
|
return route.name === 'home' ? 'home' : 'requirements'
|
||||||
|
})
|
||||||
|
|
||||||
|
const goHome = () => {
|
||||||
|
router.push({ name: 'home' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const goRequirements = () => {
|
||||||
|
router.push({ name: 'requirements' })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<AppMenubar />
|
<AppMenubar
|
||||||
<AppToolbar />
|
:active-view="activeView"
|
||||||
|
@home="goHome"
|
||||||
|
@requirements="goRequirements"
|
||||||
|
/>
|
||||||
|
<AppToolbar
|
||||||
|
:active-view="activeView"
|
||||||
|
@home="goHome"
|
||||||
|
@requirements="goRequirements"
|
||||||
|
/>
|
||||||
<main class="app-main">
|
<main class="app-main">
|
||||||
<RequirementsView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
<AppStatusbar />
|
<AppStatusbar />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { computed } from 'vue'
|
||||||
import Menubar from 'primevue/menubar'
|
import Menubar from 'primevue/menubar'
|
||||||
import type { MenuItem } from 'primevue/menuitem'
|
import type { MenuItem } from 'primevue/menuitem'
|
||||||
|
|
||||||
const menuItems = ref<MenuItem[]>([
|
interface Props {
|
||||||
|
activeView: 'home' | 'requirements'
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
home: []
|
||||||
|
requirements: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const menuItems = computed<MenuItem[]>(() => [
|
||||||
{
|
{
|
||||||
label: 'File',
|
label: 'File',
|
||||||
items: [
|
items: [
|
||||||
@@ -91,6 +102,24 @@ const menuItems = ref<MenuItem[]>([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Windows',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Home',
|
||||||
|
icon: props.activeView === 'home' ? 'pi pi-fw pi-check' : 'pi pi-fw pi-home',
|
||||||
|
command: () => emit('home'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Requirements',
|
||||||
|
icon:
|
||||||
|
props.activeView === 'requirements'
|
||||||
|
? 'pi pi-fw pi-check'
|
||||||
|
: 'pi pi-fw pi-list-check',
|
||||||
|
command: () => emit('requirements'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Tools',
|
label: 'Tools',
|
||||||
items: [
|
items: [
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
import Toolbar from 'primevue/toolbar'
|
import Toolbar from 'primevue/toolbar'
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import Divider from 'primevue/divider'
|
import Divider from 'primevue/divider'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
activeView: 'home' | 'requirements'
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
home: []
|
||||||
|
requirements: []
|
||||||
new: []
|
new: []
|
||||||
open: []
|
open: []
|
||||||
save: []
|
save: []
|
||||||
undo: []
|
|
||||||
redo: []
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const handleHome = () => emit('home')
|
||||||
|
const handleRequirements = () => emit('requirements')
|
||||||
const handleNew = () => emit('new')
|
const handleNew = () => emit('new')
|
||||||
const handleOpen = () => emit('open')
|
const handleOpen = () => emit('open')
|
||||||
const handleSave = () => emit('save')
|
const handleSave = () => emit('save')
|
||||||
const handleUndo = () => emit('undo')
|
|
||||||
const handleRedo = () => emit('redo')
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -51,20 +56,22 @@ const handleRedo = () => emit('redo')
|
|||||||
<Divider layout="vertical" />
|
<Divider layout="vertical" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-undo"
|
class="nav-button"
|
||||||
|
icon="pi pi-home"
|
||||||
rounded
|
rounded
|
||||||
text
|
text
|
||||||
severity="secondary"
|
:severity="activeView === 'home' ? 'contrast' : 'secondary'"
|
||||||
@click="handleUndo"
|
@click="handleHome"
|
||||||
v-tooltip="'Undo'"
|
v-tooltip="'Home'"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-redo"
|
class="nav-button"
|
||||||
|
icon="pi pi-list-check"
|
||||||
rounded
|
rounded
|
||||||
text
|
text
|
||||||
severity="secondary"
|
:severity="activeView === 'requirements' ? 'contrast' : 'secondary'"
|
||||||
@click="handleRedo"
|
@click="handleRequirements"
|
||||||
v-tooltip="'Redo'"
|
v-tooltip="'Requirements'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -119,6 +126,15 @@ const handleRedo = () => emit('redo')
|
|||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.nav-button.p-button) {
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.nav-button.p-button-contrast) {
|
||||||
|
background: rgba(78, 107, 255, 0.15);
|
||||||
|
color: #1e3a8a;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.p-button.p-button-rounded) {
|
:deep(.p-button.p-button-rounded) {
|
||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import Card from 'primevue/card'
|
|
||||||
import Chip from 'primevue/chip'
|
import Chip from 'primevue/chip'
|
||||||
import Divider from 'primevue/divider'
|
import Divider from 'primevue/divider'
|
||||||
import TabView from 'primevue/tabview'
|
import TabView from 'primevue/tabview'
|
||||||
@@ -33,7 +32,6 @@ interface Props {
|
|||||||
|
|
||||||
defineProps<Props>()
|
defineProps<Props>()
|
||||||
|
|
||||||
// Helper function to get severity color for status
|
|
||||||
const statusSeverity = (status: Requirement['status']) => {
|
const statusSeverity = (status: Requirement['status']) => {
|
||||||
const map: Record<Requirement['status'], 'secondary' | 'info' | 'success' | 'danger' | 'contrast'> = {
|
const map: Record<Requirement['status'], 'secondary' | 'info' | 'success' | 'danger' | 'contrast'> = {
|
||||||
Draft: 'secondary',
|
Draft: 'secondary',
|
||||||
@@ -45,7 +43,6 @@ const statusSeverity = (status: Requirement['status']) => {
|
|||||||
return map[status]
|
return map[status]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get severity color for priority
|
|
||||||
const prioritySeverity = (priority: Requirement['priority']) => {
|
const prioritySeverity = (priority: Requirement['priority']) => {
|
||||||
const map: Record<Requirement['priority'], 'success' | 'info' | 'warn' | 'danger'> = {
|
const map: Record<Requirement['priority'], 'success' | 'info' | 'warn' | 'danger'> = {
|
||||||
Low: 'success',
|
Low: 'success',
|
||||||
@@ -59,37 +56,30 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="detail-zone">
|
<section class="detail-zone">
|
||||||
<Card class="detail-card">
|
<section class="detail-surface">
|
||||||
<template #title>
|
<header class="detail-card__header">
|
||||||
<div class="detail-card__header">
|
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Selected requirement</p>
|
<p class="eyebrow">Selected requirement</p>
|
||||||
<h2>{{ requirement.title }}</h2>
|
<h2>{{ requirement.title }}</h2>
|
||||||
<p class="detail-card__subtitle">
|
<p class="detail-card__subtitle">
|
||||||
{{ requirement.reference }} · Owner: {{ requirement.owner }}
|
{{ requirement.reference }} - Owner: {{ requirement.owner }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-actions">
|
<div class="detail-actions">
|
||||||
<Button label="Edit" icon="pi pi-pencil" severity="secondary" outlined />
|
<Button label="Edit" icon="pi pi-pencil" severity="secondary" outlined />
|
||||||
<Button label="Mark as delivered" icon="pi pi-check" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #content>
|
<TabView class="detail-tabs">
|
||||||
<TabView>
|
|
||||||
<TabPanel value="details" header="Details">
|
<TabPanel value="details" header="Details">
|
||||||
<!-- Status and priority tags -->
|
|
||||||
<div class="detail-badges">
|
<div class="detail-badges">
|
||||||
<Tag :value="requirement.status" :severity="statusSeverity(requirement.status)" />
|
<Tag :value="requirement.status" :severity="statusSeverity(requirement.status)" />
|
||||||
<Tag :value="requirement.priority" :severity="prioritySeverity(requirement.priority)" rounded />
|
<Tag :value="requirement.priority" :severity="prioritySeverity(requirement.priority)" rounded />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
|
||||||
<p class="detail-description">{{ requirement.description }}</p>
|
<p class="detail-description">{{ requirement.description }}</p>
|
||||||
|
|
||||||
<!-- Blockers section (shown only if blockers exist) -->
|
|
||||||
<article v-if="requirement.blockers.length" class="info-panel info-panel--warning">
|
<article v-if="requirement.blockers.length" class="info-panel info-panel--warning">
|
||||||
<h3>Identified blockers</h3>
|
<h3>Identified blockers</h3>
|
||||||
<ul class="blockers-list">
|
<ul class="blockers-list">
|
||||||
@@ -102,7 +92,6 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<!-- Key attributes: Author, Stakeholders, Priority, Flexibility -->
|
|
||||||
<div class="detail-grid">
|
<div class="detail-grid">
|
||||||
<article class="info-panel">
|
<article class="info-panel">
|
||||||
<h3>Author</h3>
|
<h3>Author</h3>
|
||||||
@@ -129,7 +118,6 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<!-- Acceptance criteria and impacted modules -->
|
|
||||||
<div class="detail-columns">
|
<div class="detail-columns">
|
||||||
<article class="info-panel">
|
<article class="info-panel">
|
||||||
<h3>Acceptance criteria</h3>
|
<h3>Acceptance criteria</h3>
|
||||||
@@ -162,29 +150,29 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabView>
|
</TabView>
|
||||||
</template>
|
</section>
|
||||||
</Card>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.detail-zone {
|
.detail-zone {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-card {
|
.detail-surface {
|
||||||
padding: 0.5rem;
|
display: flex;
|
||||||
border: 1px solid rgba(96, 117, 156, 0.16);
|
flex-direction: column;
|
||||||
border-radius: 0.5rem;
|
height: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
background: rgba(255, 255, 255, 0.86);
|
background: rgba(255, 255, 255, 0.86);
|
||||||
box-shadow: 0 24px 60px rgba(34, 49, 77, 0.12);
|
box-shadow: 0 24px 60px rgba(34, 49, 77, 0.12);
|
||||||
backdrop-filter: blur(18px);
|
backdrop-filter: blur(18px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-card :is(.p-card-body) {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-card__header {
|
.detail-card__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -201,7 +189,7 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
color: #5c6b88;
|
color: #5c6b88;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-card h2 {
|
.detail-surface h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #12213a;
|
color: #12213a;
|
||||||
}
|
}
|
||||||
@@ -219,6 +207,25 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-tabs {
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.detail-tabs.p-tabview) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.detail-tabs .p-tabview-panels) {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.detail-badges {
|
.detail-badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -265,36 +272,12 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
color: #4c5d77;
|
color: #4c5d77;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-panel--accent {
|
|
||||||
background: linear-gradient(180deg, rgba(237, 243, 255, 0.92), rgba(248, 250, 255, 0.95));
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-panel--warning {
|
.info-panel--warning {
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
background: linear-gradient(180deg, rgba(255, 244, 236, 0.95), rgba(255, 249, 244, 0.95));
|
background: linear-gradient(180deg, rgba(255, 244, 236, 0.95), rgba(255, 249, 244, 0.95));
|
||||||
border-color: rgba(223, 134, 57, 0.24);
|
border-color: rgba(223, 134, 57, 0.24);
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-panel dl {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 0.75rem;
|
|
||||||
margin: 0.75rem 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-panel dt {
|
|
||||||
color: #73829d;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-panel dd {
|
|
||||||
margin: 0.2rem 0 0;
|
|
||||||
color: #12213a;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checklist,
|
.checklist,
|
||||||
.blockers-list {
|
.blockers-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -343,11 +326,6 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-tabview-panels) {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1120px) {
|
@media (max-width: 1120px) {
|
||||||
.detail-grid,
|
.detail-grid,
|
||||||
.detail-columns {
|
.detail-columns {
|
||||||
@@ -366,9 +344,11 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-grid,
|
.detail-grid,
|
||||||
.detail-columns,
|
.detail-columns {
|
||||||
.info-panel dl {
|
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -184,8 +184,8 @@ const onNodeSelect = (node: TreeNode) => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border: 1px solid rgba(96, 117, 156, 0.16);
|
border: none;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0;
|
||||||
background: rgba(255, 255, 255, 0.86);
|
background: rgba(255, 255, 255, 0.86);
|
||||||
box-shadow: 0 24px 60px rgba(34, 49, 77, 0.12);
|
box-shadow: 0 24px 60px rgba(34, 49, 77, 0.12);
|
||||||
backdrop-filter: blur(18px);
|
backdrop-filter: blur(18px);
|
||||||
@@ -330,3 +330,5 @@ const onNodeSelect = (node: TreeNode) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import PrimeVue from 'primevue/config'
|
|||||||
import Aura from '@primeuix/themes/aura'
|
import Aura from '@primeuix/themes/aura'
|
||||||
import 'primeicons/primeicons.css'
|
import 'primeicons/primeicons.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
app.use(PrimeVue, {
|
app.use(PrimeVue, {
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
9
kwa-ui/src/router/index.ts
Normal file
9
kwa-ui/src/router/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
import { routes } from './routes'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
20
kwa-ui/src/router/routes.ts
Normal file
20
kwa-ui/src/router/routes.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
import RequirementsView from '../views/RequirementsView.vue'
|
||||||
|
|
||||||
|
export const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: HomeView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/requirements',
|
||||||
|
name: 'requirements',
|
||||||
|
component: RequirementsView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
redirect: { name: 'home' },
|
||||||
|
},
|
||||||
|
]
|
||||||
58
kwa-ui/src/views/HomeView.vue
Normal file
58
kwa-ui/src/views/HomeView.vue
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<section class="home-view">
|
||||||
|
<section class="home-panel">
|
||||||
|
<p class="eyebrow">Home</p>
|
||||||
|
<h1>Requirements Workspace</h1>
|
||||||
|
<p class="subtitle">
|
||||||
|
Use the toolbar icons to switch between this home page and the requirements dashboard.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.home-view {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top left, rgba(78, 107, 255, 0.18), transparent 34%),
|
||||||
|
radial-gradient(circle at top right, rgba(21, 184, 164, 0.14), transparent 28%),
|
||||||
|
linear-gradient(180deg, rgba(245, 248, 255, 1) 0%, rgba(235, 241, 250, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-panel {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
border: none;
|
||||||
|
background: rgba(255, 255, 255, 0.86);
|
||||||
|
box-shadow: 0 24px 60px rgba(34, 49, 77, 0.12);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
margin: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #5c6b88;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0.5rem 0 0;
|
||||||
|
color: #12213a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
color: #4c5d77;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.home-panel {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, onBeforeUnmount } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import Splitter from 'primevue/splitter'
|
||||||
|
import SplitterPanel from 'primevue/splitterpanel'
|
||||||
|
|
||||||
import RequirementsTreeList from '../components/RequirementsTreeList.vue'
|
import RequirementsTreeList from '../components/RequirementsTreeList.vue'
|
||||||
import RequirementDetail from '../components/RequirementDetail.vue'
|
import RequirementDetail from '../components/RequirementDetail.vue'
|
||||||
@@ -13,7 +15,20 @@ const requirementsStore = useRequirementsStore()
|
|||||||
const { requirements, selectedId, searchQuery, selectedRequirement, stats } =
|
const { requirements, selectedId, searchQuery, selectedRequirement, stats } =
|
||||||
storeToRefs(requirementsStore)
|
storeToRefs(requirementsStore)
|
||||||
|
|
||||||
// Helper function to create tree nodes from requirements
|
const splitterDraggingClass = 'is-splitter-dragging'
|
||||||
|
|
||||||
|
const onSplitterResizeStart = () => {
|
||||||
|
document.body.classList.add(splitterDraggingClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSplitterResizeEnd = () => {
|
||||||
|
document.body.classList.remove(splitterDraggingClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
document.body.classList.remove(splitterDraggingClass)
|
||||||
|
})
|
||||||
|
|
||||||
const buildTreeData = (): TreeNode[] => {
|
const buildTreeData = (): TreeNode[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -70,7 +85,13 @@ const treeData = computed(() => buildTreeData())
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="requirements-view">
|
<section class="requirements-view">
|
||||||
<section class="workspace">
|
<Splitter
|
||||||
|
class="workspace-splitter"
|
||||||
|
:gutter-size="6"
|
||||||
|
@resizestart="onSplitterResizeStart"
|
||||||
|
@resizeend="onSplitterResizeEnd"
|
||||||
|
>
|
||||||
|
<SplitterPanel :size="28" :min-size="20">
|
||||||
<RequirementsTreeList
|
<RequirementsTreeList
|
||||||
:tree-data="treeData"
|
:tree-data="treeData"
|
||||||
:searchQuery="searchQuery"
|
:searchQuery="searchQuery"
|
||||||
@@ -79,15 +100,20 @@ const treeData = computed(() => buildTreeData())
|
|||||||
@update:searchQuery="searchQuery = $event"
|
@update:searchQuery="searchQuery = $event"
|
||||||
@update:selectedId="selectedId = $event"
|
@update:selectedId="selectedId = $event"
|
||||||
/>
|
/>
|
||||||
|
</SplitterPanel>
|
||||||
|
|
||||||
|
<SplitterPanel :size="72" :min-size="35">
|
||||||
<RequirementDetail :requirement="selectedRequirement" />
|
<RequirementDetail :requirement="selectedRequirement" />
|
||||||
</section>
|
</SplitterPanel>
|
||||||
|
</Splitter>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.requirements-view {
|
.requirements-view {
|
||||||
padding: 2rem;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at top left, rgba(78, 107, 255, 0.18), transparent 34%),
|
radial-gradient(circle at top left, rgba(78, 107, 255, 0.18), transparent 34%),
|
||||||
@@ -95,74 +121,60 @@ const treeData = computed(() => buildTreeData())
|
|||||||
linear-gradient(180deg, rgba(245, 248, 255, 1) 0%, rgba(235, 241, 250, 1) 100%);
|
linear-gradient(180deg, rgba(245, 248, 255, 1) 0%, rgba(235, 241, 250, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-card {
|
.workspace-splitter {
|
||||||
display: flex;
|
flex: 1;
|
||||||
align-items: flex-end;
|
height: 100%;
|
||||||
justify-content: space-between;
|
min-height: 0;
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
border: 1px solid rgba(96, 117, 156, 0.16);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background: rgba(255, 255, 255, 0.86);
|
|
||||||
box-shadow: 0 24px 60px rgba(34, 49, 77, 0.12);
|
|
||||||
backdrop-filter: blur(18px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-card h1 {
|
.workspace-splitter :deep(.p-splitter-panel) {
|
||||||
margin: 0.35rem 0 0.65rem;
|
min-height: 0;
|
||||||
font-size: clamp(1.8rem, 3vw, 2.7rem);
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.05;
|
|
||||||
color: #12213a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-card__text {
|
.workspace-splitter :deep(.p-splitter-gutter) {
|
||||||
max-width: 60ch;
|
position: relative;
|
||||||
color: #51627f;
|
background: transparent;
|
||||||
|
cursor: col-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-actions {
|
.workspace-splitter :deep(.p-splitter-gutter::before) {
|
||||||
display: flex;
|
content: '';
|
||||||
flex-wrap: wrap;
|
position: absolute;
|
||||||
justify-content: flex-end;
|
top: 8px;
|
||||||
gap: 0.75rem;
|
bottom: 8px;
|
||||||
|
left: 50%;
|
||||||
|
width: 2px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(96, 117, 156, 0.32);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eyebrow {
|
.workspace-splitter :deep(.p-splitter-gutter:hover::before),
|
||||||
margin: 0;
|
.workspace-splitter :deep(.p-splitter-gutter:active::before) {
|
||||||
text-transform: uppercase;
|
background: rgba(78, 107, 255, 0.6);
|
||||||
letter-spacing: 0.14em;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #5c6b88;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace {
|
.workspace-splitter :deep(.p-splitter-gutter-handle) {
|
||||||
display: grid;
|
width: 0;
|
||||||
grid-template-columns: minmax(18rem, 22rem) minmax(0, 1fr);
|
height: 0;
|
||||||
gap: 1rem;
|
}
|
||||||
align-items: start;
|
|
||||||
|
:global(body.is-splitter-dragging),
|
||||||
|
:global(body.is-splitter-dragging *) {
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1120px) {
|
@media (max-width: 1120px) {
|
||||||
.workspace {
|
.workspace-splitter {
|
||||||
grid-template-columns: 1fr;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
.requirements-view {
|
.requirements-view {
|
||||||
padding: 1rem;
|
padding: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.hero-card {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-actions {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user