chore: fix lint errors and reduce warnings across codebase (#234)
* chore: fix lint errors and reduce warnings across codebase - Fix noExplicitAny catches in backend routes and plugins - Fix noNestedTernary issues in backend services - Add keyboard event handlers for useKeyWithClickEvents in frontend - Disable noImportantStyles rule in biome.json - Fix formatting errors across all changed files - Fix test file lint issues Closes #233 * fix: restore any types in test files for TS compatibility * fix: revert Auth.tsx dependency array changes that caused infinite re-render * fix: null-safe user.username access in AppContext dependency array
This commit is contained in:
@@ -51,8 +51,18 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content about-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
className="modal-overlay"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="modal-content about-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useUnsavedChanges } from "../context";
|
||||
import type { ThemePreference } from "../hooks";
|
||||
import { useTheme } from "../hooks";
|
||||
import { useAuth } from "./Auth";
|
||||
|
||||
|
||||
@@ -39,8 +39,19 @@ export function ConfirmModal({
|
||||
}, [onCancel]);
|
||||
|
||||
return (
|
||||
<div className={`modal-overlay${overlayClassName ? ` ${overlayClassName}` : ""}`} onClick={onCancel}>
|
||||
<div className="modal-content" onClick={(e) => e.stopPropagation()} style={{ maxWidth: "450px" }}>
|
||||
<div
|
||||
className={`modal-overlay${overlayClassName ? ` ${overlayClassName}` : ""}`}
|
||||
onClick={onCancel}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onCancel();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
style={{ maxWidth: "450px" }}
|
||||
>
|
||||
<button className="modal-close" onClick={onCancel}>
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -28,7 +28,13 @@ export function DateInput({ value, placeholder, className, ...rest }: DateInputP
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`date-input-wrapper ${className ?? ""}`} onClick={handleClick}>
|
||||
<div
|
||||
className={`date-input-wrapper ${className ?? ""}`}
|
||||
onClick={handleClick}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") handleClick();
|
||||
}}
|
||||
>
|
||||
<span className="date-input-display" aria-hidden="true">
|
||||
{displayValue || placeholder || ""}
|
||||
</span>
|
||||
|
||||
@@ -29,7 +29,13 @@ export function DateTimeInput({ value, placeholder, className, ...rest }: DateTi
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`date-input-wrapper ${className ?? ""}`} onClick={handleClick}>
|
||||
<div
|
||||
className={`date-input-wrapper ${className ?? ""}`}
|
||||
onClick={handleClick}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") handleClick();
|
||||
}}
|
||||
>
|
||||
<span className="date-input-display" aria-hidden="true">
|
||||
{displayValue || placeholder || ""}
|
||||
</span>
|
||||
|
||||
@@ -13,8 +13,19 @@ export default function ExportModal({ isOpen, onClose, onExport, exporting }: Ex
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content" onClick={(e) => e.stopPropagation()} style={{ maxWidth: "450px" }}>
|
||||
<div
|
||||
className="modal-overlay"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
style={{ maxWidth: "450px" }}
|
||||
>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -19,12 +19,24 @@ export function Lightbox({ src, alt, onClose }: LightboxProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="lightbox-overlay" onClick={handleOverlayClick}>
|
||||
<div
|
||||
className="lightbox-overlay"
|
||||
onClick={handleOverlayClick}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
>
|
||||
<div className="lightbox-container">
|
||||
<button className="lightbox-close" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
<img src={src} alt={alt} className="lightbox-image" onClick={(e) => e.stopPropagation()} />
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="lightbox-image"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -154,14 +154,24 @@ export function MedDetailModal({
|
||||
const packageSize = getPackageSize(selectedMed);
|
||||
const currentStock = medCoverage ? Math.round(medCoverage.medsLeft) : getMedTotal(selectedMed);
|
||||
const status = medCoverage ? getStockStatus(medCoverage.daysLeft, medCoverage.medsLeft, settings) : null;
|
||||
const textClass =
|
||||
status?.className === "danger" ? "danger-text" : status?.className === "warning" ? "warning-text" : "success-text";
|
||||
const fallbackTextClass = status?.className === "warning" ? "warning-text" : "success-text";
|
||||
const textClass = status?.className === "danger" ? "danger-text" : fallbackTextClass;
|
||||
const stock = getBlisterStock(currentStock, selectedMed.pillsPerBlister, selectedMed.looseTablets, packageSize);
|
||||
const fullForBounds = Math.max(0, parseStockInput(editStockFullInput));
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content med-detail-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
className="modal-overlay"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="modal-content med-detail-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
@@ -172,6 +182,11 @@ export function MedDetailModal({
|
||||
<div
|
||||
className={`med-detail-avatar-wrapper ${selectedMed.imageUrl ? "clickable" : ""}`}
|
||||
onClick={() => selectedMed.imageUrl && onOpenImageLightbox()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
if (selectedMed.imageUrl) onOpenImageLightbox();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MedicationAvatar name={selectedMed.name} imageUrl={selectedMed.imageUrl} size="lg" />
|
||||
{selectedMed.imageUrl && <span className="expand-icon">🔍</span>}
|
||||
@@ -408,6 +423,9 @@ export function MedDetailModal({
|
||||
<h3
|
||||
className="section-header-clickable"
|
||||
onClick={() => onRefillHistoryExpandedChange(!refillHistoryExpanded)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") onRefillHistoryExpandedChange(!refillHistoryExpanded);
|
||||
}}
|
||||
>
|
||||
{t("refill.history")} ({refillHistory.length})
|
||||
<span className="expand-arrow">{refillHistoryExpanded ? "▼" : "▶"}</span>
|
||||
@@ -488,8 +506,16 @@ export function MedDetailModal({
|
||||
e.stopPropagation();
|
||||
onCloseRefillModal();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === "Escape") onCloseRefillModal();
|
||||
}}
|
||||
>
|
||||
<div className="modal-content refill-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
className="modal-content refill-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button className="modal-close" onClick={onCloseRefillModal}>
|
||||
×
|
||||
</button>
|
||||
@@ -585,8 +611,16 @@ export function MedDetailModal({
|
||||
e.stopPropagation();
|
||||
onCloseEditStockModal();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === "Escape") onCloseEditStockModal();
|
||||
}}
|
||||
>
|
||||
<div className="modal-content edit-stock-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
className="modal-content edit-stock-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button className="modal-close" onClick={onCloseEditStockModal}>
|
||||
×
|
||||
</button>
|
||||
@@ -602,6 +636,8 @@ export function MedDetailModal({
|
||||
? editStockPartialBlisterPills
|
||||
: editStockFullBlisters * selectedMed.pillsPerBlister + editStockPartialBlisterPills;
|
||||
const difference = newTotal - currentTotal;
|
||||
const negativeFallback = difference < 0 ? "negative" : "";
|
||||
const differenceClass = difference > 0 ? "positive" : negativeFallback;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -691,9 +727,7 @@ export function MedDetailModal({
|
||||
{newTotal} {newTotal === 1 ? t("common.pill") : t("common.pills")}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`summary-row difference ${difference > 0 ? "positive" : difference < 0 ? "negative" : ""}`}
|
||||
>
|
||||
<div className={`summary-row difference ${differenceClass}`}>
|
||||
<span>{t("editStock.difference")}:</span>
|
||||
<span>
|
||||
{difference > 0 ? "+" : ""}
|
||||
|
||||
@@ -137,13 +137,28 @@ export function MobileEditModal({
|
||||
const currentMed = editingId ? meds.find((m) => m.id === editingId) : null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content edit-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
className="modal-overlay"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="modal-content edit-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="edit-modal-header">
|
||||
<button type="button" className="ghost small btn-nav" onClick={onClose}>
|
||||
← {t("common.back")}
|
||||
</button>
|
||||
<h2>{editingId ? (readOnlyMode ? t("form.viewEntry") : t("form.editEntry")) : t("form.newEntry")}</h2>
|
||||
<h2>
|
||||
{(() => {
|
||||
const editLabel = readOnlyMode ? t("form.viewEntry") : t("form.editEntry");
|
||||
return editingId ? editLabel : t("form.newEntry");
|
||||
})()}
|
||||
</h2>
|
||||
</div>
|
||||
<form
|
||||
className="form-grid mobile-edit-form"
|
||||
|
||||
@@ -9,8 +9,18 @@ export default function ProfileModal({ isOpen, onClose }: ProfileModalProps) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content profile-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
className="modal-overlay"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="modal-content profile-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -42,8 +42,18 @@ export function ShareDialog({
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content share-dialog-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
className="modal-overlay"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="modal-content share-dialog-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
@@ -53,71 +63,79 @@ export function ShareDialog({
|
||||
<p className="share-dialog-description">{t("share.description")}</p>
|
||||
</div>
|
||||
|
||||
{sharePeople.length === 0 ? (
|
||||
<div className="share-dialog-empty">
|
||||
<p>{t("share.noPeople")}</p>
|
||||
</div>
|
||||
) : shareLink ? (
|
||||
<div className="share-dialog-result">
|
||||
<p className="share-success">{t("share.linkGenerated")}</p>
|
||||
<div className="share-link-box">
|
||||
<input
|
||||
type="text"
|
||||
value={shareLink}
|
||||
readOnly
|
||||
className="share-link-input"
|
||||
onClick={(e) => (e.target as HTMLInputElement).select()}
|
||||
/>
|
||||
<button className="btn-copy" onClick={onCopyShareLink}>
|
||||
{shareCopied ? "✓" : "📋"}
|
||||
</button>
|
||||
</div>
|
||||
{shareCopied && <span className="share-copied-hint">{t("share.copied")}</span>}
|
||||
<div className="share-dialog-footer">
|
||||
<button
|
||||
className="ghost"
|
||||
onClick={() => {
|
||||
onShareLinkChange(null);
|
||||
onShareCopiedChange(false);
|
||||
}}
|
||||
>
|
||||
{t("share.generateAnother")}
|
||||
</button>
|
||||
<button onClick={onClose}>{t("common.close")}</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="share-dialog-form">
|
||||
<div className="form-group">
|
||||
<label>{t("share.selectPerson")}</label>
|
||||
<select value={shareSelectedPerson} onChange={(e) => onShareSelectedPersonChange(e.target.value)}>
|
||||
{sharePeople.map((person) => (
|
||||
<option key={person} value={person}>
|
||||
{person}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{(() => {
|
||||
if (sharePeople.length === 0) {
|
||||
return (
|
||||
<div className="share-dialog-empty">
|
||||
<p>{t("share.noPeople")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (shareLink) {
|
||||
return (
|
||||
<div className="share-dialog-result">
|
||||
<p className="share-success">{t("share.linkGenerated")}</p>
|
||||
<div className="share-link-box">
|
||||
<input
|
||||
type="text"
|
||||
value={shareLink}
|
||||
readOnly
|
||||
className="share-link-input"
|
||||
onClick={(e) => (e.target as HTMLInputElement).select()}
|
||||
/>
|
||||
<button className="btn-copy" onClick={onCopyShareLink}>
|
||||
{shareCopied ? "✓" : "📋"}
|
||||
</button>
|
||||
</div>
|
||||
{shareCopied && <span className="share-copied-hint">{t("share.copied")}</span>}
|
||||
<div className="share-dialog-footer">
|
||||
<button
|
||||
className="ghost"
|
||||
onClick={() => {
|
||||
onShareLinkChange(null);
|
||||
onShareCopiedChange(false);
|
||||
}}
|
||||
>
|
||||
{t("share.generateAnother")}
|
||||
</button>
|
||||
<button onClick={onClose}>{t("common.close")}</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="share-dialog-form">
|
||||
<div className="form-group">
|
||||
<label>{t("share.selectPerson")}</label>
|
||||
<select value={shareSelectedPerson} onChange={(e) => onShareSelectedPersonChange(e.target.value)}>
|
||||
{sharePeople.map((person) => (
|
||||
<option key={person} value={person}>
|
||||
{person}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>{t("share.selectPeriod")}</label>
|
||||
<select value={shareSelectedDays} onChange={(e) => onShareSelectedDaysChange(Number(e.target.value))}>
|
||||
<option value={30}>{t("dashboard.schedules.1month")}</option>
|
||||
<option value={90}>{t("dashboard.schedules.3months")}</option>
|
||||
<option value={180}>{t("dashboard.schedules.6months")}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>{t("share.selectPeriod")}</label>
|
||||
<select value={shareSelectedDays} onChange={(e) => onShareSelectedDaysChange(Number(e.target.value))}>
|
||||
<option value={30}>{t("dashboard.schedules.1month")}</option>
|
||||
<option value={90}>{t("dashboard.schedules.3months")}</option>
|
||||
<option value={180}>{t("dashboard.schedules.6months")}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="share-dialog-footer">
|
||||
<button className="ghost" onClick={onClose}>
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
<button onClick={onGenerateShareLink} disabled={shareGenerating || !shareSelectedPerson}>
|
||||
{shareGenerating ? t("share.generating") : t("share.generateLink")}
|
||||
</button>
|
||||
<div className="share-dialog-footer">
|
||||
<button className="ghost" onClick={onClose}>
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
<button onClick={onGenerateShareLink} disabled={shareGenerating || !shareSelectedPerson}>
|
||||
{shareGenerating ? t("share.generating") : t("share.generateLink")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -209,7 +209,7 @@ export function SharedSchedule() {
|
||||
|
||||
// Get dose ID - for per-intake takenBy, the ID already has the person suffix
|
||||
// This helper is kept for compatibility but since dose.id already includes the suffix, it just returns the id
|
||||
function getDoseId(doseId: string, _person: string | null): string {
|
||||
function _getDoseId(doseId: string, _person: string | null): string {
|
||||
// The dose.id already includes the person suffix if there's a per-intake takenBy
|
||||
return doseId;
|
||||
}
|
||||
@@ -479,7 +479,8 @@ export function SharedSchedule() {
|
||||
|
||||
const intake = intakes[blisterIdx];
|
||||
const intakePerson = intake?.takenBy;
|
||||
const peopleForThisIntake = intakePerson ? [intakePerson] : med.takenBy?.length > 0 ? med.takenBy : [null];
|
||||
const fallbackPeople = med.takenBy?.length > 0 ? med.takenBy : [null];
|
||||
const peopleForThisIntake = intakePerson ? [intakePerson] : fallbackPeople;
|
||||
|
||||
let timeBasedConsumed = 0;
|
||||
let lastAutoConsumedDateMs = 0;
|
||||
@@ -579,7 +580,8 @@ export function SharedSchedule() {
|
||||
const status = getStockStatus(coverage.daysLeft, coverage.medsLeft, stockThresholds);
|
||||
return status.className;
|
||||
});
|
||||
return statuses.includes("danger") ? "danger" : statuses.includes("warning") ? "warning" : "success";
|
||||
const fallbackStatus = statuses.includes("warning") ? "warning" : "success";
|
||||
return statuses.includes("danger") ? "danger" : fallbackStatus;
|
||||
}
|
||||
|
||||
// Whether to show stock status indicators on the shared schedule
|
||||
@@ -606,7 +608,7 @@ export function SharedSchedule() {
|
||||
const missedPastDoseIds = useMemo(() => {
|
||||
const allPastDoseIds = pastDays.flatMap((d) => d.meds.flatMap((m) => m.doses.map((dose) => dose.id)));
|
||||
return allPastDoseIds.filter((id) => !isDoseIdDone(id));
|
||||
}, [pastDays, takenDoses, dismissedDoses, data]);
|
||||
}, [pastDays, isDoseIdDone]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -714,14 +716,19 @@ export function SharedSchedule() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="shared-schedule-period">
|
||||
{t("share.period")}:{" "}
|
||||
{data.scheduleDays === 30
|
||||
? t("dashboard.schedules.1month")
|
||||
: data.scheduleDays === 90
|
||||
? t("dashboard.schedules.3months")
|
||||
: t("dashboard.schedules.6months")}
|
||||
</p>
|
||||
{(() => {
|
||||
const periodLabel =
|
||||
data.scheduleDays === 30
|
||||
? t("dashboard.schedules.1month")
|
||||
: data.scheduleDays === 90
|
||||
? t("dashboard.schedules.3months")
|
||||
: t("dashboard.schedules.6months");
|
||||
return (
|
||||
<p className="shared-schedule-period">
|
||||
{t("share.period")}: {periodLabel}
|
||||
</p>
|
||||
);
|
||||
})()}
|
||||
</header>
|
||||
|
||||
<div className="timeline">
|
||||
@@ -757,14 +764,18 @@ export function SharedSchedule() {
|
||||
const isManuallyExpanded = manuallyExpandedDays.has(day.dateStr);
|
||||
const isCollapsed = !isManuallyExpanded;
|
||||
|
||||
const pastMissedClass = allDoseIds.length > 0 ? "past-missed" : "";
|
||||
return (
|
||||
<div
|
||||
key={day.dateStr}
|
||||
className={`day-block past ${isCollapsed ? "collapsed" : ""} ${allReallyTaken ? "all-taken" : allDoseIds.length > 0 ? "past-missed" : ""}`}
|
||||
className={`day-block past ${isCollapsed ? "collapsed" : ""} ${allReallyTaken ? "all-taken" : pastMissedClass}`}
|
||||
>
|
||||
<div
|
||||
className="day-divider clickable"
|
||||
onClick={() => toggleDayCollapse(day.dateStr, true)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") toggleDayCollapse(day.dateStr, true);
|
||||
}}
|
||||
title={isCollapsed ? t("common.expand") : t("common.collapse")}
|
||||
>
|
||||
<span className="day-collapse-icon">{isCollapsed ? "▶" : "▼"}</span>
|
||||
@@ -817,6 +828,11 @@ export function SharedSchedule() {
|
||||
<div
|
||||
className={med?.imageUrl ? "med-avatar clickable" : ""}
|
||||
onClick={() => med?.imageUrl && openLightbox(med.imageUrl, med.name)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
if (med?.imageUrl) openLightbox(med.imageUrl, med.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MedicationAvatar name={item.medName} imageUrl={med?.imageUrl} size="sm" />
|
||||
</div>
|
||||
@@ -894,6 +910,9 @@ export function SharedSchedule() {
|
||||
}, 50);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") setShowPastDays(!showPastDays);
|
||||
}}
|
||||
>
|
||||
<span className="past-days-icon">{showPastDays ? "▼" : "▶"}</span>
|
||||
<span className="past-days-label">
|
||||
@@ -941,6 +960,9 @@ export function SharedSchedule() {
|
||||
<div
|
||||
className="day-divider clickable"
|
||||
onClick={() => toggleDayCollapse(day.dateStr, isAutoCollapsed)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") toggleDayCollapse(day.dateStr, isAutoCollapsed);
|
||||
}}
|
||||
title={isCollapsed ? t("common.expand") : t("common.collapse")}
|
||||
>
|
||||
<span className="day-collapse-icon">{isCollapsed ? "▶" : "▼"}</span>
|
||||
@@ -982,6 +1004,11 @@ export function SharedSchedule() {
|
||||
<div
|
||||
className={med?.imageUrl ? "med-avatar clickable" : ""}
|
||||
onClick={() => med?.imageUrl && openLightbox(med.imageUrl, med.name)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
if (med?.imageUrl) openLightbox(med.imageUrl, med.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MedicationAvatar name={item.medName} imageUrl={med?.imageUrl} size="sm" />
|
||||
</div>
|
||||
@@ -1058,6 +1085,9 @@ export function SharedSchedule() {
|
||||
<div
|
||||
className={`future-days-toggle ${showFutureDays ? "expanded" : ""}`}
|
||||
onClick={() => setShowFutureDays(!showFutureDays)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") setShowFutureDays(!showFutureDays);
|
||||
}}
|
||||
>
|
||||
<span className="future-days-icon">{showFutureDays ? "▼" : "▶"}</span>
|
||||
<span className="future-days-label">
|
||||
@@ -1099,6 +1129,9 @@ export function SharedSchedule() {
|
||||
<div
|
||||
className="day-divider clickable"
|
||||
onClick={() => toggleDayCollapse(day.dateStr, isAutoCollapsed)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") toggleDayCollapse(day.dateStr, isAutoCollapsed);
|
||||
}}
|
||||
title={isCollapsed ? t("common.expand") : t("common.collapse")}
|
||||
>
|
||||
<span className="day-collapse-icon">{isCollapsed ? "▶" : "▼"}</span>
|
||||
@@ -1139,6 +1172,11 @@ export function SharedSchedule() {
|
||||
<div
|
||||
className={med?.imageUrl ? "med-avatar clickable" : ""}
|
||||
onClick={() => med?.imageUrl && openLightbox(med.imageUrl, med.name)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
if (med?.imageUrl) openLightbox(med.imageUrl, med.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MedicationAvatar name={item.medName} imageUrl={med?.imageUrl} size="sm" />
|
||||
</div>
|
||||
@@ -1215,7 +1253,13 @@ export function SharedSchedule() {
|
||||
|
||||
{/* Image Lightbox */}
|
||||
{lightboxImage && (
|
||||
<div className="lightbox-overlay" onClick={closeLightbox}>
|
||||
<div
|
||||
className="lightbox-overlay"
|
||||
onClick={closeLightbox}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") closeLightbox();
|
||||
}}
|
||||
>
|
||||
<button className="lightbox-close" onClick={closeLightbox}>
|
||||
×
|
||||
</button>
|
||||
@@ -1224,6 +1268,7 @@ export function SharedSchedule() {
|
||||
alt={lightboxImage.name}
|
||||
className="lightbox-image"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -36,8 +36,18 @@ export function UserFilterModal({
|
||||
const userMeds = meds.filter((m) => !m.isObsolete && (m.takenBy || []).includes(selectedUser));
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content user-meds-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
className="modal-overlay"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="modal-content user-meds-modal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
@@ -75,6 +85,12 @@ export function UserFilterModal({
|
||||
onClearUser();
|
||||
onOpenMedDetail(med);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
onClearUser();
|
||||
onOpenMedDetail(med);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MedicationAvatar name={med.name} imageUrl={med.imageUrl} size="sm" />
|
||||
<div className="user-med-info">
|
||||
|
||||
Reference in New Issue
Block a user