New-Thanasoft/thanasoft-front/refont_interview/InterventionDetailSidebar.vue
nyavokevin 9cbc1bcbdb feat(ui): add price lists and group-based quote flows
Add price list management across the API, store, services, routes,
navigation, and sales views.

Support quotes for either a client or a client group, including PDF
download and nullable client validation for group-based recipients.

Extend client groups to manage assigned clients directly from the form
and detail views, and refresh supplier, intervention, stock, and order
screens with updated interactions and layouts.
2026-04-02 12:07:11 +03:00

231 lines
10 KiB
Vue

<template>
<div class="sidebar-wrap">
<!-- Hero Card -->
<div class="hero-card">
<div class="hero-avatar">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
</div>
<h2 class="hero-name">{{ intervention.defuntName || 'Personne inconnue' }}</h2>
<p class="hero-type">{{ intervention.title || 'Type non défini' }}</p>
<div class="status-badge" :class="'sb-' + (intervention.status?.color || 'secondary')">
{{ intervention.status?.label || 'En attente' }}
</div>
</div>
<div class="divider"></div>
<!-- Quick Stats -->
<div class="quick-stats">
<div class="qs-row">
<div class="qs-icon" style="background:#eef2ff;color:#4f46e5">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
</div>
<div class="qs-text">
<div class="qs-label">Date</div>
<div class="qs-value">{{ intervention.date || '—' }}</div>
</div>
</div>
<div class="qs-row">
<div class="qs-icon" style="background:#ecfdf5;color:#059669">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
</div>
<div class="qs-text">
<div class="qs-label">Lieu</div>
<div class="qs-value">{{ intervention.lieux || '—' }}</div>
</div>
</div>
<div class="qs-row">
<div class="qs-icon" style="background:#fff7ed;color:#d97706">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
</div>
<div class="qs-text">
<div class="qs-label">Durée</div>
<div class="qs-value">{{ intervention.duree || '—' }}</div>
</div>
</div>
</div>
<div class="divider"></div>
<!-- Team preview -->
<div v-if="intervention.members?.length" class="team-preview">
<div class="tp-label">Équipe</div>
<div class="tp-avatars">
<div
v-for="(m, i) in intervention.members.slice(0, 5)"
:key="i"
class="tp-avatar"
:title="m.name"
:style="{ zIndex: 10 - i }"
>
{{ getInitials(m.name) }}
</div>
<div v-if="intervention.members.length > 5" class="tp-avatar tp-more">
+{{ intervention.members.length - 5 }}
</div>
</div>
</div>
<div class="divider"></div>
<!-- Tab Navigation -->
<nav class="tab-nav">
<button
v-for="tab in tabs"
:key="tab.id"
class="tab-item"
:class="{ active: activeTab === tab.id }"
@click="$emit('change-tab', tab.id)"
>
<span class="tab-icon" v-html="tab.icon"></span>
<span class="tab-label">{{ tab.label }}</span>
<span v-if="tab.id === 'team' && teamCount > 0" class="tab-badge">{{ teamCount }}</span>
<span v-if="tab.id === 'documents' && documentsCount > 0" class="tab-badge">{{ documentsCount }}</span>
</button>
</nav>
<!-- Assign Button -->
<div class="assign-wrap">
<button class="assign-btn" @click="$emit('assign-practitioner')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="8.5" cy="7" r="4"/>
<line x1="20" y1="8" x2="20" y2="14"/>
<line x1="23" y1="11" x2="17" y2="11"/>
</svg>
Assigner un praticien
</button>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
defineProps({
intervention: { type: Object, required: true },
activeTab: { type: String, default: 'overview' },
practitioners:{ type: Array, default: () => [] },
teamCount: { type: Number, default: 0 },
documentsCount:{ type: Number, default: 0 },
});
defineEmits(['change-tab', 'assign-practitioner']);
const getInitials = n => n ? n.split(' ').map(w => w[0]).join('').toUpperCase().substring(0,2) : '?';
const tabs = [
{ id:'overview', label:"Vue d'ensemble", icon:`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>` },
{ id:'details', label:'Détails', icon:`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>` },
{ id:'team', label:'Équipe', icon:`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>` },
{ id:'documents', label:'Documents', icon:`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>` },
{ id:'quote', label:'Devis', icon:`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>` },
{ id:'history', label:'Historique', icon:`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4.5"/></svg>` },
];
</script>
<style scoped>
.sidebar-wrap {
--brand: #4f46e5;
--brand-lt: #eef2ff;
--brand-dk: #3730a3;
--surface: #ffffff;
--surface-2:#f8fafc;
--border: #e2e8f0;
--border-lt:#f1f5f9;
--text-1: #0f172a;
--text-2: #64748b;
--text-3: #94a3b8;
--r-sm: 8px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,.06);
font-family: 'Inter', system-ui, sans-serif;
}
/* Hero */
.hero-card {
padding: 24px 20px 18px;
display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center;
}
.hero-avatar {
width: 58px; height: 58px; border-radius: 50%;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
display: flex; align-items: center; justify-content: center;
color: white; margin-bottom: 2px;
box-shadow: 0 4px 14px rgba(79,70,229,.28);
}
.hero-name { font-size: 15px; font-weight: 700; color: var(--text-1); margin: 0; line-height: 1.3; }
.hero-type { font-size: 12px; color: var(--text-2); margin: 0; font-weight: 500; }
/* Status badge */
.status-badge { display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 20px; font-size: 11.5px; font-weight: 600; }
.sb-success { background:#dcfce7; color:#16a34a; }
.sb-warning { background:#fef9c3; color:#ca8a04; }
.sb-danger { background:#fee2e2; color:#dc2626; }
.sb-info { background:#dbeafe; color:#2563eb; }
.sb-primary { background:#eef2ff; color:#4f46e5; }
.sb-secondary{ background:#f1f5f9; color:#64748b; }
.divider { height: 1px; background: var(--border-lt); }
/* Quick stats */
.quick-stats { padding: 14px 18px; display: flex; flex-direction: column; gap: 10px; }
.qs-row { display: flex; align-items: flex-start; gap: 10px; }
.qs-icon { width: 28px; height: 28px; border-radius: 7px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.qs-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .5px; color: var(--text-3); font-weight: 600; }
.qs-value { font-size: 12.5px; color: var(--text-1); font-weight: 500; margin-top: 1px; }
/* Team preview */
.team-preview { padding: 12px 18px; display: flex; align-items: center; gap: 12px; }
.tp-label { font-size: 11.5px; font-weight: 600; color: var(--text-3); text-transform: uppercase; letter-spacing: .4px; }
.tp-avatars { display: flex; }
.tp-avatar {
width: 30px; height: 30px; border-radius: 50%;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
border: 2px solid var(--surface);
color: white; font-size: 10px; font-weight: 700;
display: flex; align-items: center; justify-content: center;
margin-left: -6px; cursor: default;
transition: transform .15s;
}
.tp-avatar:first-child { margin-left: 0; }
.tp-avatar:hover { transform: translateY(-3px); }
.tp-more { background: var(--surface-2); color: var(--text-2); font-size: 9px; }
/* Tab nav */
.tab-nav { padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; }
.tab-item {
display: flex; align-items: center; gap: 9px; padding: 8px 11px;
border-radius: var(--r-sm); border: none; background: transparent; cursor: pointer;
width: 100%; text-align: left; font-size: 13px; font-weight: 500; color: var(--text-2);
transition: all .12s;
}
.tab-item:hover { background: var(--surface-2); color: var(--text-1); }
.tab-item.active { background: var(--brand-lt); color: var(--brand); font-weight: 600; }
.tab-icon { flex-shrink: 0; display: flex; color: var(--text-3); }
.tab-item.active .tab-icon { color: var(--brand); }
.tab-label { flex: 1; }
.tab-badge {
min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
background: var(--brand); color: white; font-size: 10px; font-weight: 700;
display: flex; align-items: center; justify-content: center;
}
/* Assign */
.assign-wrap { padding: 0 10px 12px; }
.assign-btn {
width: 100%; display: flex; align-items: center; justify-content: center; gap: 7px;
padding: 9px; border: 1.5px dashed var(--border); border-radius: var(--r-sm);
background: transparent; cursor: pointer; font-size: 12.5px; font-weight: 500; color: var(--text-2);
transition: all .15s;
}
.assign-btn:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-lt); }
</style>