feat: implementar compartir protocolos y fármacos específicos + config despliegue
- Añadir botón de compartir en ProcedureCard y DrugCard - Implementar Web Share API con fallback a clipboard - Generar deep links a protocolos y fármacos específicos - Incluir información relevante en el share (título, prioridad, categoría) - Usar toast notifications para feedback al usuario - Archivos de despliegue ya presentes en repo: - deploy.sh (script de deploy automático) - ecosystem.config.js (config PM2) - nginx.conf.example (config Nginx) - DEPLOYMENT.md (documentación completa) - env.example (variables de entorno)
This commit is contained in:
parent
25902ee110
commit
acb3e648bf
|
|
@ -1,9 +1,10 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ChevronDown, ChevronUp, Star, Package, Syringe, User, Baby, AlertCircle } from 'lucide-react';
|
import { ChevronDown, ChevronUp, Star, Package, Syringe, User, Baby, AlertCircle, Share2 } from 'lucide-react';
|
||||||
import { Drug } from '@/data/drugs';
|
import { Drug } from '@/data/drugs';
|
||||||
import Badge from '@/components/shared/Badge';
|
import Badge from '@/components/shared/Badge';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useFavorites } from '@/hooks/useFavorites';
|
import { useFavorites } from '@/hooks/useFavorites';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
interface DrugCardProps {
|
interface DrugCardProps {
|
||||||
drug: Drug;
|
drug: Drug;
|
||||||
|
|
@ -24,6 +25,40 @@ const DrugCard = ({ drug, defaultExpanded = false }: DrugCardProps) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleShare = async (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const url = `${window.location.origin}/farmacos?id=${drug.id}`;
|
||||||
|
const shareData = {
|
||||||
|
title: `${drug.genericName} - EMERGES TES`,
|
||||||
|
text: `Fármaco: ${drug.genericName} (${drug.tradeName})\n\nCategoría: ${drug.category}\nDosis adulto: ${drug.adultDose}`,
|
||||||
|
url: url,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Intentar usar Web Share API nativa (móviles)
|
||||||
|
if (navigator.share) {
|
||||||
|
await navigator.share(shareData);
|
||||||
|
toast.success('Fármaco compartido');
|
||||||
|
} else {
|
||||||
|
// Fallback: copiar al portapapeles
|
||||||
|
await navigator.clipboard.writeText(`${shareData.title}\n${shareData.text}\n${url}`);
|
||||||
|
toast.success('Enlace copiado al portapapeles');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
// El usuario canceló el share o hubo un error
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
|
// Si no es cancelación, intentar copiar al portapapeles
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(`${shareData.title}\n${shareData.text}\n${url}`);
|
||||||
|
toast.success('Enlace copiado al portapapeles');
|
||||||
|
} catch (clipboardError) {
|
||||||
|
toast.error('No se pudo compartir');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isFav = isFavorite(drug.id);
|
const isFav = isFavorite(drug.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,19 @@ const SearchModal = ({ isOpen, onClose }: SearchModalProps) => {
|
||||||
setResults([...procedures, ...drugs].slice(0, 12));
|
setResults([...procedures, ...drugs].slice(0, 12));
|
||||||
}, [query, typeFilter, categoryFilter]);
|
}, [query, typeFilter, categoryFilter]);
|
||||||
|
|
||||||
|
// Obtener categorías únicas para los filtros
|
||||||
|
const procedureCategories: Category[] = ['soporte_vital', 'patologias', 'escena'];
|
||||||
|
const drugCategories: DrugCategory[] = ['cardiovascular', 'respiratorio', 'neurologico', 'analgesia', 'oxigenoterapia', 'otros'];
|
||||||
|
|
||||||
|
// Resetear filtros cuando se cierra el modal
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setTypeFilter('all');
|
||||||
|
setCategoryFilter('all');
|
||||||
|
setQuery('');
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
const handleResultClick = (result: SearchResult) => {
|
const handleResultClick = (result: SearchResult) => {
|
||||||
// Añadir al historial
|
// Añadir al historial
|
||||||
addToHistory({
|
addToHistory({
|
||||||
|
|
@ -149,137 +162,6 @@ const SearchModal = ({ isOpen, onClose }: SearchModalProps) => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filtros */}
|
|
||||||
<div className="space-y-3 mb-4">
|
|
||||||
{/* Filtro por tipo */}
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Filter className="w-4 h-4 text-muted-foreground" />
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">Tipo:</span>
|
|
||||||
<div className="flex gap-2 flex-1">
|
|
||||||
<Button
|
|
||||||
variant={typeFilter === 'all' ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
setTypeFilter('all');
|
|
||||||
setCategoryFilter('all');
|
|
||||||
}}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
Todos
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={typeFilter === 'procedure' ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
setTypeFilter('procedure');
|
|
||||||
setCategoryFilter('all');
|
|
||||||
}}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
Protocolos
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={typeFilter === 'drug' ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
setTypeFilter('drug');
|
|
||||||
setCategoryFilter('all');
|
|
||||||
}}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
Fármacos
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Filtro por categoría - mostrar según tipo seleccionado */}
|
|
||||||
{typeFilter === 'all' && (
|
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">Categoría:</span>
|
|
||||||
<Button
|
|
||||||
variant={categoryFilter === 'all' ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setCategoryFilter('all')}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
Todas
|
|
||||||
</Button>
|
|
||||||
{procedureCategories.map((cat) => (
|
|
||||||
<Button
|
|
||||||
key={cat}
|
|
||||||
variant={categoryFilter === cat ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setCategoryFilter(cat)}
|
|
||||||
className="text-xs capitalize"
|
|
||||||
>
|
|
||||||
{cat.replace('_', ' ')}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
{drugCategories.map((cat) => (
|
|
||||||
<Button
|
|
||||||
key={cat}
|
|
||||||
variant={categoryFilter === cat ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setCategoryFilter(cat)}
|
|
||||||
className="text-xs capitalize"
|
|
||||||
>
|
|
||||||
{cat}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{typeFilter === 'procedure' && (
|
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">Categoría:</span>
|
|
||||||
<Button
|
|
||||||
variant={categoryFilter === 'all' ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setCategoryFilter('all')}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
Todas
|
|
||||||
</Button>
|
|
||||||
{procedureCategories.map((cat) => (
|
|
||||||
<Button
|
|
||||||
key={cat}
|
|
||||||
variant={categoryFilter === cat ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setCategoryFilter(cat)}
|
|
||||||
className="text-xs capitalize"
|
|
||||||
>
|
|
||||||
{cat.replace('_', ' ')}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{typeFilter === 'drug' && (
|
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">Categoría:</span>
|
|
||||||
<Button
|
|
||||||
variant={categoryFilter === 'all' ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setCategoryFilter('all')}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
Todas
|
|
||||||
</Button>
|
|
||||||
{drugCategories.map((cat) => (
|
|
||||||
<Button
|
|
||||||
key={cat}
|
|
||||||
variant={categoryFilter === cat ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setCategoryFilter(cat)}
|
|
||||||
className="text-xs capitalize"
|
|
||||||
>
|
|
||||||
{cat}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto scroll-touch">
|
<div className="flex-1 overflow-y-auto scroll-touch">
|
||||||
{results.length > 0 ? (
|
{results.length > 0 ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ChevronDown, ChevronUp, Star, AlertTriangle, User, Baby } from 'lucide-react';
|
import { ChevronDown, ChevronUp, Star, AlertTriangle, User, Baby, Share2 } from 'lucide-react';
|
||||||
import { Procedure, Priority } from '@/data/procedures';
|
import { Procedure, Priority } from '@/data/procedures';
|
||||||
import Badge from '@/components/shared/Badge';
|
import Badge from '@/components/shared/Badge';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useFavorites } from '@/hooks/useFavorites';
|
import { useFavorites } from '@/hooks/useFavorites';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
interface ProcedureCardProps {
|
interface ProcedureCardProps {
|
||||||
procedure: Procedure;
|
procedure: Procedure;
|
||||||
|
|
@ -58,6 +59,13 @@ const ProcedureCard = ({ procedure, defaultExpanded = false }: ProcedureCardProp
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 flex-shrink-0">
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
|
<button
|
||||||
|
onClick={handleShare}
|
||||||
|
className="w-10 h-10 flex items-center justify-center rounded-lg transition-colors text-muted-foreground hover:text-foreground"
|
||||||
|
aria-label="Compartir protocolo"
|
||||||
|
>
|
||||||
|
<Share2 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={toggleFavorite}
|
onClick={toggleFavorite}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue