update ui sizes and distances
This commit is contained in:
@@ -157,21 +157,21 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
.detail-card {
|
.detail-card {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border: 1px solid rgba(96, 117, 156, 0.16);
|
border: 1px solid rgba(96, 117, 156, 0.16);
|
||||||
border-radius: 1.5rem;
|
border-radius: 0.5rem;
|
||||||
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) {
|
.detail-card :is(.p-card-body) {
|
||||||
padding: 1.5rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-card__header {
|
.detail-card__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1.5rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eyebrow {
|
.eyebrow {
|
||||||
@@ -198,14 +198,14 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-badges {
|
.detail-badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.6rem;
|
gap: 0.45rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-description {
|
.detail-description {
|
||||||
@@ -217,23 +217,23 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
.detail-grid,
|
.detail-grid,
|
||||||
.detail-columns {
|
.detail-columns {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-grid {
|
.detail-grid {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
margin-top: 1rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-columns {
|
.detail-columns {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
margin-top: 1rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-panel {
|
.info-panel {
|
||||||
padding: 1rem;
|
padding: 0.75rem;
|
||||||
border: 1px solid rgba(96, 117, 156, 0.14);
|
border: 1px solid rgba(96, 117, 156, 0.14);
|
||||||
border-radius: 1rem;
|
border-radius: 0.5rem;
|
||||||
background: rgba(247, 249, 255, 0.9);
|
background: rgba(247, 249, 255, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-panel p {
|
.info-panel p {
|
||||||
margin: 0.65rem 0 0;
|
margin: 0.5rem 0 0;
|
||||||
color: #4c5d77;
|
color: #4c5d77;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ const prioritySeverity = (priority: Requirement['priority']) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-panel--warning {
|
.info-panel--warning {
|
||||||
margin-top: 1rem;
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,325 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import InputText from 'primevue/inputtext'
|
|
||||||
import Listbox from 'primevue/listbox'
|
|
||||||
import Tag from 'primevue/tag'
|
|
||||||
|
|
||||||
export interface Requirement {
|
|
||||||
id: number
|
|
||||||
reference: string
|
|
||||||
title: string
|
|
||||||
status: 'Draft' | 'In Review' | 'Approved' | 'Blocked' | 'Delivered'
|
|
||||||
priority: 'Low' | 'Medium' | 'High' | 'Critical'
|
|
||||||
owner: string
|
|
||||||
progress: number
|
|
||||||
dueDate: string
|
|
||||||
description: string
|
|
||||||
rationale: string
|
|
||||||
acceptanceCriteria: string[]
|
|
||||||
impactedModules: string[]
|
|
||||||
blockers: string[]
|
|
||||||
notes: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
requirements: Requirement[]
|
|
||||||
searchQuery: string
|
|
||||||
selectedId: number
|
|
||||||
stats: {
|
|
||||||
total: number
|
|
||||||
approvedCount: number
|
|
||||||
blockedCount: number
|
|
||||||
criticalCount: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emits {
|
|
||||||
(e: 'update:searchQuery', value: string): void
|
|
||||||
(e: 'update:selectedId', value: number): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
defineEmits<Emits>()
|
|
||||||
|
|
||||||
const filteredRequirements = computed(() => {
|
|
||||||
const query = props.searchQuery.trim().toLowerCase()
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
return props.requirements
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.requirements.filter((requirement) => {
|
|
||||||
return [
|
|
||||||
requirement.title,
|
|
||||||
requirement.reference,
|
|
||||||
requirement.owner,
|
|
||||||
requirement.status,
|
|
||||||
requirement.priority,
|
|
||||||
]
|
|
||||||
.join(' ')
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(query)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Helper function to get severity color for status
|
|
||||||
const statusSeverity = (status: Requirement['status']) => {
|
|
||||||
const map: Record<Requirement['status'], 'secondary' | 'info' | 'success' | 'danger' | 'contrast'> = {
|
|
||||||
Draft: 'secondary',
|
|
||||||
'In Review': 'info',
|
|
||||||
Approved: 'success',
|
|
||||||
Blocked: 'danger',
|
|
||||||
Delivered: 'contrast',
|
|
||||||
}
|
|
||||||
return map[status]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to get severity color for priority
|
|
||||||
const prioritySeverity = (priority: Requirement['priority']) => {
|
|
||||||
const map: Record<Requirement['priority'], 'success' | 'info' | 'warn' | 'danger'> = {
|
|
||||||
Low: 'success',
|
|
||||||
Medium: 'info',
|
|
||||||
High: 'warn',
|
|
||||||
Critical: 'danger',
|
|
||||||
}
|
|
||||||
return map[priority]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<aside class="sidebar">
|
|
||||||
<!-- Sidebar header with title and total count -->
|
|
||||||
<div class="sidebar__header">
|
|
||||||
<div>
|
|
||||||
<p class="eyebrow">Catalog</p>
|
|
||||||
<h2>Requirements</h2>
|
|
||||||
</div>
|
|
||||||
<Tag :value="stats.total" severity="info" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search field -->
|
|
||||||
<span class="p-input-icon-left search-field">
|
|
||||||
<i class="pi pi-search" />
|
|
||||||
<InputText
|
|
||||||
:value="searchQuery"
|
|
||||||
@input="(event) => $emit('update:searchQuery', (event.target as HTMLInputElement).value)"
|
|
||||||
placeholder="Search requirements"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<!-- Mini statistics cards -->
|
|
||||||
<div class="mini-stats">
|
|
||||||
<article>
|
|
||||||
<span>Total</span>
|
|
||||||
<strong>{{ stats.total }}</strong>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<span>Approved</span>
|
|
||||||
<strong>{{ stats.approvedCount }}</strong>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<span>Blocked</span>
|
|
||||||
<strong>{{ stats.blockedCount }}</strong>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<span>Critical</span>
|
|
||||||
<strong>{{ stats.criticalCount }}</strong>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Requirements listbox -->
|
|
||||||
<Listbox
|
|
||||||
:model-value="selectedId"
|
|
||||||
@update:model-value="(value) => $emit('update:selectedId', value)"
|
|
||||||
:options="filteredRequirements"
|
|
||||||
option-label="title"
|
|
||||||
option-value="id"
|
|
||||||
class="requirements-list"
|
|
||||||
>
|
|
||||||
<template #option="{ option }">
|
|
||||||
<div v-if="option" class="requirement-item">
|
|
||||||
<div>
|
|
||||||
<p class="requirement-item__reference">{{ option.reference }}</p>
|
|
||||||
<strong>{{ option.title }}</strong>
|
|
||||||
<small>{{ option.owner }}</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="requirement-item__tags">
|
|
||||||
<Tag :value="option.status" :severity="statusSeverity(option.status)" />
|
|
||||||
<Tag :value="option.priority" :severity="prioritySeverity(option.priority)" rounded />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #empty>
|
|
||||||
<div class="empty-state">
|
|
||||||
<i class="pi pi-inbox" />
|
|
||||||
<p>No requirement matches your search.</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Listbox>
|
|
||||||
</aside>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.sidebar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1.25rem;
|
|
||||||
border: 1px solid rgba(96, 117, 156, 0.16);
|
|
||||||
border-radius: 1.5rem;
|
|
||||||
background: rgba(255, 255, 255, 0.86);
|
|
||||||
box-shadow: 0 24px 60px rgba(34, 49, 77, 0.12);
|
|
||||||
backdrop-filter: blur(18px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eyebrow {
|
|
||||||
margin: 0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.14em;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #5c6b88;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar h2 {
|
|
||||||
margin: 0;
|
|
||||||
color: #12213a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-field {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-field :is(.p-inputtext) {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 0.95rem;
|
|
||||||
padding-left: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-stats article {
|
|
||||||
padding: 0.9rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
background: rgba(242, 246, 255, 0.88);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-stats span {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.35rem;
|
|
||||||
color: #667690;
|
|
||||||
font-size: 0.82rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-stats strong {
|
|
||||||
color: #12213a;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-list {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 28rem;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-list :is(.p-listbox, .p-listbox-list) {
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-list :is(.p-listbox-list) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-list :is(.p-listbox-item) {
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-list :is(.p-listbox-item.p-highlight) .requirement-item {
|
|
||||||
border-color: rgba(78, 107, 255, 0.34);
|
|
||||||
background: linear-gradient(135deg, rgba(78, 107, 255, 0.12), rgba(255, 255, 255, 0.92));
|
|
||||||
box-shadow: 0 12px 30px rgba(78, 107, 255, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
padding: 1rem;
|
|
||||||
border: 1px solid rgba(96, 117, 156, 0.14);
|
|
||||||
border-radius: 1rem;
|
|
||||||
background: rgba(248, 250, 255, 0.88);
|
|
||||||
transition:
|
|
||||||
transform 0.2s ease,
|
|
||||||
box-shadow 0.2s ease,
|
|
||||||
border-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-item:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
border-color: rgba(78, 107, 255, 0.22);
|
|
||||||
box-shadow: 0 10px 24px rgba(34, 49, 77, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-item__reference {
|
|
||||||
margin: 0 0 0.25rem;
|
|
||||||
color: #6c7b97;
|
|
||||||
font-size: 0.82rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-item strong {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
color: #12213a;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-item small {
|
|
||||||
color: #6b7b94;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-item__tags {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
min-height: 12rem;
|
|
||||||
color: #6b7b94;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state i {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
|
||||||
.mini-stats {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -182,10 +182,10 @@ const onNodeSelect = (node: TreeNode) => {
|
|||||||
.requirements-tree {
|
.requirements-tree {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem;
|
||||||
padding: 1rem;
|
padding: 0.75rem;
|
||||||
border: 1px solid rgba(96, 117, 156, 0.16);
|
border: 1px solid rgba(96, 117, 156, 0.16);
|
||||||
border-radius: 1.5rem;
|
border-radius: 0.5rem;
|
||||||
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);
|
||||||
@@ -196,8 +196,8 @@ const onNodeSelect = (node: TreeNode) => {
|
|||||||
.stats-bar {
|
.stats-bar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 0.5rem;
|
gap: 0.35rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.35rem;
|
||||||
border-bottom: 1px solid rgba(96, 117, 156, 0.12);
|
border-bottom: 1px solid rgba(96, 117, 156, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ const onNodeSelect = (node: TreeNode) => {
|
|||||||
|
|
||||||
.search-field :is(.p-inputtext) {
|
.search-field :is(.p-inputtext) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 0.8rem;
|
border-radius: 0.5rem;
|
||||||
padding-left: 0.85rem;
|
padding-left: 0.85rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
@@ -237,7 +237,7 @@ const onNodeSelect = (node: TreeNode) => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-radius: 0.8rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.requirements-tree-view {
|
.requirements-tree-view {
|
||||||
@@ -278,7 +278,7 @@ const onNodeSelect = (node: TreeNode) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ const onNodeSelect = (node: TreeNode) => {
|
|||||||
|
|
||||||
.node-tags {
|
.node-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.3rem;
|
gap: 0.25rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,8 +308,8 @@ const onNodeSelect = (node: TreeNode) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.requirements-tree {
|
.requirements-tree {
|
||||||
padding: 0.75rem;
|
padding: 0.6rem;
|
||||||
gap: 0.5rem;
|
gap: 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-field :is(.p-inputtext) {
|
.search-field :is(.p-inputtext) {
|
||||||
|
|||||||
@@ -115,11 +115,11 @@ const treeData = computed(() => buildTreeData())
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1.5rem;
|
gap: 1rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1rem;
|
||||||
padding: 1.5rem 1.75rem;
|
padding: 1rem 1.25rem;
|
||||||
border: 1px solid rgba(96, 117, 156, 0.16);
|
border: 1px solid rgba(96, 117, 156, 0.16);
|
||||||
border-radius: 1.5rem;
|
border-radius: 0.5rem;
|
||||||
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);
|
||||||
@@ -157,7 +157,7 @@ const treeData = computed(() => buildTreeData())
|
|||||||
.workspace {
|
.workspace {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(18rem, 22rem) minmax(0, 1fr);
|
grid-template-columns: minmax(18rem, 22rem) minmax(0, 1fr);
|
||||||
gap: 1.5rem;
|
gap: 1rem;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user