238 lines
11 KiB
PHP
238 lines
11 KiB
PHP
<?php
|
|
$titulo_pagina = 'Cotações';
|
|
$pagina_ativa = 'cotacoes';
|
|
|
|
require_once __DIR__ . '/includes/config.php';
|
|
require_once __DIR__ . '/includes/db.php';
|
|
require_once __DIR__ . '/includes/auth.php';
|
|
|
|
ao_exigir_permissao('cotacoes');
|
|
|
|
$ao_usuario_id = (int) ao_usuario_logado()['id'];
|
|
|
|
/** Converte "38,42" ou "38.42" em float. Retorna null se não for um número válido. */
|
|
function ao_cot_parse_decimal_br($valor) {
|
|
$valor = str_replace(',', '.', trim((string) $valor));
|
|
return is_numeric($valor) ? (float) $valor : null;
|
|
}
|
|
|
|
$erro = null;
|
|
$sucesso = null;
|
|
|
|
// Passa a acompanhar um novo ativo. A cotação em si mora numa tabela única e
|
|
// global ("cotacoes"): se o ativo ainda não existe ali, criamos com preço
|
|
// zerado (até alguém informar o primeiro valor); se já existe, é só ligar
|
|
// este usuário a ele.
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['acao'] ?? '') === 'acompanhar') {
|
|
$ativo = strtoupper(trim($_POST['ativo'] ?? ''));
|
|
|
|
if ($ativo === '') {
|
|
$erro = 'Informe o código do ativo que você quer acompanhar.';
|
|
} else {
|
|
try {
|
|
$pdo->prepare('INSERT IGNORE INTO cotacoes (ativo) VALUES (?)')->execute([$ativo]);
|
|
$pdo->prepare('INSERT INTO cotacoes_acompanhamentos (usuario_id, ativo) VALUES (?, ?)')
|
|
->execute([$ao_usuario_id, $ativo]);
|
|
header('Location: cotacoes.php?acompanhado=1');
|
|
exit;
|
|
} catch (PDOException $e) {
|
|
$erro = ($e->getCode() === '23000') ? ('Você já acompanha ' . $ativo . '.') : ('Erro ao salvar: ' . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Para de acompanhar (não mexe na cotação compartilhada, só na lista pessoal).
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['acao'] ?? '') === 'parar') {
|
|
$id = (int) ($_POST['id'] ?? 0);
|
|
$pdo->prepare('DELETE FROM cotacoes_acompanhamentos WHERE id = ? AND usuario_id = ?')->execute([$id, $ao_usuario_id]);
|
|
header('Location: cotacoes.php?parado=1');
|
|
exit;
|
|
}
|
|
|
|
// Atualiza o preço de um ativo na tabela única/global — vale para todo mundo
|
|
// que acompanha esse ativo, não só para quem atualizou. Só deixamos atualizar
|
|
// o preço de um ativo que o próprio usuário acompanha (evita alguém ficar
|
|
// alterando cotação de ativo que nem apareceu pra ele na tela).
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['acao'] ?? '') === 'atualizar_preco') {
|
|
$ativo = strtoupper(trim($_POST['ativo'] ?? ''));
|
|
$preco_bruto = trim($_POST['preco'] ?? '');
|
|
$preco = ao_cot_parse_decimal_br($preco_bruto);
|
|
|
|
$stmtAcompanha = $pdo->prepare('SELECT 1 FROM cotacoes_acompanhamentos WHERE usuario_id = ? AND ativo = ?');
|
|
$stmtAcompanha->execute([$ao_usuario_id, $ativo]);
|
|
|
|
if (!$stmtAcompanha->fetch()) {
|
|
$erro = 'Você só pode atualizar o preço de ativos que você acompanha.';
|
|
} elseif ($preco === null || $preco <= 0) {
|
|
$erro = 'Informe um preço válido (maior que zero).';
|
|
} else {
|
|
$stmtAtual = $pdo->prepare('SELECT preco_atual FROM cotacoes WHERE ativo = ?');
|
|
$stmtAtual->execute([$ativo]);
|
|
$preco_atual_antigo = (float) $stmtAtual->fetchColumn();
|
|
|
|
// Se é a primeira vez que alguém informa um preço para este ativo, não
|
|
// dá pra calcular variação a partir de zero — começa "zerada" (0%) em
|
|
// vez de mostrar um salto absurdo de +infinito.
|
|
$preco_anterior = $preco_atual_antigo > 0 ? $preco_atual_antigo : $preco;
|
|
|
|
$stmt = $pdo->prepare('UPDATE cotacoes SET preco_anterior = ?, preco_atual = ?, atualizado_por = ? WHERE ativo = ?');
|
|
$stmt->execute([$preco_anterior, $preco, $ao_usuario_id, $ativo]);
|
|
header('Location: cotacoes.php?atualizado=1');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
if (isset($_GET['acompanhado'])) $sucesso = 'Ativo adicionado à sua lista de acompanhamento.';
|
|
if (isset($_GET['parado'])) $sucesso = 'Você parou de acompanhar esse ativo.';
|
|
if (isset($_GET['atualizado'])) $sucesso = 'Cotação atualizada — todo mundo que acompanha esse ativo já vê o novo preço.';
|
|
|
|
// Mantém o que foi digitado no formulário de "acompanhar" se deu erro.
|
|
$ativo_digitado = ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['acao'] ?? '') === 'acompanhar')
|
|
? (string) ($_POST['ativo'] ?? '')
|
|
: '';
|
|
|
|
// A lista de preços vem sempre da tabela "cotacoes" (única/global) — aqui só
|
|
// filtramos para os ativos que ESTE usuário decidiu acompanhar.
|
|
$stmt = $pdo->prepare(
|
|
'SELECT ca.id AS acompanhamento_id, c.ativo, c.preco_atual, c.preco_anterior, c.atualizado_em, c.atualizado_por,
|
|
u.nome AS atualizado_por_nome
|
|
FROM cotacoes_acompanhamentos ca
|
|
INNER JOIN cotacoes c ON c.ativo = ca.ativo
|
|
LEFT JOIN usuarios u ON u.id = c.atualizado_por
|
|
WHERE ca.usuario_id = ?
|
|
ORDER BY c.ativo'
|
|
);
|
|
$stmt->execute([$ao_usuario_id]);
|
|
$acompanhados = $stmt->fetchAll();
|
|
|
|
// Calcula a variação percentual de cada um e separa em altas (>= 0%) e
|
|
// baixas (< 0%) para as duas grids pedidas.
|
|
foreach ($acompanhados as &$a) {
|
|
$anterior = (float) $a['preco_anterior'];
|
|
$atual = (float) $a['preco_atual'];
|
|
$a['tem_cotacao'] = $a['atualizado_por'] !== null;
|
|
$a['variacao'] = $anterior > 0 ? (($atual - $anterior) / $anterior) * 100 : 0.0;
|
|
}
|
|
unset($a);
|
|
|
|
$altas = array_values(array_filter($acompanhados, fn($a) => $a['variacao'] >= 0));
|
|
$baixas = array_values(array_filter($acompanhados, fn($a) => $a['variacao'] < 0));
|
|
|
|
usort($altas, fn($a, $b) => $b['variacao'] <=> $a['variacao']);
|
|
usort($baixas, fn($a, $b) => $a['variacao'] <=> $b['variacao']);
|
|
|
|
require __DIR__ . '/includes/header.php';
|
|
|
|
/** Uma linha da grid de cotações (compartilhada entre a grid de altas e a de baixas). */
|
|
function ao_cot_linha(array $a) {
|
|
$classe = $a['variacao'] >= 0 ? 'ao-badge-alta' : 'ao-badge-baixa';
|
|
$seta = $a['variacao'] >= 0 ? '▲' : '▼';
|
|
?>
|
|
<tr>
|
|
<td class="ao-numeric"><?= htmlspecialchars($a['ativo']) ?></td>
|
|
<td class="ao-numeric">
|
|
<?php if ($a['tem_cotacao']): ?>
|
|
R$ <?= number_format((float) $a['preco_atual'], 2, ',', '.') ?>
|
|
<?php else: ?>
|
|
<span style="color: var(--ao-text-muted);">sem cotação ainda</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="ao-numeric">
|
|
<?php if ($a['tem_cotacao']): ?>
|
|
<span class="ao-badge <?= $classe ?>"><?= $seta ?> <?= number_format(abs($a['variacao']), 2, ',', '.') ?>%</span>
|
|
<?php else: ?>
|
|
<span style="color: var(--ao-text-muted);">—</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td style="color: var(--ao-text-muted); font-size: var(--ao-fs-xs);">
|
|
<?php if ($a['tem_cotacao']): ?>
|
|
<?= htmlspecialchars($a['atualizado_por_nome'] ?? '—') ?> em <?= htmlspecialchars(date('d/m/Y H:i', strtotime($a['atualizado_em']))) ?>
|
|
<?php else: ?>
|
|
—
|
|
<?php endif; ?>
|
|
</td>
|
|
<td style="white-space: nowrap; text-align: right;">
|
|
<form method="post" action="cotacoes.php" style="display:inline-flex; gap: 4px; align-items: center;">
|
|
<input type="hidden" name="acao" value="atualizar_preco">
|
|
<input type="hidden" name="ativo" value="<?= htmlspecialchars($a['ativo']) ?>">
|
|
<input type="text" name="preco" placeholder="Novo preço" required
|
|
style="width: 100px; padding: 4px 8px; font-size: var(--ao-fs-xs);">
|
|
<button type="submit" class="ao-btn ao-btn-secondary" style="padding: 4px 10px; font-size: var(--ao-fs-xs);">Atualizar</button>
|
|
</form>
|
|
<form method="post" action="cotacoes.php" style="display:inline;" onsubmit="return confirm('Parar de acompanhar <?= htmlspecialchars($a['ativo']) ?>?');">
|
|
<input type="hidden" name="acao" value="parar">
|
|
<input type="hidden" name="id" value="<?= (int) $a['acompanhamento_id'] ?>">
|
|
<button type="submit" class="ao-btn ao-btn-secondary" style="padding: 4px 10px; font-size: var(--ao-fs-xs);">Parar</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
?>
|
|
|
|
<h1 class="ao-page-title">Cotações do dia</h1>
|
|
|
|
<?php if ($erro): ?>
|
|
<div class="ao-badge ao-badge-baixa" style="display:block; margin-bottom: var(--ao-space-4); padding: 10px 14px; font-size: var(--ao-fs-sm);"><?= htmlspecialchars($erro) ?></div>
|
|
<?php endif; ?>
|
|
<?php if ($sucesso): ?>
|
|
<div class="ao-badge ao-badge-alta" style="display:block; margin-bottom: var(--ao-space-4); padding: 10px 14px; font-size: var(--ao-fs-sm);"><?= htmlspecialchars($sucesso) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="ao-card" style="max-width: 480px; margin-bottom: var(--ao-space-6);">
|
|
<div class="ao-card-header"><strong>Acompanhar novo ativo</strong></div>
|
|
<p style="color: var(--ao-text-muted); font-size: var(--ao-fs-sm); margin-top: 0;">
|
|
A cotação é compartilhada: qualquer pessoa que também acompanhe o ativo pode
|
|
atualizar o preço, e todo mundo que o acompanha vê o valor mais recente.
|
|
</p>
|
|
<form method="post" action="cotacoes.php" class="ao-row" style="gap: var(--ao-space-3); align-items: flex-end;">
|
|
<input type="hidden" name="acao" value="acompanhar">
|
|
<div class="ao-field" style="flex: 1; margin-bottom: 0;">
|
|
<label for="ativo">Código do ativo</label>
|
|
<input type="text" id="ativo" name="ativo" placeholder="Ex: PETR4" required
|
|
style="text-transform: uppercase;" value="<?= htmlspecialchars($ativo_digitado) ?>">
|
|
</div>
|
|
<button type="submit" class="ao-btn ao-btn-primary">Acompanhar</button>
|
|
</form>
|
|
</div>
|
|
|
|
<?php if (!$acompanhados): ?>
|
|
<div class="ao-card">
|
|
<p style="margin: 0; color: var(--ao-text-muted);">Você ainda não está acompanhando nenhum ativo. Use o formulário acima para adicionar o primeiro.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
|
|
<div class="ao-card" style="margin-bottom: var(--ao-space-6);">
|
|
<div class="ao-card-header"><strong>Altas do dia</strong></div>
|
|
<table class="ao-table">
|
|
<thead>
|
|
<tr><th>Ativo</th><th>Último</th><th>Variação</th><th>Última atualização</th><th></th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (!$altas): ?>
|
|
<tr><td colspan="5" style="color: var(--ao-text-muted);">Nenhum ativo em alta no momento.</td></tr>
|
|
<?php endif; ?>
|
|
<?php foreach ($altas as $a) ao_cot_linha($a); ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="ao-card">
|
|
<div class="ao-card-header"><strong>Baixas do dia</strong></div>
|
|
<table class="ao-table">
|
|
<thead>
|
|
<tr><th>Ativo</th><th>Último</th><th>Variação</th><th>Última atualização</th><th></th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (!$baixas): ?>
|
|
<tr><td colspan="5" style="color: var(--ao-text-muted);">Nenhum ativo em baixa no momento.</td></tr>
|
|
<?php endif; ?>
|
|
<?php foreach ($baixas as $a) ao_cot_linha($a); ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<?php endif; ?>
|
|
|
|
<?php require __DIR__ . '/includes/footer.php'; ?>
|