Restructure with Turborepo
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import './AlertsView.css';
|
||||
|
||||
const iconMap = {
|
||||
bell: (
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 22c1.1 0 2-.9 2-2h-4a2 2 0 0 0 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z" />
|
||||
</svg>
|
||||
),
|
||||
store: (
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M18.36 9l.6 3H5.04l.6-3h12.72M20 4H4v2h16V4zm0 3H4l-1 5v2h1v6h10v-6h4v6h2v-6h1v-2l-1-5zM6 18v-4h6v4H6z" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
function AlertsView() {
|
||||
const [availability, setAvailability] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const fetchAvailabilityNotifications = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const notifsRes = await fetch('/api/notifications/mine', { credentials: 'include' });
|
||||
if (notifsRes.status === 401) {
|
||||
setError('Por favor, inicia sesión para ver tus avisos.');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
let notifsData = { global: [], pharmacy: [] };
|
||||
if (notifsRes.ok) {
|
||||
notifsData = await notifsRes.json();
|
||||
}
|
||||
|
||||
const mergedAvailability = [
|
||||
...(notifsData.global || []).map(item => ({ ...item, scope: 'global' })),
|
||||
...(notifsData.pharmacy || []).map(item => ({ ...item, scope: 'pharmacy' })),
|
||||
].sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''));
|
||||
|
||||
setAvailability(mergedAvailability);
|
||||
} catch (err) {
|
||||
console.error('Error fetching availability notifications:', err);
|
||||
setError('No se pudieron cargar los avisos.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAvailabilityNotifications();
|
||||
}, []);
|
||||
|
||||
const handleDeleteAvailability = async (item) => {
|
||||
try {
|
||||
const res = await fetch('/api/notifications/mine', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ scope: item.scope, id: item.id }),
|
||||
});
|
||||
if (res.ok || res.status === 204) {
|
||||
setAvailability(prev => prev.filter(i => !(i.scope === item.scope && i.id === item.id)));
|
||||
|
||||
// Also update local storage if possible to reflect that we unsubscribed
|
||||
try {
|
||||
const STORAGE_KEY = 'farmaclic.subscriptions.v1';
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) {
|
||||
const keyToSearch = item.scope === 'pharmacy'
|
||||
? `${item.medicine_nregistro}:${item.pharmacy_id}`
|
||||
: item.medicine_nregistro;
|
||||
const filtered = parsed.filter(k => k !== keyToSearch);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not update localStorage subscriptions:', e);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Error deleting availability alert');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError('No se pudo eliminar la alerta de disponibilidad.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="alerts-view">
|
||||
<main className="alerts-main">
|
||||
<div className="alerts-header">
|
||||
<h2 className="alerts-title">Mis Avisos</h2>
|
||||
<p className="alerts-subtitle">Mantente al día con tus medicamentos.</p>
|
||||
</div>
|
||||
|
||||
{loading && <p className="alerts-status" style={{ textAlign: 'center', padding: '2rem' }}>Cargando avisos...</p>}
|
||||
{!loading && error && <p className="alerts-error" style={{ color: 'var(--error)', textAlign: 'center', padding: '2rem' }}>{error}</p>}
|
||||
|
||||
{!loading && !error && (
|
||||
<>
|
||||
{/* Availability Section */}
|
||||
<section className="alerts-section">
|
||||
<h3 className="alerts-section-title" style={{ marginBottom: '1rem' }}>Disponibilidad</h3>
|
||||
<div className="alerts-list">
|
||||
{availability.length === 0 ? (
|
||||
<p className="alerts-empty" style={{ color: 'var(--on-surface-variant)', fontStyle: 'italic' }}>No tienes suscripciones de disponibilidad.</p>
|
||||
) : (
|
||||
availability.map((alert) => {
|
||||
const cardColor = alert.scope === 'pharmacy' ? 'primary' : 'tertiary';
|
||||
const cardIcon = alert.scope === 'pharmacy' ? 'store' : 'bell';
|
||||
const key = `${alert.scope}:${alert.id}`;
|
||||
return (
|
||||
<div key={key} className={`alert-card alert-card--${cardColor}`}>
|
||||
<div className="alert-card-top">
|
||||
<div className={`alert-icon alert-icon--${cardColor}`}>
|
||||
{iconMap[cardIcon]}
|
||||
</div>
|
||||
<div className="alert-info">
|
||||
<h3 className="alert-title">{alert.medicine_name || alert.medicine_nregistro}</h3>
|
||||
{alert.scope === 'pharmacy' ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem', marginTop: '0.25rem' }}>
|
||||
<span className={`alert-badge alert-badge--${cardColor}`} style={{ width: 'fit-content' }}>
|
||||
🏥 {alert.pharmacy_name || `Farmacia #${alert.pharmacy_id}`}
|
||||
</span>
|
||||
{alert.pharmacy_address && (
|
||||
<p className="alert-address-text" style={{ fontSize: '0.9rem', color: 'var(--on-surface-variant)', margin: 0 }}>
|
||||
📍 {alert.pharmacy_address}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<span className={`alert-badge alert-badge--${cardColor}`} style={{ width: 'fit-content', marginTop: '0.25rem' }}>
|
||||
🔔 Notificarme cuando esté disponible
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="alert-actions" style={{ justifyContent: 'flex-end' }}>
|
||||
<button
|
||||
className="alert-delete"
|
||||
aria-label="Eliminar"
|
||||
onClick={() => handleDeleteAvailability(alert)}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="3 6 5 6 21 6" />
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AlertsView;
|
||||
Reference in New Issue
Block a user