Function Show-Journal {
param(
[string]$police,
[string]$client,
[string]$userName
)
# --- PERSISTANCE INTERNE ---
function Internal-GetJournal {
if (Test-Path $script:journalFile) {
try { return Get-Content $script:journalFile -Encoding utf8 | ConvertFrom-Json }
catch { return New-Object PSObject }
}
return New-Object PSObject
}
function Internal-SaveJournal {
param($data)
try { $data | ConvertTo-Json -Depth 10 | Out-File $script:journalFile -Encoding utf8 }
catch { [System.Windows.Forms.MessageBox]::Show("Erreur sauvegarde JSON") }
}
# Initialisation
$journalData = Internal-GetJournal
if ($null -eq $journalData) { $journalData = New-Object PSObject }
$script:editIndex = -1
$startTime = Get-Date
$script:actionDone = $false
# --- DARK MODE ADAPTATIF 2026 ---
$hour = (Get-Date).Hour
$isNight = ($hour -lt 7 -or $hour -gt 19)
$bgMain = if ($isNight) { [System.Drawing.Color]::FromArgb(5, 5, 10) } else { [System.Drawing.Color]::FromArgb(10, 12, 18) }
$bgHeader = if ($isNight) { [System.Drawing.Color]::FromArgb(15, 15, 30) } else { [System.Drawing.Color]::FromArgb(20, 25, 40) }
# --- FENÊTRE ---
$jouForm = New-Object System.Windows.Forms.Form
$jouForm.Text = "TERMINAL AUDIT v3.0 (2026) - $client"
$jouForm.Size = New-Object System.Drawing.Size(700, 820)
$jouForm.BackColor = $bgMain
$jouForm.StartPosition = "CenterParent"
$jouForm.FormBorderStyle = "FixedDialog"
# --- HEADER HUD ---
$panelHeader = New-Object System.Windows.Forms.Panel
$panelHeader.Size = New-Object System.Drawing.Size(700, 110)
$panelHeader.BackColor = $bgHeader
$jouForm.Controls.Add($panelHeader)
$lblTitle = New-Object System.Windows.Forms.Label
$lblTitle.Text = "AUDIT RECOUVREMENT | $police"
$lblTitle.SetBounds(20, 15, 500, 25)
$lblTitle.ForeColor = [System.Drawing.Color]::Cyan
$lblTitle.Font = New-Object System.Drawing.Font("Segoe UI", 12, [System.Drawing.FontStyle]::Bold)
$panelHeader.Controls.Add($lblTitle)
# --- ALERTE DOSSIER FROID ---
[array]$notesInit = if ($journalData.PSObject.Properties[$police]) { @($journalData."$police") } else { @() }
if ($notesInit.Count -gt 0) {
if ($notesInit[0] -match "\[(\d{2}/\d{2})") {
try {
$lastDate = [DateTime]::ParseExact($matches[1] + "/$((Get-Date).Year)", "dd/MM/yyyy", $null)
if (((Get-Date) - $lastDate).Days -gt 15) {
$lblTitle.ForeColor = [System.Drawing.Color]::OrangeRed
$lblTitle.Text += " [ALERTE: DOSSIER FROID]"
}
} catch {}
}
}
$lblStats = New-Object System.Windows.Forms.Label
$lblStats.SetBounds(22, 45, 600, 20)
$lblStats.ForeColor = [System.Drawing.Color]::FromArgb(150, 150, 180)
$lblStats.Font = New-Object System.Drawing.Font("Consolas", 9, [System.Drawing.FontStyle]::Regular)
$panelHeader.Controls.Add($lblStats)
$txtSearch = New-Object System.Windows.Forms.TextBox
$txtSearch.SetBounds(20, 75, 645, 25)
$txtSearch.BackColor = [System.Drawing.Color]::FromArgb(30, 35, 50)
$txtSearch.ForeColor = [System.Drawing.Color]::White
$txtSearch.BorderStyle = "FixedSingle"
$txtSearch.Text = "🔍 Rechercher une action..."
$panelHeader.Controls.Add($txtSearch)
$txtSearch.Add_GotFocus({
if ($this.Text -like "*Rechercher*") {
$this.Text = ""
$this.ForeColor = [System.Drawing.Color]::White
}
})
$txtSearch.Add_LostFocus({
if ($this.Text -eq "") {
$this.Text = "🔍 Rechercher une action..."
$this.ForeColor = [Drawing.Color]::Gray
}
})
# --- HISTORIQUE ---
$logDisplay = New-Object System.Windows.Forms.RichTextBox
$logDisplay.SetBounds(20, 130, 645, 250)
$logDisplay.BackColor = [System.Drawing.Color]::FromArgb(15, 18, 25)
$logDisplay.ForeColor = [System.Drawing.Color]::White
$logDisplay.Font = New-Object System.Drawing.Font("Consolas", 11, [System.Drawing.FontStyle]::Regular)
$logDisplay.ReadOnly = $true
$logDisplay.BorderStyle = "None"
$jouForm.Controls.Add($logDisplay)
$logDisplay.Add_MouseDown({
if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Right) {
$charIndex = $this.GetCharIndexFromPosition($_.Location)
$this.SelectionStart = $charIndex; $this.SelectionLength = 0
}
})
# --- MISE À JOUR AFFICHAGE ---
function Update-JournalDisplay ($filter = "") {
$logDisplay.ReadOnly = $false; $logDisplay.Clear()
[array]$notes = if ($journalData.PSObject.Properties[$police]) { @($journalData."$police" -match '.' ) } else { @() }
$lastDateText = if($notes.Count -gt 0){ $notes[0].Substring(1,11) } else { "AUCUN" }
$lblStats.Text = "ACTIONS: $($notes.Count) | DERNIER CONTACT: $lastDateText"
foreach ($n in $notes) {
if ($filter -eq "" -or $n -like "*$filter*") {
if ($n -match "\[(.*?)\] \[(.*?)\] ⚡ (.*)") {
$logDisplay.SelectionColor = [System.Drawing.Color]::DarkCyan;
$logDisplay.AppendText("[$($matches[1])] ")
$logDisplay.SelectionColor = [System.Drawing.Color]::LimeGreen;
$logDisplay.AppendText("($($matches[2])) ")
$logDisplay.SelectionColor = [System.Drawing.Color]::White;
$logDisplay.AppendText("» $($matches[3])`n")
$logDisplay.SelectionColor = [System.Drawing.Color]::FromArgb(40,45,60);
$logDisplay.AppendText("-" * 80 + "`n")
}
}
}
$logDisplay.ReadOnly = $true
}
# --- CALCULATEUR DE PROMESSE
function Show-PromiseCalc {
$pForm = New-Object System.Windows.Forms.Form
$pForm.Size = New-Object System.Drawing.Size(350, 180)
$pForm.Text = "CALCULATEUR DE LA PROMESSE"
$pForm.BackColor = [System.Drawing.Color]::FromArgb(20,20,30)
$pForm.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterParent
$pForm.FormBorderStyle = "FixedDialog"
$lblP = New-Object System.Windows.Forms.Label
$lblP.Text = "Date du règlement (JJ/MM) :"
$lblP.SetBounds(15,15,300,25)
$lblP.ForeColor = [System.Drawing.Color]::White
$lblP.Font = New-Object System.Drawing.Font("Segoe UI", 11)
$txtP = New-Object System.Windows.Forms.TextBox
$txtP.SetBounds(15,45,150,30)
$txtP.Font = New-Object System.Drawing.Font("Segoe UI", 12)
$btnC = New-Object System.Windows.Forms.Button
$btnC.Text = "CALCULER ET AJOUTER"
$btnC.SetBounds(15,85,200,35)
$btnC.ForeColor = [System.Drawing.Color]::Cyan
$btnC.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$btnC.Cursor = [System.Windows.Forms.Cursors]::Hand
$btnC.Add_Click({
# Regex pour vérifier le format JJ/MM
if ($txtP.Text -match "^(\d{2})/(\d{2})$") {
try {
# Récupération dynamique de l'année actuelle
$currentYear = (Get-Date).Year
# Tentative de création de la date avec l'année en cours
$targetDate = [DateTime]::ParseExact($txtP.Text + "/$currentYear", "dd/MM/yyyy", $null)
# Sécurité : Si la date saisie est déjà passée (ex: on est en Nov, on tape 01/02)
# On bascule automatiquement sur l'année suivante
if ($targetDate.Date -lt (Get-Date).Date) {
$targetDate = $targetDate.AddYears(1)
}
# Calcul de la différence réelle en jours
$today = (Get-Date).Date
$diff = ($targetDate - $today).Days + 1
# Formattage propre pour l'affichage
$finalDateStr = $targetDate.ToString("dd/MM/yyyy")
# Ajout direct dans votre champ de note principal
$txtNote.SelectionFont = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold)
$txtNote.AppendText(" | Échéance : $diff jours (le $finalDateStr)")
$pForm.Close()
} catch {
[System.Windows.Forms.MessageBox]::Show("Erreur : La date '$($txtP.Text)' n'est pas valide.", "Erreur de saisie")
}
} else {
[System.Windows.Forms.MessageBox]::Show("Merci de respecter le format JJ/MM (ex: 12/05)", "Format incorrect")
}
})
$pForm.Controls.AddRange(@($lblP, $txtP, $btnC))
$pForm.ShowDialog()
}
# --- RACCOURCIS ---
$flow = New-Object System.Windows.Forms.FlowLayoutPanel; $flow.SetBounds(20, 395, 645, 65)
$jouForm.Controls.Add($flow)
$labels = @("SMS", "WHATSAPP", "EMAIL", "TELEPHONE", "PROMESSE", "ABSENCE", "INJOIGNABLE", "NRP", "FAUX NUMERO", "MEMOIRE")
foreach ($l in $labels) {
$btnA = New-Object System.Windows.Forms.Button;
$btnA.Text = $l;
$btnA.FlatStyle = "Flat";
$btnA.ForeColor = [System.Drawing.Color]::Cyan
$btnA.Font = New-Object System.Drawing.Font("Segoe UI", 7, [System.Drawing.FontStyle]::Bold)
$btnA.Size = New-Object System.Drawing.Size(100, 25)
$btnA.Add_Click({
$txtNote.Clear()
$txtNote.SelectionFont = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold)
$txtNote.SelectionColor = [System.Drawing.Color]::Cyan;
$txtNote.AppendText("[$($this.Text)]")
$txtNote.SelectionFont = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Regular)
$txtNote.SelectionColor = [System.Drawing.Color]::White;
$txtNote.AppendText(" ")
if ($this.Text -eq "PROMESSE") { Show-PromiseCalc }
$txtNote.Focus()
})
$flow.Controls.Add($btnA)
}
# --- SAISIE ---
$saisieContainer = New-Object System.Windows.Forms.Panel;
$saisieContainer.SetBounds(20, 470, 645, 120);
$saisieContainer.BackColor = [System.Drawing.Color]::FromArgb(25, 30, 45);
$saisieContainer.BorderStyle = "FixedSingle"
$jouForm.Controls.Add($saisieContainer)
$txtNote = New-Object System.Windows.Forms.RichTextBox;
$txtNote.SetBounds(8, 8, 625, 100);
$txtNote.BackColor = [System.Drawing.Color]::FromArgb(25, 30, 45);
$txtNote.ForeColor = [System.Drawing.Color]::White;
$txtNote.BorderStyle = "None";
$txtNote.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Regular)
$saisieContainer.Controls.Add($txtNote)
# --- ACTIONS ---
$btnSave = New-Object System.Windows.Forms.Button;
$btnSave.Text = "VALIDER ET ARCHIVER";
$btnSave.SetBounds(20, 600, 645, 50);
$btnSave.FlatStyle = "Flat";
$btnSave.BackColor = [System.Drawing.Color]::FromArgb(0, 120, 215);
$btnSave.ForeColor = [System.Drawing.Color]::White
$btnSave.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold)
$btnSave.Add_Click({
if ($txtNote.Text.Trim() -eq "") { return }
$entry = "[$(Get-Date -Format 'dd/MM HH:mm')] [$userName] ⚡ $($txtNote.Text.Replace('"',"'"))"
[array]$notes = if ($journalData.PSObject.Properties[$police]) { @($journalData."$police") } else { @() }
if ($script:editIndex -ge 0) { $notes[$script:editIndex] = $entry; $script:editIndex = -1 } else {
$notes = @($entry) + $notes }
$btnSave.BackColor = [System.Drawing.Color]::FromArgb(0, 120, 215)
$journalData | Add-Member -MemberType NoteProperty -Name $police -Value $notes -Force
Internal-SaveJournal -data $journalData;
$txtNote.Clear();
Update-JournalDisplay;
$script:actionDone = $true
})
$jouForm.Controls.Add($btnSave)
# --- AUTO-JOURNALISATION ---
$jouForm.Add_FormClosing({
if (-not $script:actionDone -and ((Get-Date) - $startTime).TotalMinutes -gt 3) {
$autoEntry = "[$(Get-Date -Format 'dd/MM HH:mm')] [$userName] ⚡ [SYSTEME] Consultation dossier sans action (>3min)"
[array]$n = if ($journalData.PSObject.Properties[$police]) { @($journalData."$police") } else { @() }
$journalData | Add-Member -MemberType NoteProperty -Name $police -Value (@($autoEntry) + $n) -Force
Internal-SaveJournal -data $journalData
}
})
$ctxMenu = New-Object System.Windows.Forms.ContextMenuStrip
$ctxMenu.Items.Add("📝 Modifier", $null, {
$line = $logDisplay.GetLineFromCharIndex($logDisplay.SelectionStart)
$idx = [Math]::Max(0, [Math]::Floor($line / 3))
[array]$n = @($journalData."$police")
if ($idx -lt $n.Count) { $txtNote.Text = ($n[$idx] -split " ⚡ ")[1];
$script:editIndex = $idx;
$btnSave.BackColor = [System.Drawing.Color]::DarkOrange; $txtNote.Focus() }
})
# bugs de suppression cause CR/LF indexation de lignes à fixer
$ctxMenu.Items.Add("❌ Supprimer", $null, {
$line = $logDisplay.GetLineFromCharIndex($logDisplay.SelectionStart)
$idx = [Math]::Max(0, [Math]::Floor($line / 3))
[array]$n = @($journalData."$police")
$confirm = [System.Windows.Forms.MessageBox]::Show("Supprimer cette note ?`n`n$($n[$idx])", "Audit", 4)
if ($idx -lt $n.Count -and $confirm -eq 6) {
$new = for($i=0; $i -lt $n.Count; $i++){ if($i -ne $idx){ $n[$i] } }
$journalData."$police" = @($new);
Internal-SaveJournal -data $journalData;
Update-JournalDisplay
}
})
$logDisplay.ContextMenuStrip = $ctxMenu
$txtSearch.Add_TextChanged({
if ($this.Text -like "*Rechercher*") { Update-JournalDisplay } else { Update-JournalDisplay $this.Text }
})
Update-JournalDisplay
$jouForm.ShowDialog()
}
$btnJournal.Add_Click({
# 1. Vérification : une ligne est-elle sélectionnée dans la liste ?
if ($list.SelectedItems.Count -gt 0) {
# 2. Récupération de l'élément sélectionné
$selectedItem = $list.SelectedItems[0]
# 3. Extraction des données (Ajustez les index selon vos colonnes)
[string]$selectedPolice = $selectedItem.Text # Colonne 1 (Souvent la Police)
[string]$selectedClient = $selectedItem.SubItems[1].Text # Colonne 2 (Souvent le Nom Client)
# 4. Récupération de l'utilisateur (Priorité à votre variable script, sinon Windows)
$currentUser = if ($script:currentUser) { $script:currentUser } else { $env:USERNAME }
# 5. Appel de la fonction Show-Journal avec les paramètres
Show-Journal -police $selectedPolice -client $selectedClient -userName $currentUser
}
else {
# Message d'alerte si rien n'est sélectionné
[System.Windows.Forms.MessageBox]::Show("Veuillez sélectionner un dossier dans la liste pour ouvrir le journal d'actions.", "Sélection Requise", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
}
})
#############################################
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
}
# --- Chargement des assemblages ---
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# --- Palette de couleurs Modern Dark ---
$theme = @{
Background = [System.Drawing.Color]::FromArgb(15, 17, 26)
CardBg = [System.Drawing.Color]::FromArgb(25, 28, 41)
Accent = [System.Drawing.Color]::FromArgb(0, 120, 215)
TextMain = [System.Drawing.Color]::FromArgb(240, 240, 240)
TextMuted = [System.Drawing.Color]::FromArgb(140, 145, 160)
Success = [System.Drawing.Color]::FromArgb(40, 199, 111)
Warning = [System.Drawing.Color]::FromArgb(255, 159, 67)
Danger = [System.Drawing.Color]::FromArgb(234, 84, 85)
Info = [System.Drawing.Color]::FromArgb(0, 207, 232)
}
$today = (Get-Date).Date
# --- Calculs des données ---
$total = $list.Items.Count
$actifs = 0; $resilies = 0; $contentieux = 0; $mdr = 0
$riskScores = @()
foreach ($item in $list.Items) {
try {
$dEffet = [datetime]::ParseExact($item.SubItems[3].Text.Trim(),"dd/MM/yyyy",$null)
$dResil = [datetime]::ParseExact($item.SubItems[6].Text.Trim(),"dd/MM/yyyy",$null)
$dMDR = [datetime]::ParseExact($item.SubItems[4].Text.Trim(),"dd/MM/yyyy",$null)
if ($dResil -gt $today) { $actifs++ } else { $resilies++ }
if ($dResil.AddDays(20) -le $today) { $contentieux++ }
if ($dMDR -le $today) { $mdr++ }
# Score de risque simplifié pour l'affichage
$risk = if ($dResil -le $today) { 100 } else { [math]::Max(0, 100 - ($dResil - $today).TotalDays) }
$riskScores += [math]::Min(100, $risk)
} catch {}
}
$avgRisk = if ($riskScores.Count) { ($riskScores | Measure-Object -Average).Average } else { 0 }
# --- Formulaire Principal ---
$form = New-Object System.Windows.Forms.Form
$form.Text = "Analytics Dashboard 2026"
$form.Size = New-Object System.Drawing.Size(1000, 650)
$form.BackColor = $theme.Background
$form.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$form.StartPosition = "CenterScreen"
$form.MaximizeBox = $false
$form.FormBorderStyle = "FixedSingle"
# --- Header ---
$header = New-Object System.Windows.Forms.Label
$header.Text = "Vue d'ensemble du Portefeuille"
$header.Font = New-Object System.Drawing.Font("Segoe UI Semilight", 22)
$header.ForeColor = $theme.TextMain
$header.AutoSize = $true
$header.Location = "30, 25"
$form.Controls.Add($header)
$subHeader = New-Object System.Windows.Forms.Label
$subHeader.Text = "Données synchronisées en temps réel • $total contrats analysés"
$subHeader.ForeColor = $theme.TextMuted
$subHeader.Location = "34, 70"
$subHeader.AutoSize = $true
$form.Controls.Add($subHeader)
# --- Fonction pour créer des cartes modernes ---
function New-StatCard {
param($title, $value, $posX, $posY, $color)
$p = New-Object System.Windows.Forms.Panel
$p.Size = "210, 120"
$p.Location = "$posX, $posY"
$p.BackColor = $theme.CardBg
$lVal = New-Object System.Windows.Forms.Label
$lVal.Text = $value
$lVal.Font = New-Object System.Drawing.Font("Segoe UI Bold", 20)
$lVal.ForeColor = $color
$lVal.Location = "15, 20"
$lVal.AutoSize = $true
$lTit = New-Object System.Windows.Forms.Label
$lTit.Text = $title
$lTit.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$lTit.ForeColor = $theme.TextMuted
$lTit.Location = "18, 65"
$lTit.AutoSize = $true
# Petite barre de décoration au bas
$line = New-Object System.Windows.Forms.Panel
$line.Size = "210, 4"
$line.Dock = "Bottom"
$line.BackColor = $color
$p.Controls.AddRange(@($lVal, $lTit, $line))
return $p
}
# --- Ajout des cartes ---
$form.Controls.Add((New-StatCard "ACTIFS" $actifs 40 120 $theme.Success))
$form.Controls.Add((New-StatCard "RÉSILIÉS" $resilies 275 120 $theme.Danger))
$form.Controls.Add((New-StatCard "CONTENTIEUX" $contentieux 510 120 $theme.Warning))
$form.Controls.Add((New-StatCard "MISES EN DEMEURE" $mdr 745 120 $theme.Info))
# --- Zone Risque Global (Visualisation) ---
$riskPanel = New-Object System.Windows.Forms.Panel
$riskPanel.Size = "915, 200"
$riskPanel.Location = "40, 270"
$riskPanel.BackColor = $theme.CardBg
$riskTitle = New-Object System.Windows.Forms.Label
$riskTitle.Text = "NIVEAU DE RISQUE GLOBAL DU PORTEFEUILLE"
$riskTitle.ForeColor = $theme.TextMain
$riskTitle.Location = "20, 20"
$riskTitle.AutoSize = $true
$riskPanel.Controls.Add($riskTitle)
# Barre de progression personnalisée
$progBg = New-Object System.Windows.Forms.Panel
$progBg.Size = "870, 15"
$progBg.Location = "20, 60"
$progBg.BackColor = [System.Drawing.Color]::FromArgb(45, 50, 70)
$progFill = New-Object System.Windows.Forms.Panel
$progFill.Size = "$([int](8.7 * $avgRisk)), 15"
$progFill.BackColor = if ($avgRisk -gt 50) { $theme.Danger } else { $theme.Success }
$progBg.Controls.Add($progFill)
$riskPanel.Controls.Add($progBg)
$riskDesc = New-Object System.Windows.Forms.Label
$riskDesc.Text = "Le score moyen de dangerosité est de $([math]::Round($avgRisk,1))%. Une valeur élevée indique une fin de contrat imminente pour la majorité des dossiers."
$riskDesc.ForeColor = $theme.TextMuted
$riskDesc.Location = "20, 90"
$riskDesc.Size = "850, 50"
$riskPanel.Controls.Add($riskDesc)
$form.Controls.Add($riskPanel)
# --- Bouton Action ---
$btnExit = New-Object System.Windows.Forms.Button
$btnExit.Text = "Fermer l'analyse"
$btnExit.Size = "200, 45"
$btnExit.Location = "755, 530"
$btnExit.FlatStyle = "Flat"
$btnExit.FlatAppearance.BorderSize = 0
$btnExit.BackColor = $theme.Accent
$btnExit.ForeColor = "White"
$btnExit.Cursor = [System.Windows.Forms.Cursors]::Hand
$btnExit.Add_Click({ $form.Close() })
$form.Controls.Add($btnExit)
$form.ShowDialog()
}
$btnDashboard.Add_Click({
Show-Dashboard $list
})