function Show-StarTimeline {
param($selectedItem)
if ($list.SelectedItems.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("Veuillez sélectionner un dossier dans la liste.")
return
}
# Récupération de la première ligne sélectionnée
$sel = $list.SelectedItems[0]
try {
$dEffet = [datetime]::ParseExact($sel.SubItems[3].Text.Trim(), "dd/MM/yyyy", $null)
$dCont = [datetime]::ParseExact($sel.SubItems[4].Text.Trim(), "dd/MM/yyyy", $null)
$dSusp = [datetime]::ParseExact($sel.SubItems[5].Text.Trim(), "dd/MM/yyyy", $null)
$dResil = [datetime]::ParseExact($sel.SubItems[6].Text.Trim(), "dd/MM/yyyy", $null)
$clientNom = $sel.SubItems[1].Text
$policeNum = $sel.Text
} catch {
[System.Windows.Forms.MessageBox]::Show("Erreur : Les dates dans le tableau doivent être au format dd/MM/yyyy (ex: 20/01/2026)")
return
}
# Étape dossier contentieux : toujours à Résiliation + 20 jours
$dContentieux = $dResil.AddDays(20)
# Création des étapes
$etapes = @(
@{Name="EFFET"; Date=$dEffet; Color=[System.Drawing.Color]::Cyan; Pos="Up"},
@{Name="MISE EN DEMEURE"; Date=$dCont; Color=[System.Drawing.Color]::Lime; Pos="Down"},
@{Name="SUSPENSION"; Date=$dSusp; Color=[System.Drawing.Color]::Yellow; Pos="Up"},
@{Name="RÉSILIATION"; Date=$dResil; Color=[System.Drawing.Color]::OrangeRed; Pos="Down"},
@{Name="DOSSIER CONTENTIEUX"; Date=$dContentieux; Color=[System.Drawing.Color]::Crimson; Pos="Up"}
)
# Configuration fenêtre
$timelineForm = New-Object System.Windows.Forms.Form
$timelineForm.Text = "STAR - ANALYSE TEMPORELLE"
$timelineForm.Size = New-Object System.Drawing.Size(1000, 500)
$timelineForm.StartPosition = "CenterParent"
$timelineForm.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$timelineForm.MaximizeBox = $false
$timelineForm.BackColor = [System.Drawing.Color]::FromArgb(40,40,60)
$timelineForm.Opacity = 0.95
$script:animProgress = 0.0
$script:glowStep = 0.0
$script:glowDirection = 1
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 15
$picTimeline = New-Object System.Windows.Forms.PictureBox
$picTimeline.Dock = "Fill"
$timelineForm.Controls.Add($picTimeline)
$picTimeline.Add_Paint({
param($sender,$e)
$g = $e.Graphics
$g.SmoothingMode = "AntiAlias"
$g.TextRenderingHint = "ClearTypeGridFit"
$today = (Get-Date).Date
# ===== AJOUT COMPLETION CYCLE =====
[double]$completion = 0.0
[string]$completionLabel = ""
$phaseColor = [System.Drawing.Color]::Cyan
if ($today -le $dResil.Date) {
# Phase 1 : Effet -> Résiliation
$totalPhase = ($dResil - $dEffet).TotalDays
if ($totalPhase -le 0) { $totalPhase = 1 }
$completion = (($today - $dEffet).TotalDays / $totalPhase) * 100
$completionLabel = "CYCLE CONTRAT"
$phaseColor = [System.Drawing.Color]::Cyan
}
elseif ($today -le $dContentieux.Date) {
# Phase 2 : J+1 Résiliation -> Contentieux
$totalPhase = ($dContentieux - $dResil.AddDays(1)).TotalDays
if ($totalPhase -le 0) { $totalPhase = 1 }
$completion = (($today - $dResil.AddDays(1)).TotalDays / $totalPhase) * 100
$completionLabel = "DOSSIER CLOS"
$phaseColor = [System.Drawing.Color]::Crimson
}
else {
$completion = 100
$completionLabel = "TERMINÉE"
$phaseColor = [System.Drawing.Color]::OrangeRed
}
if ($completion -lt 0) { $completion = 0 }
if ($completion -gt 100) { $completion = 100 }
$completionTxt = "{0:N2} %" -f $completion
# ================================
[float]$startX = 100
[float]$endX = $sender.Width - 100
[float]$yBase = $sender.Height / 2 + 20
# Tri de la dernière étape pour calcul durée totale
$lastDate = ($etapes | Sort-Object Date | Select-Object -Last 1).Date
[double]$totalJours = ($lastDate - $dEffet).TotalDays
if ($totalJours -le 0) { $totalJours = 1 }
# Prochaine étape future
$prochaine = $null
foreach($et in ($etapes | Sort-Object Date)) {
if ($et.Date.Date -ge $today) { $prochaine = $et; break }
}
# Animation progression
$easedT = 1 - [Math]::Pow(1 - $script:animProgress, 3)
[float]$ratioTarget = ($today - $dEffet).TotalDays / $totalJours
if ($ratioTarget -lt 0) { $ratioTarget = 0 }
if ($ratioTarget -gt 1) { $ratioTarget = 1 }
[float]$currentX = $startX + (($ratioTarget * $easedT) * ($endX - $startX))
# Ligne dégradée
$rectLine = [System.Drawing.RectangleF]::FromLTRB($startX, $yBase-1, ($currentX + 1), $yBase+1)
if ($rectLine.Width -gt 1) {
$brushL = New-Object System.Drawing.Drawing2D.LinearGradientBrush($rectLine, [System.Drawing.Color]::Cyan, [System.Drawing.Color]::OrangeRed, 0.0)
$g.DrawLine((New-Object System.Drawing.Pen($brushL, 2)), $startX, $yBase, $currentX, $yBase)
}
$g.DrawLine((New-Object System.Drawing.Pen([System.Drawing.Color]::FromArgb(40, 40, 50), 2)), $currentX, $yBase, $endX, $yBase)
# Dessin des étapes
foreach ($et in $etapes) {
[float]$ratioX = ($et.Date - $dEffet).TotalDays / $totalJours
[float]$posX = $startX + ($ratioX * ($endX - $startX))
$isPassed = $today -gt $et.Date.Date
$alphaBox = if ($isPassed) { 30 } else { 180 }
$accent = if ($isPassed) { [System.Drawing.Color]::FromArgb(80, 120, 120, 120) } else { $et.Color }
$textColor = if ($isPassed) { [System.Drawing.Color]::Gray } else { [System.Drawing.Color]::White }
$boxW = 135; $boxH = 50
$boxY = if ($et.Pos -eq "Up") { $yBase - 110 } else { $yBase + 60 }
# Glassmorphism
$g.FillRectangle((New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb($alphaBox, 10, 10, 20))), ($posX - $boxW/2), $boxY, $boxW, $boxH)
$g.DrawRectangle((New-Object System.Drawing.Pen([System.Drawing.Color]::FromArgb(40, 255, 255, 255))), ($posX - $boxW/2), $boxY, $boxW, $boxH)
$barY = if ($et.Pos -eq "Up") { ($boxY + $boxH - 4) } else { $boxY }
$g.FillRectangle((New-Object System.Drawing.SolidBrush($accent)), ($posX - $boxW/2), $barY, $boxW, 4)
$g.DrawString($et.Name, (New-Object System.Drawing.Font("Segoe UI", 8, [System.Drawing.FontStyle]::Bold)), (New-Object System.Drawing.SolidBrush($textColor)), ($posX - $boxW/2 + 8), ($boxY + 10))
$g.DrawString($et.Date.ToString("dd MMM yyyy"), (New-Object System.Drawing.Font("Consolas", 8)), (New-Object System.Drawing.SolidBrush($accent)), ($posX - $boxW/2 + 8), ($boxY + 28))
$lineYEnd = if ($et.Pos -eq "Up") { $boxY + $boxH } else { $boxY }
$g.DrawLine((New-Object System.Drawing.Pen($accent, 1)), $posX, $yBase, $posX, $lineYEnd)
$g.FillEllipse((New-Object System.Drawing.SolidBrush($accent)), ($posX - 6), ($yBase - 6), 12, 12)
}
# Triangle et point "aujourd'hui"
$pSize = 10 + ($script:glowStep * 20)
$pAlpha = [int](180 * (1.0 - $script:glowStep))
$g.FillEllipse((New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb($pAlpha, [System.Drawing.Color]::Lime))), ($currentX - $pSize/2), ($yBase - $pSize/2), $pSize, $pSize)
$g.FillEllipse([System.Drawing.Brushes]::White, ($currentX - 4), ($yBase - 4), 8, 8)
$triPts = @(
(New-Object System.Drawing.PointF($currentX, ($yBase - 12))),
(New-Object System.Drawing.PointF(($currentX - 6), ($yBase - 22))),
(New-Object System.Drawing.PointF(($currentX + 6), ($yBase - 22)))
)
$g.FillPolygon([System.Drawing.Brushes]::White, $triPts)
# Header
$g.DrawString("$($clientNom.ToUpper()) | $policeNum", (New-Object System.Drawing.Font("Segoe UI Light", 16)), [System.Drawing.Brushes]::White, 40, 20)
if ($prochaine) {
$diff = ($prochaine.Date.Date - $today).Days
$txt = if ($diff -eq 0) { "ÉCHÉANCE : AUJOURD'HUI ($($prochaine.Name))" } else { "$($prochaine.Name) DANS $diff JOURS" }
$g.DrawString($txt, (New-Object System.Drawing.Font("Segoe UI", 14, [System.Drawing.FontStyle]::Bold)), (New-Object System.Drawing.SolidBrush($prochaine.Color)), 40, 55)
} else {
$g.DrawString("DOSSIER RÉSILLIÉ / CLÔTURÉ", (New-Object System.Drawing.Font("Segoe UI", 16, [System.Drawing.FontStyle]::Bold)), [System.Drawing.Brushes]::OrangeRed, 40, 55)
$g.DrawString("PROCÉDURE DE RECOUVREMENT TERMINÉE", (New-Object System.Drawing.Font("Segoe UI", 9)), [System.Drawing.Brushes]::Crimson, 43, 85)
}
# ===== AJOUT HUD COMPLETION =====
$hudSize = 140
$hudX = $sender.Width - $hudSize - 850
$hudY = $sender.Height - $hudSize - 10
# Fond glass
$g.FillEllipse(
(New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(40,10,10,20))),
$hudX, $hudY, $hudSize, $hudSize
)
# Cercle de fond
$g.DrawEllipse(
(New-Object System.Drawing.Pen([System.Drawing.Color]::FromArgb(40,255,255,255),6)),
$hudX + 10, $hudY + 10, $hudSize - 20, $hudSize - 20
)
# Arc de progression
$g.DrawArc(
(New-Object System.Drawing.Pen($phaseColor,6)),
$hudX + 10, $hudY + 10, $hudSize - 20, $hudSize - 20,
-90, ($completion / 100) * 360
)
# Texte %
$g.DrawString(
$completionTxt,
(New-Object System.Drawing.Font("Segoe UI",14,[System.Drawing.FontStyle]::Bold)),
[System.Drawing.Brushes]::White,
$hudX + 38, $hudY + 55
)
# Libellé phase
$g.DrawString(
$completionLabel,
(New-Object System.Drawing.Font("Segoe UI",8)),
(New-Object System.Drawing.SolidBrush($phaseColor)),
$hudX + 28, $hudY + 85
)
# ================================
#footer
#$g.DrawString("CLIENT : $($clientNom.ToUpper()) | POLICE : $policeNum", (New-Object System.Drawing.Font("Segoe UI", 20)), [System.Drawing.Brushes]::Gray, 130, $sender.Height - 60)
})
$timer.Add_Tick({
if ($script:animProgress -lt 1.0) { $script:animProgress += 0.02 }
$script:glowStep += (0.05 * $script:glowDirection)
if ($script:glowStep -ge 1.0 -or $script:glowStep -le 0.0) { $script:glowDirection *= -1 }
$picTimeline.Invalidate()
})
$timelineForm.Add_Load({ $timer.Start() })
$timelineForm.ShowDialog()
$timer.Dispose()
}
###########################################################
function Get-Client {
param(
[string]$Contrat,
[string]$CsvFile
)
if (Test-Path $CsvFile) {
$client = Import-Csv $CsvFile | Where-Object { $_.Police -eq $Contrat }
if ($client) { return $client[0] } else { return $null }
} else {
return $null
}
}
function Get-ClientsByYear {
param(
[int]$Year,
[string]$RootPath
)
$csvFile = Join-Path $RootPath "detail\detail$Year.csv"
if (Test-Path $csvFile) {
return Import-Csv $csvFile
} else {
return @()
}
}
function Save-Client {
param(
[pscustomobject]$Client,
[string]$CsvFile
)
# Crée le fichier si inexistant
if (-not (Test-Path $CsvFile)) {
$Client | Export-Csv -Path $CsvFile -NoTypeInformation
return
}
# Lire le CSV existant
$clients = Import-Csv -Path $CsvFile
# Remplacer si Police existe déjà
$clients = $clients | Where-Object { $_.Police -ne $Client.Police }
# Ajouter le nouveau client
$clients += $Client
# Sauvegarder
$clients | Export-Csv -Path $CsvFile -NoTypeInformation
}
# =========================
# FONCTION AJOUT CHAMPS FORM
# =========================
function Add-Field {
param(
[System.Windows.Forms.Panel]$Panel,
[ref]$Y,
[string]$Label,
[string]$Type = 'TextBox',
$Value = $null,
[int]$Height = 28,
[bool]$Multiline = $false,
[bool]$ReadOnly = $false,
[string[]]$Items = @()
)
# Label
$lbl = New-Object System.Windows.Forms.Label
$lbl.Text = $Label
$lbl.Location = New-Object System.Drawing.Point(20, $Y.Value)
$lbl.AutoSize = $true
$Panel.Controls.Add($lbl)
# Contrôle
switch ($Type) {
'TextBox' {
$ctrl = New-Object System.Windows.Forms.TextBox
$ctrl.Multiline = $Multiline
$ctrl.ReadOnly = $ReadOnly
$ctrl.Text = $Value
$ctrl.Height = if ($Multiline) { [Math]::Max($Height, 60) } else { $Height }
$ctrl.Width = 620
}
'ComboBox' {
$ctrl = New-Object System.Windows.Forms.ComboBox
$ctrl.DropDownStyle = 'DropDownList'
if ($Items) { $ctrl.Items.AddRange($Items) }
if ($Value) { $ctrl.SelectedItem = $Value }
$ctrl.Size = New-Object System.Drawing.Size(620,28)
}
'DateTimePicker' {
$ctrl = New-Object System.Windows.Forms.DateTimePicker
try { if ($Value) { $ctrl.Value = [datetime]$Value } } catch {}
$ctrl.Size = New-Object System.Drawing.Size(620,28)
}
'NumericUpDown' {
$ctrl = New-Object System.Windows.Forms.NumericUpDown
$ctrl.Maximum = 1000
$ctrl.Minimum = 0
$ctrl.Value = if ($Value) { [decimal]$Value } else { 0 }
$ctrl.Size = New-Object System.Drawing.Size(620,28)
}
default { throw "Type inconnu: $Type" }
}
$ctrl.Location = New-Object System.Drawing.Point(20, ($Y.Value + 20))
$Panel.Controls.Add($ctrl)
$Y.Value += $ctrl.Height + 25
return $ctrl
}
# =========================
# FONCTION FORM DETAIL CLIENT
# =========================
function Show-ClientDetailForm {
param(
[System.Windows.Forms.ListView]$List,
[string]$RootPath
)
if ($List.SelectedItems.Count -eq 0) { return $false }
$policeId = $List.SelectedItems[0].Text
$year = ($policeId -replace '\D','')
$detailFolder = Join-Path $RootPath "detail"
if (-not (Test-Path $detailFolder)) { New-Item -ItemType Directory $detailFolder | Out-Null }
$csvFile = Join-Path $detailFolder "detail$year.csv"
$client = Get-Client -Contrat $policeId -CsvFile $csvFile
# ========= FORM =========
$form = New-Object System.Windows.Forms.Form
$form.Text = "Fiche Client – $($List.SelectedItems[0].SubItems[1].Text)"
$form.Size = '800,600'
$form.StartPosition = 'CenterParent'
$form.FormBorderStyle = 'FixedDialog'
$form.MaximizeBox = $false
$form.Font = New-Object System.Drawing.Font("Segoe UI",10)
$tabs = New-Object System.Windows.Forms.TabControl
$tabs.Dock = 'Top'
$tabs.Height = 500
$form.Controls.Add($tabs)
function New-Tab($name) {
$tab = New-Object System.Windows.Forms.TabPage
$tab.Text = $name
$panel = New-Object System.Windows.Forms.Panel
$panel.Dock = 'Fill'
$panel.AutoScroll = $true
$tab.Controls.Add($panel)
$tabs.TabPages.Add($tab)
return $panel
}
# ========= ASSURÉ =========
$y = 15
$pAssure = New-Tab "Assuré"
$tbNom = Add-Field $pAssure ([ref]$y) "Nom" 'TextBox' $($List.SelectedItems[0].SubItems[1].Text) 28 $false $true
$cbCategorie = Add-Field $pAssure ([ref]$y) "Catégorie" 'ComboBox' $client.Categorie $null $false $false @("Personne Physique","Personne Morale")
$cbGenre = Add-Field $pAssure ([ref]$y) "Genre" 'ComboBox' $client.Genre $null $false $false @("Homme","Femme")
$dpDateNaiss = Add-Field $pAssure ([ref]$y) "Date de naissance" 'DateTimePicker' $client.DateNaissance
$tbCin = Add-Field $pAssure ([ref]$y) "CIN / MF" 'TextBox' $client.CIN
$tbPro = Add-Field $pAssure ([ref]$y) "Situation professionnelle" 'TextBox' $client.SituationPro
$cbFamilial = Add-Field $pAssure ([ref]$y) "Situation familiale" 'ComboBox' $client.SituationFamilial $null $false $false @("Marié","Divorcé","Célibataire")
$nudEnfant = Add-Field $pAssure ([ref]$y) "Nombre d'enfants" 'NumericUpDown' $client.NbEnfants
$tbAdresse = Add-Field $pAssure ([ref]$y) "Adresse" 'TextBox' $client.Adresse 60 $true
# ========= CONTRAT =========
$y = 15
$pContrat = New-Tab "Contrat"
$tbPolice = Add-Field $pContrat ([ref]$y) "Police" 'TextBox' $policeId 28 $false $true
$dpDateOrigine= Add-Field $pContrat ([ref]$y) "Date d'origine" 'DateTimePicker' $client.DateOrigine
# ========= VEHICULE =========
$y = 15
$pVehicule = New-Tab "Véhicule"
$tbType = Add-Field $pVehicule ([ref]$y) "Type" 'TextBox' $client.Type
$tbImmat = Add-Field $pVehicule ([ref]$y) "Immatriculation" 'TextBox' $client.Immatriculation
$dpMC = Add-Field $pVehicule ([ref]$y) "1.M.C" 'DateTimePicker' $client.DateMC
$tbValeur = Add-Field $pVehicule ([ref]$y) "Valeur vénale" 'TextBox' $client.ValeurVenale
# ========= CONTACT =========
$y = 15
$pContact = New-Tab "Contact"
$tbTel1 = Add-Field $pContact ([ref]$y) "Téléphone 1" 'TextBox' $client.Tel1
$tbTel2 = Add-Field $pContact ([ref]$y) "Téléphone 2" 'TextBox' $client.Tel2
$tbEmail = Add-Field $pContact ([ref]$y) "Email" 'TextBox' $client.Email
# ========= NOTES =========
$y = 15
$pNotes = New-Tab "Notes"
$tbNotes = Add-Field $pNotes ([ref]$y) "Observations" 'TextBox' $client.Notes 90 $true
# ========= BOUTONS =========
$btnSave = New-Object System.Windows.Forms.Button
$btnSave.Text = "Enregistrer"
$btnSave.Location = '600,520'
$btnSave.Size = '150,35'
$form.Controls.Add($btnSave)
$btnClose = New-Object System.Windows.Forms.Button
$btnClose.Text = "Fermer"
$btnClose.Location = '430,520'
$btnClose.Size = '150,35'
$form.Controls.Add($btnClose)
# ========= LOGIQUE =========
$updateControls = {
$cbGenre.Enabled = ($cbCategorie.SelectedItem -eq "Personne Physique")
$nudEnfant.Enabled = ($cbFamilial.SelectedItem -in @("Marié","Divorcé"))
}
$updateControls.Invoke()
$cbCategorie.Add_SelectedIndexChanged($updateControls)
$cbFamilial.Add_SelectedIndexChanged($updateControls)
# ======= EVENTS =======
$btnSave.Add_Click({
if ([string]::IsNullOrWhiteSpace($tbPolice.Text)) {
[System.Windows.Forms.MessageBox]::Show("Police obligatoire","Erreur",'OK','Error')
return
}
# Création de l'objet client
$new = [pscustomobject]@{
Nom = $tbNom.Text
Categorie = $cbCategorie.SelectedItem
Genre = $cbGenre.SelectedItem
DateNaissance = $dpDateNaiss.Value.ToShortDateString()
CIN = $tbCin.Text
SituationPro = $tbPro.Text
SituationFamilial = $cbFamilial.SelectedItem
NbEnfants = [int]$nudEnfant.Value
Adresse = $tbAdresse.Text
Police = $tbPolice.Text
DateOrigine = $dpDateOrigine.Value.ToShortDateString()
Type = $tbType.Text
Immatriculation = $tbImmat.Text
DateMC = $dpMC.Value.ToShortDateString()
ValeurVenale = $tbValeur.Text
Tel1 = $tbTel1.Text
Tel2 = $tbTel2.Text
Email = $tbEmail.Text
Notes = $tbNotes.Text
}
try {
Save-Client -Client $new -CsvFile $csvFile
[System.Windows.Forms.MessageBox]::Show("Client enregistré !","Succès")
$form.Tag = $true
$form.Close()
}
catch {
[System.Windows.Forms.MessageBox]::Show($_.Exception.Message,"Erreur",'OK','Error')
}
})
$btnClose.Add_Click({ $form.Close() })
$form.ShowDialog() | Out-Null
return [bool]$form.Tag
}
# =========================
# BOUTON DETAIL LISTVIEW
# =========================
$btnDetail.Add_Click({
Show-ClientDetailForm -List $list -RootPath $PSScriptRoot
})