update ui sizes and distances

This commit is contained in:
Sylvain Schneider
2026-05-11 23:20:59 +02:00
parent e73ba429e7
commit b742a9a59e
4 changed files with 29 additions and 354 deletions

View File

@@ -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);
} }

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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;
} }