From 236ae4ab8cc443e2812e830a858e76dab05ceb41 Mon Sep 17 00:00:00 2001 From: jfgiraud Date: Thu, 2 Jul 2026 14:15:39 +0000 Subject: [PATCH] menu csv full operationnel --- css/csv.css | 104 +++++++++++++++++++++++++++++++++++ js/csv.js | 52 ++++++++++++++++++ php/csv.php | 125 +++++++++++++++++++++++++++++++++++++++++++ php/db.php | 43 +++++++++++++++ php/download_csv.php | 27 ++++++++++ php/update_csv.php | 43 +++++++++++++++ 6 files changed, 394 insertions(+) create mode 100644 css/csv.css create mode 100644 js/csv.js create mode 100644 php/csv.php create mode 100644 php/download_csv.php create mode 100644 php/update_csv.php diff --git a/css/csv.css b/css/csv.css new file mode 100644 index 0000000..e53aa69 --- /dev/null +++ b/css/csv.css @@ -0,0 +1,104 @@ +/* Styles pour la page de synchronisation CSV */ + +body.csv-page { + background: #08101f; + color: #e7eefc; + min-height: 100vh; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} + +.csv-shell { + min-height: calc(100vh - 120px); + display: flex; + align-items: center; + justify-content: center; + padding: 30px 20px; +} + +.csv-card { + width: min(780px, 100%); + background: rgba(12, 18, 32, 0.96); + border: 1px solid rgba(113, 153, 255, 0.16); + border-radius: 22px; + box-shadow: 0 30px 80px rgba(0, 0, 0, 0.28); + padding: 32px; + display: grid; + gap: 28px; +} + +.csv-content { + display: grid; + gap: 16px; +} + +.csv-card h1 { + margin: 0; + font-size: clamp(1.8rem, 2.5vw, 2.8rem); + letter-spacing: -0.04em; +} + +.csv-status { + margin: 0; + font-size: 1rem; + line-height: 1.6; + color: #d3e6ff; +} + +.status-ok { + color: #c5f0c7; +} + +.status-warning { + color: #ffdab3; +} + +.csv-actions { + display: flex; + justify-content: flex-end; + gap: 12px; + flex-wrap: wrap; +} + +.btn-action { + border: 1px solid rgba(102, 138, 255, 0.24); + border-radius: 999px; + padding: 12px 18px; + font-weight: 700; + font-size: 0.95rem; + cursor: pointer; + transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease; + min-width: 110px; +} + +.btn-action:hover { + transform: translateY(-1px); +} + +.btn-primary { + background: linear-gradient(135deg, rgba(72, 124, 255, 0.16), rgba(34, 46, 88, 0.98)); + color: #f7fbff; + border-color: rgba(90, 142, 255, 0.35); +} + +.btn-secondary { + background: rgba(24, 32, 58, 0.96); + color: #d7e6ff; + border-color: rgba(118, 143, 190, 0.2); +} + +.csv-feedback { + min-height: 24px; + color: #f7b3b3; + font-size: 0.95rem; +} + +@media (max-width: 640px) { + .csv-card { + padding: 24px; + } + + .csv-actions { + justify-content: center; + } +} diff --git a/js/csv.js b/js/csv.js new file mode 100644 index 0000000..a58ef70 --- /dev/null +++ b/js/csv.js @@ -0,0 +1,52 @@ +document.addEventListener('DOMContentLoaded', () => { + const updateButton = document.getElementById('csv-update-yes'); + const cancelButton = document.getElementById('csv-update-no'); + const feedbackElement = document.getElementById('csv-feedback'); + + if (!updateButton || !cancelButton || !feedbackElement) { + return; + } + + const setFeedback = (message, isError = false) => { + feedbackElement.textContent = message; + feedbackElement.style.color = isError ? '#ffb3b3' : '#b8f2b8'; + }; + + updateButton.addEventListener('click', async () => { + updateButton.disabled = true; + cancelButton.disabled = true; + setFeedback('Mise à jour en cours, veuillez patienter...'); + + try { + const response = await fetch('/php/update_csv.php', { + method: 'POST', + headers: { + 'Accept': 'application/json', + }, + }); + + const result = await response.json(); + + if (!response.ok || !result.success) { + const message = result.message || 'Échec de la mise à jour CSV.'; + setFeedback(message, true); + updateButton.disabled = false; + cancelButton.disabled = false; + return; + } + + setFeedback(result.message || 'CSV mis à jour avec succès.', false); + setTimeout(() => { + window.location.href = '/php/csv.php'; + }, 1200); + } catch (error) { + setFeedback('Erreur réseau lors de la mise à jour CSV.', true); + updateButton.disabled = false; + cancelButton.disabled = false; + } + }); + + cancelButton.addEventListener('click', () => { + window.location.href = '/php/menu.php'; + }); +}); diff --git a/php/csv.php b/php/csv.php new file mode 100644 index 0000000..0652ed6 --- /dev/null +++ b/php/csv.php @@ -0,0 +1,125 @@ + trim((string) ($row[1] ?? '')), + 'yahoo_code' => trim((string) ($row[2] ?? '')), + 'company_name' => trim((string) ($row[3] ?? '')), + ]; + } + + fclose($handle); + } +} + +$actions = $actionsManager->listActions(); +$differences = []; + +foreach ($actions as $action) { + $isin = trim((string) ($action['isin'] ?? '')); + if ($isin === '') { + continue; + } + + $expected = [ + 'ticker' => trim((string) ($action['ticker'] ?? '')), + 'yahoo_code' => trim((string) ($action['yahoo_code'] ?? '')), + 'company_name' => trim((string) ($action['company_name'] ?? '')), + ]; + + if (!isset($csvRecords[$isin])) { + $differences[] = ['isin' => $isin, 'reason' => 'absent']; + continue; + } + + $csvRow = $csvRecords[$isin]; + foreach ($expected as $field => $value) { + if ($csvRow[$field] !== $value) { + $differences[] = ['isin' => $isin, 'reason' => $field]; + break; + } + } +} + +$csvReady = $csvExists && empty($differences); + +if ($csvReadError !== null) { + $statusMessage = 'Impossible de vérifier le fichier actions.csv : ' . $csvReadError; +} elseif ($csvReady) { + $statusMessage = 'Le fichier actions.csv est à jour.'; +} else { + $statusMessage = 'Le fichier actions.csv peut être mis à jour.'; + if ($csvExists) { + $statusMessage .= ' ' . count($differences) . ' différence(s) détectée(s).'; + } +} + +require_once __DIR__ . '/header.php'; +?> +
+
+
+

Synchronisation CSV

+

+
+ +
+ + Retour au menu + + + + + + + Télécharger CSV + +
+ +
+
+
+ + +fetchAll(); } + /** + * Exporte toutes les actions vers un fichier CSV. + * + * @param string $filePath + * @return bool + */ + public function exportActionsToCsv(string $filePath): bool + { + $actions = $this->listActions(); + + $directory = dirname($filePath); + if (!is_dir($directory) && !mkdir($directory, 0755, true) && !is_dir($directory)) { + return false; + } + + $handle = fopen($filePath, 'w'); + if ($handle === false) { + return false; + } + + $header = ['isin', 'ticker', 'yahoo_code', 'company_name', 'updated_at']; + if (fputcsv($handle, $header) === false) { + fclose($handle); + return false; + } + + foreach ($actions as $action) { + if (fputcsv($handle, [ + $action['isin'], + $action['ticker'], + $action['yahoo_code'], + $action['company_name'], + $action['updated_at'], + ]) === false) { + fclose($handle); + return false; + } + } + + fclose($handle); + return true; + } + /** * Retourne une action par son ISIN. * diff --git a/php/download_csv.php b/php/download_csv.php new file mode 100644 index 0000000..d1d57d3 --- /dev/null +++ b/php/download_csv.php @@ -0,0 +1,27 @@ + false, 'message' => 'Méthode non autorisée.']); + exit; +} + +if (empty($_SESSION['user_id'])) { + http_response_code(401); + echo json_encode(['success' => false, 'message' => 'Authentification requise.']); + exit; +} + +$database = new Database(); +$actionsManager = new ActionsManager($database); +$csvFilePath = __DIR__ . '/../assets/csv/actions.csv'; + +try { + $success = $actionsManager->exportActionsToCsv($csvFilePath); + if (!$success) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Impossible de générer le fichier CSV.']); + exit; + } + + echo json_encode(['success' => true, 'message' => 'Fichier CSV mis à jour avec succès.']); + exit; +} catch (Throwable $exception) { + http_response_code(500); + echo json_encode(['success' => false, 'message' => 'Erreur interne : ' . $exception->getMessage()]); + exit; +}