feat(stock-status): implement stock status indicators for medication days and update styles
This commit is contained in:
@@ -2,7 +2,7 @@ import { FastifyInstance } from "fastify";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import { db } from "../db/client.js";
|
import { db } from "../db/client.js";
|
||||||
import { medications, shareTokens } from "../db/schema.js";
|
import { medications, shareTokens, userSettings } from "../db/schema.js";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { requireAuth, optionalAuth } from "../plugins/auth.js";
|
import { requireAuth, optionalAuth } from "../plugins/auth.js";
|
||||||
import { env } from "../plugins/env.js";
|
import { env } from "../plugins/env.js";
|
||||||
@@ -48,6 +48,9 @@ export async function shareRoutes(app: FastifyInstance) {
|
|||||||
return reply.notFound("Share link not found");
|
return reply.notFound("Share link not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get user settings for stock thresholds
|
||||||
|
const [settings] = await db.select().from(userSettings).where(eq(userSettings.userId, share.userId));
|
||||||
|
|
||||||
// Get medications for this user filtered by takenBy
|
// Get medications for this user filtered by takenBy
|
||||||
const meds = await db.select().from(medications).where(
|
const meds = await db.select().from(medications).where(
|
||||||
and(
|
and(
|
||||||
@@ -78,6 +81,8 @@ export async function shareRoutes(app: FastifyInstance) {
|
|||||||
genericName: med.genericName,
|
genericName: med.genericName,
|
||||||
pillWeightMg: med.pillWeightMg,
|
pillWeightMg: med.pillWeightMg,
|
||||||
imageUrl: med.imageUrl,
|
imageUrl: med.imageUrl,
|
||||||
|
count: med.count,
|
||||||
|
tabsPerStrip: med.tabsPerStrip,
|
||||||
blisters,
|
blisters,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -86,6 +91,9 @@ export async function shareRoutes(app: FastifyInstance) {
|
|||||||
takenBy: share.takenBy,
|
takenBy: share.takenBy,
|
||||||
scheduleDays: share.scheduleDays,
|
scheduleDays: share.scheduleDays,
|
||||||
medications: medicationsWithBlisters,
|
medications: medicationsWithBlisters,
|
||||||
|
stockThresholds: {
|
||||||
|
lowStockDays: settings?.lowStockDays ?? 30,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+135
-6
@@ -968,7 +968,7 @@ function AppContent() {
|
|||||||
{coverage.low.map((row) => {
|
{coverage.low.map((row) => {
|
||||||
const status = getStockStatus(row.daysLeft, row.medsLeft, settings);
|
const status = getStockStatus(row.daysLeft, row.medsLeft, settings);
|
||||||
const med = meds.find(m => m.name === row.name);
|
const med = meds.find(m => m.name === row.name);
|
||||||
const textClass = status.className === "danger" ? "danger-text" : status.className === "warning" ? "warning-text" : "";
|
const textClass = status.className === "danger" ? "danger-text" : status.className === "warning" ? "warning-text" : "success-text";
|
||||||
const stock = getBlisterStock(
|
const stock = getBlisterStock(
|
||||||
Math.round(row.medsLeft),
|
Math.round(row.medsLeft),
|
||||||
med?.tabsPerStrip ?? 1,
|
med?.tabsPerStrip ?? 1,
|
||||||
@@ -1026,7 +1026,7 @@ function AppContent() {
|
|||||||
const status = getStockStatus(row.daysLeft, row.medsLeft, settings);
|
const status = getStockStatus(row.daysLeft, row.medsLeft, settings);
|
||||||
const med = meds.find(m => m.name === row.name);
|
const med = meds.find(m => m.name === row.name);
|
||||||
const expiryClass = getExpiryClass(med?.expiryDate, settings.expiryWarningDays);
|
const expiryClass = getExpiryClass(med?.expiryDate, settings.expiryWarningDays);
|
||||||
const textClass = status.className === "danger" ? "danger-text" : status.className === "warning" ? "warning-text" : "";
|
const textClass = status.className === "danger" ? "danger-text" : status.className === "warning" ? "warning-text" : "success-text";
|
||||||
const stock = getBlisterStock(
|
const stock = getBlisterStock(
|
||||||
Math.round(row.medsLeft),
|
Math.round(row.medsLeft),
|
||||||
med?.tabsPerStrip ?? 1,
|
med?.tabsPerStrip ?? 1,
|
||||||
@@ -1154,6 +1154,18 @@ function AppContent() {
|
|||||||
const allDayTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id));
|
const allDayTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id));
|
||||||
const takenCount = allDoseIds.filter((id) => takenDoses.has(id)).length;
|
const takenCount = allDoseIds.filter((id) => takenDoses.has(id)).length;
|
||||||
|
|
||||||
|
// Calculate worst stock status for this day
|
||||||
|
const dayStockStatuses = day.meds.map((item) => {
|
||||||
|
const medCoverage = coverageByMed[item.medName];
|
||||||
|
const depletionTime = depletionByMed[item.medName];
|
||||||
|
const willBeOutOfStock = typeof depletionTime === "number" && item.lastWhen > depletionTime;
|
||||||
|
if (willBeOutOfStock) return "danger";
|
||||||
|
if (!medCoverage) return "success";
|
||||||
|
const status = getStockStatus(medCoverage.daysLeft, medCoverage.medsLeft, settings);
|
||||||
|
return status.className;
|
||||||
|
});
|
||||||
|
const worstStatus = dayStockStatuses.includes("danger") ? "danger" : dayStockStatuses.includes("warning") ? "warning" : "success";
|
||||||
|
|
||||||
// Check if this is today, past, or future
|
// Check if this is today, past, or future
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
@@ -1168,7 +1180,7 @@ function AppContent() {
|
|||||||
const isCollapsed = isAutoCollapsed ? !isManuallyExpanded : isManuallyCollapsed;
|
const isCollapsed = isAutoCollapsed ? !isManuallyExpanded : isManuallyCollapsed;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={day.dateStr} className={`day-block ${isCollapsed ? "collapsed" : ""} ${allDayTaken ? "all-taken" : ""} ${isToday ? "today" : ""}`}>
|
<div key={day.dateStr} className={`day-block ${isCollapsed ? "collapsed" : ""} ${allDayTaken ? "all-taken" : ""} ${isToday ? "today" : ""} ${worstStatus ? `stock-${worstStatus}` : ""}`}>
|
||||||
<div
|
<div
|
||||||
className="day-divider clickable"
|
className="day-divider clickable"
|
||||||
onClick={() => toggleDayCollapse(day.dateStr, isAutoCollapsed)}
|
onClick={() => toggleDayCollapse(day.dateStr, isAutoCollapsed)}
|
||||||
@@ -1989,7 +2001,7 @@ function AppContent() {
|
|||||||
const currentStock = medCoverage ? Math.round(medCoverage.medsLeft) : selectedMed.count;
|
const currentStock = medCoverage ? Math.round(medCoverage.medsLeft) : selectedMed.count;
|
||||||
const totalStock = (selectedMed.packCount ?? 1) * (selectedMed.stripsPerPack ?? 1) * (selectedMed.tabsPerStrip ?? 1) + (selectedMed.looseTablets ?? 0);
|
const totalStock = (selectedMed.packCount ?? 1) * (selectedMed.stripsPerPack ?? 1) * (selectedMed.tabsPerStrip ?? 1) + (selectedMed.looseTablets ?? 0);
|
||||||
const status = medCoverage ? getStockStatus(medCoverage.daysLeft, medCoverage.medsLeft, settings) : null;
|
const status = medCoverage ? getStockStatus(medCoverage.daysLeft, medCoverage.medsLeft, settings) : null;
|
||||||
const textClass = status?.className === "danger" ? "danger-text" : status?.className === "warning" ? "warning-text" : "";
|
const textClass = status?.className === "danger" ? "danger-text" : status?.className === "warning" ? "warning-text" : "success-text";
|
||||||
const stock = getBlisterStock(
|
const stock = getBlisterStock(
|
||||||
currentStock,
|
currentStock,
|
||||||
selectedMed.tabsPerStrip ?? 1,
|
selectedMed.tabsPerStrip ?? 1,
|
||||||
@@ -2696,6 +2708,8 @@ type SharedMedication = {
|
|||||||
genericName?: string | null;
|
genericName?: string | null;
|
||||||
pillWeightMg?: number | null;
|
pillWeightMg?: number | null;
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
|
count?: number;
|
||||||
|
tabsPerStrip?: number;
|
||||||
blisters: Blister[];
|
blisters: Blister[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2703,6 +2717,9 @@ type SharedScheduleData = {
|
|||||||
takenBy: string;
|
takenBy: string;
|
||||||
scheduleDays: number;
|
scheduleDays: number;
|
||||||
medications: SharedMedication[];
|
medications: SharedMedication[];
|
||||||
|
stockThresholds?: {
|
||||||
|
lowStockDays: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function SharedSchedule() {
|
function SharedSchedule() {
|
||||||
@@ -2941,6 +2958,72 @@ function SharedSchedule() {
|
|||||||
const pastDays = useMemo(() => schedule.filter(d => d.isPast), [schedule]);
|
const pastDays = useMemo(() => schedule.filter(d => d.isPast), [schedule]);
|
||||||
const futureDays = useMemo(() => schedule.filter(d => !d.isPast), [schedule]);
|
const futureDays = useMemo(() => schedule.filter(d => !d.isPast), [schedule]);
|
||||||
|
|
||||||
|
// Calculate coverage for stock status colors (matches main app logic)
|
||||||
|
// This needs to account for taken doses and calculate depletion time
|
||||||
|
const { coverageByMed, depletionByMed } = useMemo(() => {
|
||||||
|
if (!data) return { coverageByMed: {}, depletionByMed: {} };
|
||||||
|
const coverage: Record<string, { daysLeft: number | null; medsLeft: number; dailyUsage: number }> = {};
|
||||||
|
const depletion: Record<string, number | null> = {};
|
||||||
|
|
||||||
|
// Calculate total pills taken per medication from takenDoses
|
||||||
|
const takenByMed: Record<string, number> = {};
|
||||||
|
for (const dose of schedule.flatMap(d => d.meds.flatMap(m => m.doses))) {
|
||||||
|
if (takenDoses.has(dose.id)) {
|
||||||
|
takenByMed[dose.medName] = (takenByMed[dose.medName] || 0) + dose.usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const med of data.medications) {
|
||||||
|
const totalCount = med.count ?? 0;
|
||||||
|
const taken = takenByMed[med.name] || 0;
|
||||||
|
const currentCount = Math.max(0, totalCount - taken);
|
||||||
|
// Calculate daily usage from blisters
|
||||||
|
const dailyUsage = med.blisters.reduce((sum, b) => sum + (b.usage / b.every), 0);
|
||||||
|
const daysLeft = dailyUsage > 0 ? currentCount / dailyUsage : null;
|
||||||
|
coverage[med.name] = { daysLeft, medsLeft: currentCount, dailyUsage };
|
||||||
|
|
||||||
|
// Calculate depletion time (when medication will run out)
|
||||||
|
if (dailyUsage > 0 && currentCount > 0) {
|
||||||
|
const daysUntilEmpty = currentCount / dailyUsage;
|
||||||
|
depletion[med.name] = Date.now() + daysUntilEmpty * 24 * 60 * 60 * 1000;
|
||||||
|
} else if (currentCount <= 0) {
|
||||||
|
depletion[med.name] = Date.now(); // Already empty
|
||||||
|
} else {
|
||||||
|
depletion[med.name] = null; // No usage schedule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { coverageByMed: coverage, depletionByMed: depletion };
|
||||||
|
}, [data, schedule, takenDoses]);
|
||||||
|
|
||||||
|
// Stock thresholds from user settings (provided by API) or defaults
|
||||||
|
const lowStockDays = data?.stockThresholds?.lowStockDays ?? 30;
|
||||||
|
|
||||||
|
// Get worst stock status for a day's medications (matches main app logic with depletion)
|
||||||
|
const getDayStockStatus = (meds: { medName: string; lastWhen: number }[]) => {
|
||||||
|
const statuses = meds.map((item) => {
|
||||||
|
const coverage = coverageByMed[item.medName];
|
||||||
|
const depletionTime = depletionByMed[item.medName];
|
||||||
|
|
||||||
|
// Will be out of stock by this day?
|
||||||
|
if (typeof depletionTime === "number" && item.lastWhen > depletionTime) {
|
||||||
|
return "danger";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!coverage) return "success";
|
||||||
|
const { daysLeft, medsLeft } = coverage;
|
||||||
|
|
||||||
|
// Currently out of stock
|
||||||
|
if (medsLeft <= 0 || daysLeft === 0) return "danger";
|
||||||
|
// No schedule (can't calculate)
|
||||||
|
if (daysLeft === null) return "success";
|
||||||
|
// Low stock: < lowStockDays (warning)
|
||||||
|
if (daysLeft < lowStockDays) return "warning";
|
||||||
|
// Normal/High stock
|
||||||
|
return "success";
|
||||||
|
});
|
||||||
|
return statuses.includes("danger") ? "danger" : statuses.includes("warning") ? "warning" : "success";
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="shared-schedule-page">
|
<div className="shared-schedule-page">
|
||||||
@@ -2999,8 +3082,11 @@ function SharedSchedule() {
|
|||||||
const isManuallyExpanded = manuallyExpandedDays.has(day.dateStr);
|
const isManuallyExpanded = manuallyExpandedDays.has(day.dateStr);
|
||||||
const isCollapsed = !isManuallyExpanded;
|
const isCollapsed = !isManuallyExpanded;
|
||||||
|
|
||||||
|
// Calculate stock status for this day
|
||||||
|
const worstStatus = getDayStockStatus(day.meds);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={day.dateStr} className={`day-block past ${isCollapsed ? "collapsed" : ""} ${allDayTaken ? "all-taken" : ""}`}>
|
<div key={day.dateStr} className={`day-block past ${isCollapsed ? "collapsed" : ""} ${allDayTaken ? "all-taken" : ""} stock-${worstStatus}`}>
|
||||||
<div
|
<div
|
||||||
className="day-divider clickable"
|
className="day-divider clickable"
|
||||||
onClick={() => toggleDayCollapse(day.dateStr, true)}
|
onClick={() => toggleDayCollapse(day.dateStr, true)}
|
||||||
@@ -3018,6 +3104,25 @@ function SharedSchedule() {
|
|||||||
</div>
|
</div>
|
||||||
{!isCollapsed && day.meds.map((item) => {
|
{!isCollapsed && day.meds.map((item) => {
|
||||||
const med = data.medications.find(m => m.name === item.medName);
|
const med = data.medications.find(m => m.name === item.medName);
|
||||||
|
const medCoverage = coverageByMed[item.medName];
|
||||||
|
const depletionTime = depletionByMed[item.medName];
|
||||||
|
const willBeOutOfStock = typeof depletionTime === "number" && item.lastWhen > depletionTime;
|
||||||
|
|
||||||
|
// Calculate status for this medication on this day
|
||||||
|
let status: { className: string; label: string } | null = null;
|
||||||
|
if (willBeOutOfStock) {
|
||||||
|
status = { className: "danger", label: "status.outOfStock" };
|
||||||
|
} else if (medCoverage) {
|
||||||
|
const { daysLeft, medsLeft } = medCoverage;
|
||||||
|
if (medsLeft <= 0 || daysLeft === 0) {
|
||||||
|
status = { className: "danger", label: "status.outOfStock" };
|
||||||
|
} else if (daysLeft !== null && daysLeft < lowStockDays) {
|
||||||
|
status = { className: "warning", label: "status.lowStock" };
|
||||||
|
} else {
|
||||||
|
status = { className: "success", label: "status.normal" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const allTaken = item.doses.every((d) => takenDoses.has(d.id));
|
const allTaken = item.doses.every((d) => takenDoses.has(d.id));
|
||||||
return (
|
return (
|
||||||
<div key={`${day.dateStr}-${item.medName}`} className={`time-row ${allTaken ? "taken" : ""}`}>
|
<div key={`${day.dateStr}-${item.medName}`} className={`time-row ${allTaken ? "taken" : ""}`}>
|
||||||
@@ -3034,6 +3139,7 @@ function SharedSchedule() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="tag-row">
|
<div className="tag-row">
|
||||||
<span className="tag subtle">{item.total} {t('common.pills')} {t('common.total')}</span>
|
<span className="tag subtle">{item.total} {t('common.pills')} {t('common.total')}</span>
|
||||||
|
{status && <span className={`tag ${status.className}`}>{t(status.label)}</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="doses-col">
|
<div className="doses-col">
|
||||||
@@ -3068,6 +3174,9 @@ function SharedSchedule() {
|
|||||||
const allDayTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id));
|
const allDayTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id));
|
||||||
const takenCount = allDoseIds.filter((id) => takenDoses.has(id)).length;
|
const takenCount = allDoseIds.filter((id) => takenDoses.has(id)).length;
|
||||||
|
|
||||||
|
// Calculate stock status for this day
|
||||||
|
const worstStatus = getDayStockStatus(day.meds);
|
||||||
|
|
||||||
// Check if this is today
|
// Check if this is today
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
@@ -3082,7 +3191,7 @@ function SharedSchedule() {
|
|||||||
const isCollapsed = isAutoCollapsed ? !isManuallyExpanded : isManuallyCollapsed;
|
const isCollapsed = isAutoCollapsed ? !isManuallyExpanded : isManuallyCollapsed;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={day.dateStr} className={`day-block ${isCollapsed ? "collapsed" : ""} ${allDayTaken ? "all-taken" : ""} ${isToday ? "today" : ""}`}>
|
<div key={day.dateStr} className={`day-block ${isCollapsed ? "collapsed" : ""} ${allDayTaken ? "all-taken" : ""} ${isToday ? "today" : ""} stock-${worstStatus}`}>
|
||||||
<div
|
<div
|
||||||
className="day-divider clickable"
|
className="day-divider clickable"
|
||||||
onClick={() => toggleDayCollapse(day.dateStr, isAutoCollapsed)}
|
onClick={() => toggleDayCollapse(day.dateStr, isAutoCollapsed)}
|
||||||
@@ -3100,6 +3209,25 @@ function SharedSchedule() {
|
|||||||
</div>
|
</div>
|
||||||
{!isCollapsed && day.meds.map((item) => {
|
{!isCollapsed && day.meds.map((item) => {
|
||||||
const med = data.medications.find(m => m.name === item.medName);
|
const med = data.medications.find(m => m.name === item.medName);
|
||||||
|
const medCoverage = coverageByMed[item.medName];
|
||||||
|
const depletionTime = depletionByMed[item.medName];
|
||||||
|
const willBeOutOfStock = typeof depletionTime === "number" && item.lastWhen > depletionTime;
|
||||||
|
|
||||||
|
// Calculate status for this medication on this day
|
||||||
|
let status: { className: string; label: string } | null = null;
|
||||||
|
if (willBeOutOfStock) {
|
||||||
|
status = { className: "danger", label: "status.outOfStock" };
|
||||||
|
} else if (medCoverage) {
|
||||||
|
const { daysLeft, medsLeft } = medCoverage;
|
||||||
|
if (medsLeft <= 0 || daysLeft === 0) {
|
||||||
|
status = { className: "danger", label: "status.outOfStock" };
|
||||||
|
} else if (daysLeft !== null && daysLeft < lowStockDays) {
|
||||||
|
status = { className: "warning", label: "status.lowStock" };
|
||||||
|
} else {
|
||||||
|
status = { className: "success", label: "status.normal" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const allTaken = item.doses.every((d) => takenDoses.has(d.id));
|
const allTaken = item.doses.every((d) => takenDoses.has(d.id));
|
||||||
return (
|
return (
|
||||||
<div key={`${day.dateStr}-${item.medName}`} className={`time-row ${allTaken ? "taken" : ""}`}>
|
<div key={`${day.dateStr}-${item.medName}`} className={`time-row ${allTaken ? "taken" : ""}`}>
|
||||||
@@ -3116,6 +3244,7 @@ function SharedSchedule() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="tag-row">
|
<div className="tag-row">
|
||||||
<span className="tag subtle">{item.total} {t('common.pills')} {t('common.total')}</span>
|
<span className="tag subtle">{item.total} {t('common.pills')} {t('common.total')}</span>
|
||||||
|
{status && <span className={`tag ${status.className}`}>{t(status.label)}</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="doses-col">
|
<div className="doses-col">
|
||||||
|
|||||||
@@ -451,6 +451,15 @@ textarea {
|
|||||||
.day-block.today .day-divider { color: var(--accent); }
|
.day-block.today .day-divider { color: var(--accent); }
|
||||||
.day-block.all-taken { border-color: rgba(57, 217, 138, 0.3); }
|
.day-block.all-taken { border-color: rgba(57, 217, 138, 0.3); }
|
||||||
.day-block.today.all-taken { border-color: var(--success); background: linear-gradient(135deg, rgba(57, 217, 138, 0.08) 0%, rgba(57, 217, 138, 0.02) 100%); }
|
.day-block.today.all-taken { border-color: var(--success); background: linear-gradient(135deg, rgba(57, 217, 138, 0.08) 0%, rgba(57, 217, 138, 0.02) 100%); }
|
||||||
|
|
||||||
|
/* Stock status colors for day blocks */
|
||||||
|
.day-block.stock-success { border-color: rgba(57, 217, 138, 0.3); }
|
||||||
|
.day-block.stock-success .day-divider { color: var(--success); opacity: 0.8; }
|
||||||
|
.day-block.stock-warning { border-color: rgba(252, 211, 77, 0.35); }
|
||||||
|
.day-block.stock-warning .day-divider { color: var(--warning); opacity: 0.8; }
|
||||||
|
.day-block.stock-danger { border-color: rgba(255, 94, 94, 0.35); }
|
||||||
|
.day-block.stock-danger .day-divider { color: var(--danger); opacity: 0.8; }
|
||||||
|
|
||||||
.day-divider {
|
.day-divider {
|
||||||
margin: 0 0 0.75rem;
|
margin: 0 0 0.75rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
@@ -2182,6 +2191,10 @@ textarea {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.med-detail-value.success-text { color: var(--success); }
|
||||||
|
.med-detail-value.warning-text { color: var(--warning); }
|
||||||
|
.med-detail-value.danger-text { color: var(--danger); }
|
||||||
|
|
||||||
.med-detail-schedules {
|
.med-detail-schedules {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user