menu csv full operationnel
This commit is contained in:
+104
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
});
|
||||
});
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/session.php';
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
use App\Database\Database;
|
||||
use App\Database\ActionsManager;
|
||||
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
header('Location: ../index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pageStylesheets = ['/css/csv.css'];
|
||||
$pageScripts = ['/js/csv.js'];
|
||||
$bodyClass = 'csv-page';
|
||||
|
||||
$database = new Database();
|
||||
$actionsManager = new ActionsManager($database);
|
||||
|
||||
$csvFilePath = __DIR__ . '/../assets/csv/actions.csv';
|
||||
$csvRecords = [];
|
||||
$csvReadError = null;
|
||||
$csvExists = is_readable($csvFilePath);
|
||||
|
||||
if ($csvExists) {
|
||||
$handle = fopen($csvFilePath, 'r');
|
||||
if ($handle === false) {
|
||||
$csvExists = false;
|
||||
$csvReadError = 'Impossible de lire le fichier CSV.';
|
||||
} else {
|
||||
$header = fgetcsv($handle);
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
if (count($row) < 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isin = trim((string) ($row[0] ?? ''));
|
||||
if ($isin === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$csvRecords[$isin] = [
|
||||
'ticker' => 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';
|
||||
?>
|
||||
<section class="csv-shell">
|
||||
<div class="csv-card">
|
||||
<div class="csv-content">
|
||||
<h1>Synchronisation CSV</h1>
|
||||
<p class="csv-status <?= $csvReady ? 'status-ok' : 'status-warning' ?>"><?= htmlspecialchars($statusMessage, ENT_QUOTES, 'UTF-8') ?></p>
|
||||
</div>
|
||||
|
||||
<div class="csv-actions">
|
||||
<?php if ($csvReady): ?>
|
||||
<a href="/php/menu.php" class="btn-action btn-primary">Retour au menu</a>
|
||||
<?php else: ?>
|
||||
<button id="csv-update-yes" type="button" class="btn-action btn-primary">Mettre à jour</button>
|
||||
<button id="csv-update-no" type="button" class="btn-action btn-secondary">Retour au menu</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($csvExists): ?>
|
||||
<a id="csv-download-link" href="/php/download_csv.php" class="btn-action btn-secondary">Télécharger CSV</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div id="csv-feedback" class="csv-feedback" role="status" aria-live="polite"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script src="/js/csv.js"></script>
|
||||
<?php require_once __DIR__ . '/footer.php';
|
||||
+43
@@ -395,6 +395,49 @@ class ActionsManager
|
||||
return $statement->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.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/session.php';
|
||||
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
header('Location: ../index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$csvFilePath = __DIR__ . '/../assets/csv/actions.csv';
|
||||
|
||||
if (!is_readable($csvFilePath)) {
|
||||
http_response_code(404);
|
||||
echo 'Fichier CSV introuvable.';
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: text/csv; charset=UTF-8');
|
||||
header('Content-Disposition: attachment; filename="actions.csv"');
|
||||
header('Content-Length: ' . (string) filesize($csvFilePath));
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
readfile($csvFilePath);
|
||||
exit;
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/session.php';
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
use App\Database\Database;
|
||||
use App\Database\ActionsManager;
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => 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;
|
||||
}
|
||||
Reference in New Issue
Block a user