function Write-HistoryLog {
param(
[Parameter(Mandatory)]
[string]$Action,
[Parameter(Mandatory)]
[string]$Contrat,
[Parameter(Mandatory)]
[string]$Details
)
$logPath = "C:\Users\walid\Desktop\test\detail\historique.csv"
if (-not (Test-Path $logPath)) {
"Date,Action,Contrat,Details,Utilisateur" | Out-File $logPath -Encoding UTF8
}
$logObject = [PSCustomObject]@{
Date = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
Action = $Action
Contrat = $Contrat
Details = $Details
Utilisateur= $currentuser
}
$logObject | Export-Csv $logPath -Append -NoTypeInformation -Encoding UTF8
}
$btnHistory = New-Object System.Windows.Forms.Button
$btnHistory.Text = "Historique"
$btnHistory.Location = New-Object System.Drawing.Point(760,120)
$btnHistory.Size = New-Object System.Drawing.Size(140,30)
$btnHistory.BackColor = $colorButton
$btnHistory.ForeColor = [System.Drawing.Color]::White
$btnHistory.Enabled = $True
$grp.Controls.Add($btnHistory)
# Action du bouton Historique
$btnHistory.Add_Click({
$formHistory = New-Object System.Windows.Forms.Form
$formHistory.Text = "🕒 Historique des modifications"
$formHistory.Size = New-Object System.Drawing.Size(850,450)
$formHistory.StartPosition = "CenterScreen"
$formHistory.BackColor = [System.Drawing.Color]::FromArgb(20,20,20)
$formHistory.ForeColor = [System.Drawing.Color]::White
$formHistory.FormBorderStyle = "FixedSingle"
$formHistory.MaximizeBox = $false
$formHistory.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$lvHistory = New-Object System.Windows.Forms.ListView
$lvHistory.View = "Details"
$lvHistory.FullRowSelect = $true
$lvHistory.GridLines = $false
$lvHistory.Dock = "Fill"
$lvHistory.BackColor = [System.Drawing.Color]::FromArgb(30,30,30)
$lvHistory.ForeColor = [System.Drawing.Color]::Gainsboro
$lvHistory.BorderStyle = "None"
$lvHistory.Columns.Add("Date / Heure",140) | Out-Null
$lvHistory.Columns.Add("Action",110) | Out-Null
$lvHistory.Columns.Add("Contrat",120) | Out-Null
$lvHistory.Columns.Add("Détails",280) | Out-Null
$lvHistory.Columns.Add("Utilisateur",120) | Out-Null
$logPath = "C:\Users\walid\Desktop\test\detail\historique.csv"
if (Test-Path $logPath) {
Import-Csv $logPath | Sort-Object Date -Descending | ForEach-Object {
$item = New-Object System.Windows.Forms.ListViewItem($_.Date)
$item.SubItems.Add($_.Action) | Out-Null
$item.SubItems.Add($_.Contrat) | Out-Null
$item.SubItems.Add($_.Details) | Out-Null
$item.SubItems.Add($CurrentUser) | Out-Null
$lvHistory.Items.Add($item) | Out-Null
}
}
$formHistory.Controls.Add($lvHistory)
$formHistory.ShowDialog()
})
####################################################### SEARCH
$grpSearch = New-Object System.Windows.Forms.GroupBox
$grpSearch.Text = "🔍 Recherche"
$grpSearch.Location = New-Object System.Drawing.Point(20,520)
$grpSearch.Size = New-Object System.Drawing.Size(1080,70)
$grpSearch.ForeColor = [System.Drawing.Color]::DeepSkyBlue
$grpSearch.BackColor = [System.Drawing.Color]::FromArgb(20,20,35) # fond sombre futuriste
$grpSearch.Font = New-Object System.Drawing.Font("Segoe UI Semibold", 12, [System.Drawing.FontStyle]::Bold)
$grpSearch.Anchor = "Bottom,Left,Right"
$form.Controls.Add($grpSearch)
$tbSearch = New-Object System.Windows.Forms.TextBox
$tbSearch.Location = New-Object System.Drawing.Point(20,26)
$tbSearch.Size = New-Object System.Drawing.Size(450,28)
$tbSearch.BackColor = [System.Drawing.Color]::FromArgb(30,30,50)
$tbSearch.ForeColor = [System.Drawing.Color]::Lime
$tbSearch.Font = New-Object System.Drawing.Font("Consolas", 11, [System.Drawing.FontStyle]::Bold)
$tbSearch.BorderStyle = "FixedSingle"
$grpSearch.Controls.Add($tbSearch)
# Bouton Réinitialiser
$btnReset = New-Object System.Windows.Forms.Button
$btnReset.Text = "⟲"
$btnReset.Location = New-Object System.Drawing.Point(480,24)
$btnReset.Size = New-Object System.Drawing.Size(45,30)
$btnReset.BackColor = [System.Drawing.Color]::FromArgb(50,50,80)
$btnReset.ForeColor = [System.Drawing.Color]::Cyan
$btnReset.FlatStyle = "Flat"
$btnReset.Font = New-Object System.Drawing.Font("Segoe UI", 14, [System.Drawing.FontStyle]::Bold)
$grpSearch.Controls.Add($btnReset)
$form.Add_Shown({ Refresh-AllItems })
# Filtrage automatique sur toutes les colonnes
$tbSearch.Add_TextChanged({
$searchText = $tbSearch.Text.ToLower()
$list.BeginUpdate()
$list.Items.Clear()
$results = $allItems | Where-Object {
$_.SubItems.Text.ToLower() -match $searchText
}
if($results) {
foreach ($res in $results) {
$null = $list.Items.Add($res.Clone())
}
}
$list.EndUpdate()
})
# Réinitialisation
$btnReset.Add_Click({
$tbSearch.Clear()
$list.Items.Clear()
foreach ($i in $allItems) { $list.Items.Add($i.Clone()) | Out-Null }
})
##################################################### EXCEL
$btnExportExcel = New-Object System.Windows.Forms.Button
$btnExportExcel.Text = "Export Excel"
$btnExportExcel.Location = New-Object System.Drawing.Point(920,120)
$btnExportExcel.Size = New-Object System.Drawing.Size(140,30)
$btnExportExcel.BackColor = $colorButton
$btnExportExcel.ForeColor = [System.Drawing.Color]::White
$btnExportExcel.Enabled = $True
$grp.Controls.Add($btnExportExcel)
####################################################### timeline
function Show-StarTimeline {
param($selectedItem)
# 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 2026"
$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
[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("STAR - CYCLE TEMPOREL", (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)
}
$g.DrawString("CLIENT : $($clientNom.ToUpper()) | POLICE : $policeNum", (New-Object System.Drawing.Font("Segoe UI", 9)), [System.Drawing.Brushes]::Gray, 40, $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 Show-DetailedAudit {
param (
[Parameter(Mandatory=$true)]
[System.Windows.Forms.ListView]$list
)
if ($list.Items.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("⚠️ Pas de données dans la liste !")
return
}
# --- Fenêtre ---
$form = New-Object System.Windows.Forms.Form
$form.Text = "🕵️ Audit détaillé des contrats"
$form.Size = New-Object System.Drawing.Size(1000,600)
$form.StartPosition = "CenterScreen"
$form.BackColor = [System.Drawing.Color]::FromArgb(18,18,32)
$form.Opacity = 0.95
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$form.MaximizeBox = $false
# --- RichTextBox ---
$txt = New-Object System.Windows.Forms.RichTextBox
$txt.Multiline = $true
$txt.ScrollBars = "Vertical"
$txt.ReadOnly = $true
$txt.Font = New-Object System.Drawing.Font("Consolas",10)
$txt.BackColor = [System.Drawing.Color]::FromArgb(25,25,50)
$txt.ForeColor = [System.Drawing.Color]::White
$txt.Dock = "Fill"
$form.Controls.Add($txt)
$today = (Get-Date).Date
function Calculate-RiskScore {
param($item)
$score = 0
try {
$dEffet = [datetime]::ParseExact($item.SubItems[3].Text.Trim(),"dd/MM/yyyy",$null)
$dMDR = [datetime]::ParseExact($item.SubItems[4].Text.Trim(),"dd/MM/yyyy",$null)
$dSusp = [datetime]::ParseExact($item.SubItems[5].Text.Trim(),"dd/MM/yyyy",$null)
$dResil = [datetime]::ParseExact($item.SubItems[6].Text.Trim(),"dd/MM/yyyy",$null)
$daysToResil = ($dResil - $today).TotalDays
if ($daysToResil -le 0) { $score += 50 } else { $score += [math]::Max(0,30 - $daysToResil) }
if ($dMDR -le $today) { $score += 20 }
if ($dSusp -le $today) { $score += 20 }
if ($score -gt 100) { $score = 100 }
} catch { $score = 0 }
return [math]::Round($score,1)
}
$txt.AppendText("📝 Audit détaillé des contrats au $($today.ToShortDateString())`r`n")
$txt.AppendText("==============================================================`r`n`r`n")
foreach ($item in $list.Items) {
try { $dEffet = [datetime]::ParseExact($item.SubItems[3].Text.Trim(),"dd/MM/yyyy",$null) } catch { $dEffet=$null }
try { $dMDR = [datetime]::ParseExact($item.SubItems[4].Text.Trim(),"dd/MM/yyyy",$null) } catch { $dMDR=$null }
try { $dSusp = [datetime]::ParseExact($item.SubItems[5].Text.Trim(),"dd/MM/yyyy",$null) } catch { $dSusp=$null }
try { $dResil = [datetime]::ParseExact($item.SubItems[6].Text.Trim(),"dd/MM/yyyy",$null) } catch { $dResil=$null }
$client = $item.SubItems[1].Text
$police = $item.SubItems[0].Text
$statuts = @()
if ($dResil -ne $null) {
if ($dResil -gt $today) { $statuts += "✅ Actif" } else { $statuts += "❌ Résilié" }
} else { $statuts += "❓ Résiliation inconnue" }
if ($dMDR -ne $null -and $dMDR -le $today) { $statuts += "💌 Mise en demeure" }
if ($dSusp -ne $null -and $dSusp -le $today) { $statuts += "⏸ Suspension" }
if ($dResil -ne $null -and ($dResil.AddDays(20) -le $today)) { $statuts += "⚠️ Contentieux" }
$score = Calculate-RiskScore -item $item
if ($score -ge 70) { $color = "Red" }
elseif ($score -ge 40) { $color = "Orange" }
else { $color = "Lime" }
# --- Préparer affichage dates sans ?. ---
if ($dEffet -ne $null) { $effetStr = $dEffet.ToShortDateString() } else { $effetStr = "N/A" }
if ($dMDR -ne $null) { $mdrStr = $dMDR.ToShortDateString() } else { $mdrStr = "N/A" }
if ($dSusp -ne $null) { $suspStr = $dSusp.ToShortDateString() } else { $suspStr = "N/A" }
if ($dResil -ne $null) { $resilStr = $dResil.ToShortDateString() } else { $resilStr = "N/A" }
$txt.SelectionStart = $txt.TextLength
$txt.SelectionColor = [System.Drawing.Color]::$color
$txt.AppendText("📃 Police: $police | Client: $client`r`n")
$txt.SelectionColor = [System.Drawing.Color]::White
$txt.AppendText(" Statuts : $([string]::Join(", ", $statuts))`r`n")
$txt.AppendText(" Dates : Effet: $effetStr ; MDR: $mdrStr ; Suspension: $suspStr ; Résiliation: $resilStr`r`n")
$txt.AppendText(" Score risque : $score / 100`r`n")
$txt.AppendText("--------------------------------------------------------------`r`n`r`n")
}
# --- Bouton Fermer ---
$btnClose = New-Object System.Windows.Forms.Button
$btnClose.Text = "Fermer"
$btnClose.Font = New-Object System.Drawing.Font("Segoe UI",10,[System.Drawing.FontStyle]::Bold)
$btnClose.Size = New-Object System.Drawing.Size(120,35)
$btnX = $form.ClientSize.Width - 140
$btnY = $form.ClientSize.Height - 50
$btnClose.Location = New-Object System.Drawing.Point($btnX,$btnY)
$btnClose.Anchor = "Bottom,Right"
$btnClose.Add_Click({ $form.Close() })
$form.Controls.Add($btnClose)
$form.ShowDialog()
}
#Show-DetailedAudit -list $list
function Show-Dashboard {
param (
[System.Windows.Forms.ListView]$list
)
if ($list.Items.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("⚠️ Pas de données à afficher.", "Info", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
return
}
# --- Couleurs et styles pro ---
$bgColor = [System.Drawing.Color]::FromArgb(40, 44, 52) # fond gris foncé
$fgColorTitle = [System.Drawing.Color]::FromArgb(230, 230, 230) # texte clair
$colorGreen = [System.Drawing.Color]::FromArgb(88, 184, 84)
$colorOrange = [System.Drawing.Color]::FromArgb(255, 140, 0)
$colorRed = [System.Drawing.Color]::FromArgb(220, 53, 69)
$colorYellow = [System.Drawing.Color]::FromArgb(255, 193, 7)
$colorBlue = [System.Drawing.Color]::FromArgb(23, 162, 184)
$colorMagenta = [System.Drawing.Color]::FromArgb(155, 89, 182)
$colorLightGray = [System.Drawing.Color]::FromArgb(200, 200, 200)
$today = (Get-Date).Date
# --- Compteurs ---
$total = $list.Items.Count
$actifs=0; $resilies=0; $contentieux=0; $miseEnDemeure=0; $suspension=0
$delais = @(); $clients=@{}; $contratsParMois=@{}
# --- Fonction calcul score de risque ---
function Calculate-RiskScore {
param($item)
$score = 0
try {
$dEffet = [datetime]::ParseExact($item.SubItems[3].Text.Trim(),"dd/MM/yyyy",$null)
$dMDR = [datetime]::ParseExact($item.SubItems[4].Text.Trim(),"dd/MM/yyyy",$null)
$dSusp = [datetime]::ParseExact($item.SubItems[5].Text.Trim(),"dd/MM/yyyy",$null)
$dResil = [datetime]::ParseExact($item.SubItems[6].Text.Trim(),"dd/MM/yyyy",$null)
$daysToResil = ($dResil - $today).TotalDays
if ($daysToResil -le 0) { $score += 50 } else { $score += [math]::Max(0, 30 - $daysToResil) }
if ($dMDR -le $today) { $score += 20 }
if ($dSusp -le $today) { $score += 20 }
if ($score -gt 100) { $score = 100 }
}
catch { $score = 0 }
return [math]::Round($score,1)
}
# --- Parcours contrats ---
foreach ($item in $list.Items) {
try {
$dEffet = [datetime]::ParseExact($item.SubItems[3].Text.Trim(),"dd/MM/yyyy",$null)
$dCont = [datetime]::ParseExact($item.SubItems[4].Text.Trim(),"dd/MM/yyyy",$null)
$dSusp = [datetime]::ParseExact($item.SubItems[5].Text.Trim(),"dd/MM/yyyy",$null)
$dResil = [datetime]::ParseExact($item.SubItems[6].Text.Trim(),"dd/MM/yyyy",$null)
}
catch { continue }
$dContentieux = $dResil.AddDays(20)
$client = $item.SubItems[1].Text
if ($dResil -gt $today) { $actifs++ } else { $resilies++ }
if ($dContentieux -le $today) { $contentieux++ }
if ($dCont -le $today) { $miseEnDemeure++ }
if ($dSusp -le $today) { $suspension++ }
$delais += ($dResil - $dEffet).TotalDays
if (-not $clients.ContainsKey($client)) { $clients[$client] = @() }
$clients[$client] += $item.Text
$mois = $dEffet.ToString("MM/yyyy")
if (-not $contratsParMois.ContainsKey($mois)) { $contratsParMois[$mois] = 0 }
$contratsParMois[$mois]++
}
# --- Moyennes et ratios ---
$delaiMoy = if ($delais.Count) {[math]::Round(($delais|Measure-Object -Average).Average,1)} else {0}
$delaiMin = if ($delais.Count) {[math]::Round(($delais|Measure-Object -Minimum).Minimum,1)} else {0}
$delaiMax = if ($delais.Count) {[math]::Round(($delais|Measure-Object -Maximum).Maximum,1)} else {0}
$ratioActif = if ($total -gt 0) { [math]::Round(($actifs/$total)*100,1) } else { 0 }
$ratioResil = if ($total -gt 0) { [math]::Round(($resilies/$total)*100,1) } else { 0 }
$ratioContentieux = if ($total -gt 0) { [math]::Round(($contentieux/$total)*100,1) } else { 0 }
$ratioSuspension = if ($total -gt 0) { [math]::Round(($suspension/$total)*100,1) } else { 0 }
$ratioMiseDemeure = if ($total -gt 0) { [math]::Round(($miseEnDemeure/$total)*100,1) } else { 0 }
# --- Score risques clients ---
$clientsRisk = @{}
foreach ($item in $list.Items) {
$client = $item.SubItems[1].Text
$score = Calculate-RiskScore -item $item
if (-not $clientsRisk.ContainsKey($client)) { $clientsRisk[$client] = @() }
$clientsRisk[$client] += $score
}
$clientsRiskAverage = $clientsRisk.GetEnumerator() | ForEach-Object {
[PSCustomObject]@{
Client = $_.Key
AvgScore = if ($_.Value.Count) { [math]::Round(($_.Value | Measure-Object -Average).Average,1) } else {0}
}
}
$topClients = $clientsRiskAverage | Sort-Object AvgScore -Descending | Select-Object -First 5
# --- Alertes intelligentes ---
$contractsSoonContentieux = 0
foreach ($item in $list.Items) {
try {
$dResil = [datetime]::ParseExact($item.SubItems[6].Text.Trim(),"dd/MM/yyyy",$null)
$dContentieux = $dResil.AddDays(20)
$daysToContentieux = ($dContentieux - $today).TotalDays
if ($daysToContentieux -ge 0 -and $daysToContentieux -le 20) { $contractsSoonContentieux++ }
}
catch { }
}
$alertMessage = if ($contractsSoonContentieux -gt 0) {
"⚠️ Attention, $contractsSoonContentieux contrat(s) risquent d'entrer en contentieux dans les 20 prochains jours."
} else {
"✅ Aucun contrat ne semble entrer en contentieux prochainement."
}
# --- Création formulaire ---
$form = New-Object System.Windows.Forms.Form
$form.Text = "🛰 STAR DASHBOARD PRO - Professionnel"
$form.Size = New-Object System.Drawing.Size(1100,700)
$form.StartPosition = "CenterScreen"
$form.BackColor = $bgColor
$form.Opacity = 0.97
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedSingle
$form.MaximizeBox = $false
# --- Titre ---
$lblTitle = New-Object System.Windows.Forms.Label
$lblTitle.Text = "🛰 STAR DASHBOARD PRO"
$lblTitle.Font = New-Object System.Drawing.Font("Segoe UI Semibold",26,[System.Drawing.FontStyle]::Bold)
$lblTitle.ForeColor = $fgColorTitle
$lblTitle.AutoSize = $true
$lblTitle.Location = New-Object System.Drawing.Point(30,20)
$form.Controls.Add($lblTitle)
# --- Stats globales ---
$y = 80; $spacing = 36
$stats = @(
@{Text = "📦 Total contrats : $total"; Color = $fgColorTitle},
@{Text = "✅ Actifs : $actifs ($ratioActif%)"; Color = $colorGreen},
@{Text = "❌ Résiliés : $resilies ($ratioResil%)"; Color = $colorRed},
@{Text = "⚠️ Contentieux : $contentieux ($ratioContentieux%)"; Color = $colorOrange},
@{Text = "💌 Mise en demeure : $miseEnDemeure ($ratioMiseDemeure%)"; Color = $colorYellow},
@{Text = "⏸ Suspension : $suspension ($ratioSuspension%)"; Color = $colorBlue},
@{Text = "⏱ Délai moyen : $delaiMoy jours (min $delaiMin / max $delaiMax)"; Color = $colorLightGray},
@{Text = "👥 Clients distincts : $($clients.Keys.Count)"; Color = $colorMagenta}
)
foreach ($stat in $stats) {
$lbl = New-Object System.Windows.Forms.Label
$lbl.Text = $stat.Text
$lbl.ForeColor = $stat.Color
$lbl.Font = New-Object System.Drawing.Font("Segoe UI",14,[System.Drawing.FontStyle]::Bold)
$lbl.AutoSize = $true
$lbl.Location = New-Object System.Drawing.Point(40,$y)
$y += $spacing
$form.Controls.Add($lbl)
}
# --- Top 5 clients à risque ---
$lblTopClientsTitle = New-Object System.Windows.Forms.Label
$lblTopClientsTitle.Text = "🔥 Top 5 clients à risque"
$lblTopClientsTitle.ForeColor = $colorOrange
$lblTopClientsTitle.Font = New-Object System.Drawing.Font("Segoe UI Semibold",16,[System.Drawing.FontStyle]::Bold)
$lblTopClientsTitle.AutoSize = $true
$lblTopClientsTitle.Location = New-Object System.Drawing.Point(600, 80)
$form.Controls.Add($lblTopClientsTitle)
$yTop = 120
foreach ($clientRisk in $topClients) {
$lblClient = New-Object System.Windows.Forms.Label
$lblClient.Text = "$($clientRisk.Client) : Score risque moyen $($clientRisk.AvgScore)/100"
$lblClient.ForeColor = $fgColorTitle
$lblClient.Font = New-Object System.Drawing.Font("Segoe UI",12,[System.Drawing.FontStyle]::Regular)
$lblClient.AutoSize = $true
$lblClient.Location = New-Object System.Drawing.Point(600, $yTop)
$form.Controls.Add($lblClient)
$yTop += 30
}
# --- Alertes ---
$lblAlert = New-Object System.Windows.Forms.Label
$lblAlert.Text = $alertMessage
$lblAlert.ForeColor = $colorRed
$lblAlert.Font = New-Object System.Drawing.Font("Segoe UI Semibold",14,[System.Drawing.FontStyle]::Bold)
$lblAlert.AutoSize = $true
$lblAlert.Location = New-Object System.Drawing.Point(40, ($y + 20))
$form.Controls.Add($lblAlert)
# --- TextBox détails clients ---
$txtDetails = New-Object System.Windows.Forms.TextBox
$txtDetails.Multiline = $true
$txtDetails.ScrollBars = "Vertical"
$txtDetails.ReadOnly = $true
$txtDetails.Font = New-Object System.Drawing.Font("Consolas",11)
$txtDetails.BackColor = [System.Drawing.Color]::FromArgb(25,25,40)
$txtDetails.ForeColor = $fgColorTitle
$txtDetails.Size = New-Object System.Drawing.Size(1000, 350)
$txtDetails.Location = New-Object System.Drawing.Point(40, ($y + 60))
$form.Controls.Add($txtDetails)
# Remplissage textbox détails
$txtDetails.AppendText("🚀 === CONTRATS PAR CLIENT ===`r`n")
foreach ($c in $clients.Keys) {
$txtDetails.AppendText("👤 $c | Contrats : $($clients[$c].Count)`r`n")
$txtDetails.AppendText(" Polices : $($clients[$c] -join ', ')`r`n`r`n")
}
$txtDetails.AppendText("📊 === CONTRATS PAR MOIS ===`r`n")
foreach ($m in $contratsParMois.Keys | Sort-Object) {
$txtDetails.AppendText("📅 $m : $($contratsParMois[$m]) contrats`r`n")
}
# --- Bouton Détails Client ---
$btnDetail = New-Object System.Windows.Forms.Button
$btnDetail.Text = "🕵️ Détails client"
$btnDetail.Font = New-Object System.Drawing.Font("Segoe UI",12,[System.Drawing.FontStyle]::Bold)
$btnDetail.Size = New-Object System.Drawing.Size(200,40)
$btnDetail.Location = New-Object System.Drawing.Point(800, ($y + 20))
$btnDetail.BackColor = [System.Drawing.Color]::FromArgb(50,50,70)
$btnDetail.ForeColor = [System.Drawing.Color]::White
$btnDetail.FlatStyle = "Flat"
$btnDetail.FlatAppearance.BorderSize = 0
$btnDetail.Add_Click({
if ($list.Items.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("⚠️ Pas de contrats à afficher.", "Info")
} else {
Show-DetailedAudit -list $list
}
})
$form.Controls.Add($btnDetail)
# --- Affichage dashboard ---
$form.ShowDialog()
}
$btnDashboard = New-Object System.Windows.Forms.Button
$btnDashboard.Text = "Dashboard"
$btnDashboard.Location = New-Object System.Drawing.Point(330, 120)
$btnDashboard.Size = New-Object System.Drawing.Size(250,30)
$btnDashboard.BackColor = $colorButton
$btnDashboard.ForeColor = [System.Drawing.Color]::White
$grp.Controls.Add($btnDashboard)
$btnDashboard.Add_Click({
Show-Dashboard $list
})