Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/call/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ export default function CallDetail() {

{/* Tabs */}
<Box className="mx-4 mt-3 flex-1 overflow-hidden rounded-xl bg-white pb-8 shadow-xs dark:bg-gray-800">
<SharedTabs tabs={renderTabs()} variant="underlined" size={isLandscape ? 'md' : 'sm'} showOverflowIndicators />
<SharedTabs tabs={renderTabs()} variant="underlined" size={isLandscape ? 'lg' : 'md'} tabClassName="min-h-11" showOverflowIndicators />
</Box>
</ScrollView>
<CallNotesModal isOpen={isNotesModalOpen} onClose={() => setIsNotesModalOpen(false)} callId={callId} />
Expand Down
16 changes: 8 additions & 8 deletions src/app/weather-alert/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default function WeatherAlertDetail() {
}, [alertId, fetchAlertDetail]);

const severityColor = useMemo(() => (alert ? getSeverityColor(alert.Severity) : '#9E9E9E'), [alert]);
const CategoryIcon = useMemo(() => (alert ? getCategoryIcon(alert.Category) : null), [alert]);
const CategoryIcon = useMemo(() => (alert ? getCategoryIcon(alert.AlertCategory) : null), [alert]);

const formatDate = (dateStr: string) => {
if (!dateStr) return t('call_detail.not_available');
Expand Down Expand Up @@ -90,19 +90,19 @@ export default function WeatherAlertDetail() {
{alert.EffectiveUtc ? (
<HStack className="justify-between">
<Text className="text-sm text-gray-500 dark:text-gray-400">{t('weather_alerts.detail.effective')}</Text>
<Text className="text-sm font-medium text-gray-900 dark:text-gray-100">{formatDate(alert.EffectiveUtc)}</Text>
<Text className="text-sm font-medium text-gray-900 dark:text-gray-100">{formatDate(alert.EffectiveOnUtc || alert.EffectiveUtc)}</Text>
</HStack>
) : null}
{alert.OnsetUtc ? (
<HStack className="justify-between">
<Text className="text-sm text-gray-500 dark:text-gray-400">{t('weather_alerts.detail.onset')}</Text>
<Text className="text-sm font-medium text-gray-900 dark:text-gray-100">{formatDate(alert.OnsetUtc)}</Text>
<Text className="text-sm font-medium text-gray-900 dark:text-gray-100">{formatDate(alert.OnsetOnUtc || alert.OnsetUtc)}</Text>
</HStack>
) : null}
{alert.ExpiresUtc ? (
<HStack className="justify-between">
<Text className="text-sm text-gray-500 dark:text-gray-400">{t('weather_alerts.detail.expires')}</Text>
<Text className="text-sm font-medium text-gray-900 dark:text-gray-100">{formatDate(alert.ExpiresUtc)}</Text>
<Text className="text-sm font-medium text-gray-900 dark:text-gray-100">{formatDate(alert.ExpiresOnUtc || alert.ExpiresUtc)}</Text>
</HStack>
) : null}
</VStack>
Expand All @@ -125,10 +125,10 @@ export default function WeatherAlertDetail() {
) : null}

{/* Instructions */}
{alert.Instructions ? (
{alert.Instruction ? (
<Box style={{ borderLeftWidth: 3, borderLeftColor: severityColor }} className="rounded-lg bg-amber-50 p-3 dark:bg-amber-900/20">
<Text className="mb-1 text-sm font-semibold text-gray-700 dark:text-gray-300">{t('weather_alerts.detail.instructions')}</Text>
<Text className="text-sm text-gray-700 dark:text-gray-300">{alert.Instructions}</Text>
<Text className="text-sm text-gray-700 dark:text-gray-300">{alert.Instruction}</Text>
</Box>
) : null}

Expand All @@ -143,10 +143,10 @@ export default function WeatherAlertDetail() {
{/* Metadata */}
<Box className="rounded-lg bg-gray-100 p-3 dark:bg-gray-800">
<VStack space="sm">
{alert.SenderName ? (
{alert.Sender ? (
<HStack className="justify-between">
<Text className="text-sm text-gray-500 dark:text-gray-400">{t('weather_alerts.detail.sender')}</Text>
<Text className="text-sm font-medium text-gray-900 dark:text-gray-100">{alert.SenderName}</Text>
<Text className="text-sm font-medium text-gray-900 dark:text-gray-100">{alert.Sender}</Text>
</HStack>
) : null}
<HStack className="justify-between">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,27 @@ const createMockAlert = (overrides = {}) => ({
Event: 'Tornado Warning',
Headline: 'Tornado Warning for County',
Description: '',
Instructions: '',
Instruction: '',
Severity: 0,
Category: 0,
AlertCategory: 0,
Urgency: 0,
Certainty: 0,
Status: 0,
SourceType: 0,
SourceAlertId: '',
SenderName: '',
Sender: '',
AreaDescription: '',
Polygon: '',
CenterGeoLocation: '',
EffectiveUtc: '',
OnsetUtc: '',
ExpiresUtc: '',
Ends: '',
ReceivedOnUtc: '',
UpdatedOnUtc: '',
WebUrl: '',
ZoneCode: '',
MessageType: '',
WeatherAlertSourceId: '',
ExternalId: '',
Geocodes: '',
SentUtc: '',
FirstSeenUtc: '',
LastUpdatedUtc: '',
ReferencesExternalId: '',
NotificationSent: false,
...overrides,
});

Expand Down
22 changes: 11 additions & 11 deletions src/components/weather-alerts/__tests__/weather-alert-card.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,27 @@ const createMockAlert = (overrides = {}) => ({
Event: 'Tornado Warning',
Headline: 'Tornado Warning for County',
Description: 'A tornado has been spotted.',
Instructions: 'Take shelter.',
Instruction: 'Take shelter.',
Severity: 1,
Category: 0,
AlertCategory: 0,
Urgency: 0,
Certainty: 0,
Status: 0,
SourceType: 0,
SourceAlertId: '',
SenderName: 'NWS',
Sender: 'NWS',
AreaDescription: 'County A',
Polygon: '',
CenterGeoLocation: '',
EffectiveUtc: '2026-04-15T10:00:00Z',
OnsetUtc: '',
ExpiresUtc: '2026-04-15T14:00:00Z',
Ends: '',
ReceivedOnUtc: '',
UpdatedOnUtc: '',
WebUrl: '',
ZoneCode: '',
MessageType: '',
WeatherAlertSourceId: '',
ExternalId: '',
Geocodes: '',
SentUtc: '',
FirstSeenUtc: '',
LastUpdatedUtc: '',
ReferencesExternalId: '',
NotificationSent: false,
...overrides,
});

Expand Down
8 changes: 5 additions & 3 deletions src/components/weather-alerts/weather-alert-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ interface WeatherAlertCardProps {
const WeatherAlertCardComponent: React.FC<WeatherAlertCardProps> = ({ alert }) => {
const { t } = useTranslation();
const severityColor = getSeverityColor(alert.Severity);
const CategoryIcon = getCategoryIcon(alert.Category);
const effectiveDate = parseWeatherAlertDate(alert.EffectiveUtc);
const expiresDate = parseWeatherAlertDate(alert.ExpiresUtc);
const CategoryIcon = getCategoryIcon(alert.AlertCategory);
// Prefer the real UTC instants; the *Utc strings are department-local display values that
// read hours off when treated as timestamps. Older servers only send the display strings.
const effectiveDate = parseWeatherAlertDate(alert.EffectiveOnUtc || alert.EffectiveUtc);
const expiresDate = parseWeatherAlertDate(alert.ExpiresOnUtc || alert.ExpiresUtc);

return (
<Box style={{ borderLeftWidth: 4, borderLeftColor: severityColor }} className="mb-2 rounded-xl bg-white p-3 shadow-xs dark:bg-gray-800">
Expand Down
8 changes: 4 additions & 4 deletions src/lib/weather-alert-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,16 @@ export const parseWeatherAlertDate = (dateStr: string): Date | null => {
export const sortAlertsBySeverity = (alerts: WeatherAlertResultData[]): WeatherAlertResultData[] => {
return [...alerts].sort((a, b) => {
if (a.Severity !== b.Severity) return a.Severity - b.Severity;
const bEffectiveTime = parseWeatherAlertDate(b.EffectiveUtc)?.getTime() ?? 0;
const aEffectiveTime = parseWeatherAlertDate(a.EffectiveUtc)?.getTime() ?? 0;
const bEffectiveTime = parseWeatherAlertDate(b.EffectiveOnUtc || b.EffectiveUtc)?.getTime() ?? 0;
const aEffectiveTime = parseWeatherAlertDate(a.EffectiveOnUtc || a.EffectiveUtc)?.getTime() ?? 0;
return bEffectiveTime - aEffectiveTime;
});
};

export const isAlertActive = (alert: WeatherAlertResultData): boolean => {
if (alert.Status !== WeatherAlertStatus.Active) return false;
if (alert.ExpiresUtc) {
const expiration = parseWeatherAlertDate(alert.ExpiresUtc);
if (alert.ExpiresOnUtc || alert.ExpiresUtc) {
const expiration = parseWeatherAlertDate(alert.ExpiresOnUtc || alert.ExpiresUtc);
return expiration ? expiration.getTime() > Date.now() : true;
}
return true;
Expand Down
41 changes: 25 additions & 16 deletions src/models/v4/weatherAlerts/weatherAlertResultData.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
// Mirrors Resgrid Core's v4 WeatherAlertResultData (Web/Resgrid.Web.Services/Models/v4/WeatherAlerts).
// Enum-valued fields are ints (see WeatherAlertSeverity/Category/Urgency/Certainty/Status in Core).
export class WeatherAlertResultData {
public WeatherAlertId: string = '';
public DepartmentId: number = 0;
public WeatherAlertSourceId: string = '';
public ExternalId: string = '';
public Sender: string = '';
public Event: string = '';
public AlertCategory: number = 4; // Other
public Severity: number = 4; // Unknown
public Urgency: number = 4; // Unknown
public Certainty: number = 4; // Unknown
public Status: number = 0; // Active
public Headline: string = '';
public Description: string = '';
public Instructions: string = '';
public Severity: number = 4;
public Category: number = 4;
public Urgency: number = 4;
public Certainty: number = 4;
public Status: number = 0;
public SourceType: number = 0;
public SourceAlertId: string = '';
public SenderName: string = '';
public Instruction: string = '';
public AreaDescription: string = '';
public Polygon: string = '';
public Geocodes: string = '';
public CenterGeoLocation: string = '';
public EffectiveUtc: string = '';
// Department-local display strings (despite the Utc names) — render verbatim only.
public OnsetUtc: string = '';
public ExpiresUtc: string = '';
public Ends: string = '';
public ReceivedOnUtc: string = '';
public UpdatedOnUtc: string = '';
public WebUrl: string = '';
public ZoneCode: string = '';
public MessageType: string = '';
public EffectiveUtc: string = '';
public SentUtc: string = '';
public FirstSeenUtc: string = '';
public LastUpdatedUtc: string = '';
// Real UTC instants with explicit "Z" — use these for any date math. Empty on older servers.
public EffectiveOnUtc?: string = '';
public OnsetOnUtc?: string = '';
public ExpiresOnUtc?: string = '';
public SentOnUtc?: string = '';
public ReferencesExternalId: string = '';
public NotificationSent: boolean = false;
public SystemMessageId?: number;
}
22 changes: 11 additions & 11 deletions src/stores/weather-alerts/__tests__/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,27 @@ const createMockAlert = (overrides = {}) => ({
Event: 'Tornado Warning',
Headline: 'Tornado Warning for County',
Description: 'A tornado has been spotted.',
Instructions: 'Take shelter immediately.',
Instruction: 'Take shelter immediately.',
Severity: 0,
Category: 0,
AlertCategory: 0,
Urgency: 0,
Certainty: 0,
Status: 0,
SourceType: 0,
SourceAlertId: 'nws-1',
SenderName: 'NWS',
Sender: 'NWS',
AreaDescription: 'County A',
Polygon: '',
CenterGeoLocation: '35.0,-97.0',
EffectiveUtc: '2026-04-15T10:00:00Z',
OnsetUtc: '2026-04-15T10:00:00Z',
ExpiresUtc: '2026-04-15T14:00:00Z',
Ends: '',
ReceivedOnUtc: '2026-04-15T10:00:00Z',
UpdatedOnUtc: '',
WebUrl: '',
ZoneCode: 'OKC001',
MessageType: 'Alert',
WeatherAlertSourceId: '',
ExternalId: '',
Geocodes: '',
SentUtc: '',
FirstSeenUtc: '',
LastUpdatedUtc: '',
ReferencesExternalId: '',
NotificationSent: false,
...overrides,
});

Expand Down
Loading