eletrotupi / tcc/ commit / 9897f03

frontend: drop redundant casts now that icons and categories are typed

Pedro Lucas Porcellis porcellis@eletrotupi.com · 12 days ago 9897f0373a757f09dbe54e9fae5842f71c99703d Browse files
Parents 2bd9b67
Changed files
12
Additions
+33
Deletions
-29
frontend/app/(tabs)/history.tsx
1 1
import { useState } from "react";
2 2
import { ScrollView, View } from "react-native";
3 3
import { ScreenLayout } from "@/components/ui/ScreenLayout";
4 4
import { Section, SectionHeader } from "@/components/ui/Sections";
5 5
import { FilterPills } from "@/components/ui/FilterPills";
15 15
import { EmptyState } from "@/components/ui/EmptyState";
16 16

17 17
function HistoryCardRenderer({ card }: { card: HistoryCard }) {
18 18
  switch (card.category) {
19 19
    case "mood":
20 -
      return <MoodHistoryCard card={card as any} />;
20 +
      return <MoodHistoryCard card={card} />;
21 21
    case "sleep":
22 -
      return <SleepRecordCard card={card as any} />;
22 +
      return <SleepRecordCard card={card} />;
23 23
    case "trigger":
24 -
      return <TriggerHistoryCard card={card as any} />;
24 +
      return <TriggerHistoryCard card={card} />;
25 25
    case "care_action":
26 26
      switch (card.subtype) {
27 27
        case "medicine":
28 -
          return <MedicineHistoryCard card={card as any} />;
28 +
          return <MedicineHistoryCard card={card} />;
29 29
        case "appointment":
30 -
          return <AppointmentHistoryCard card={card as any} />;
30 +
          return <AppointmentHistoryCard card={card} />;
31 31
        case "activity":
32 -
          return <ActivityHistoryCard card={card as any} />;
32 +
          return <ActivityHistoryCard card={card} />;
33 33
        default:
34 34
          return null;
35 35
      }
36 36
  }
37 37
}
72 72
        )}
73 73
      </ScrollView>
74 74
    </ScreenLayout>
75 75
  );
76 76
}
frontend/app/care-actions/[id].tsx
1 1
import { useState, useEffect } from "react";
2 2
import {
3 3
  StyleSheet,
4 4
  View,
5 5
  ScrollView,
60 60
  MONTHLY: "1× por mês",
61 61
};
62 62

63 63
// header meta
64 64
type HeaderMeta = {
65 -
  icon: string;
65 +
  icon: keyof typeof MaterialIcons.glyphMap;
66 66
  color: string;
67 67
  iconBg: string;
68 68
  title: string;
69 69
};
70 70

104 104
}
105 105

106 106
// DetailRow
107 107
// TODO: Might as well generalize this into a component
108 108
type DetailRowProps = {
109 -
  icon: string;
109 +
  icon: keyof typeof MaterialIcons.glyphMap;
110 110
  label: string;
111 111
  value: string;
112 112
};
113 113

114 114
function DetailRow({ icon, label, value }: DetailRowProps) {
115 115
  return (
116 116
    <Row style={styles.detailRow}>
117 117
      <View style={styles.detailIconWrap}>
118 -
        <MaterialIcons name={icon as any} size={18} color="#6366F1" />
118 +
        <MaterialIcons name={icon} size={18} color="#6366F1" />
119 119
      </View>
120 120
      <Col gap={1}>
121 121
        <Text style={styles.detailLabel}>{label}</Text>
122 122
        <Text style={styles.detailValue}>{value}</Text>
123 123
      </Col>
205 205
      >
206 206
        <Card style={{ padding: Spacing.cardGap }}>
207 207
          <Row style={{ alignItems: "center" }}>
208 208
            <View style={[styles.iconWrap, { backgroundColor: header.iconBg }]}>
209 209
              <MaterialIcons
210 -
                name={header.icon as any}
210 +
                name={header.icon}
211 211
                size={22}
212 212
                color={header.color}
213 213
              />
214 214
            </View>
215 215
            <Col gap={2}>
315 315
                              styles.linkIconCircle,
316 316
                              { backgroundColor: meta.iconBackground },
317 317
                            ]}
318 318
                          >
319 319
                            <MaterialIcons
320 -
                              name={meta.icon as any}
320 +
                              name={meta.icon}
321 321
                              size={20}
322 322
                              color={meta.color}
323 323
                            />
324 324
                          </View>
325 325
                        );
538 538
    color: Colors.light.textSecondary,
539 539
    fontStyle: "italic",
540 540
    padding: 4,
541 541
  },
542 542
});
frontend/app/entry/[id].tsx
1 1
import { useState, useEffect, use } from "react";
2 2
import {
3 3
  StyleSheet,
4 4
  View,
5 5
  ScrollView,
222 222
                      <View style={styles.impactBar}>
223 223
                        <View
224 224
                          style={[
225 225
                            styles.impactFill,
226 226
                            {
227 -
                              width: `${(link.perceivedImpact / 5) * 100}%` as any,
227 +
                              width: `${(link.perceivedImpact / 5) * 100}%`,
228 228
                              backgroundColor: impactColor(link.perceivedImpact),
229 229
                            },
230 230
                          ]}
231 231
                        />
232 232
                      </View>
440 440
    height: "100%",
441 441
    borderRadius: 99,
442 442
  },
443 443

444 444
});
frontend/components/history/ActivityHistoryCard.tsx
1 1
import { View, StyleSheet } from "react-native";
2 2
import { Text } from "@/components/ui/Text";
3 3
import { MaterialIcons } from "@expo/vector-icons";
4 4
import { Card } from "@/components/ui/Cards";
5 5
import type { HistoryCard } from "@/lib/history/types";
18 18
  });
19 19

20 20
  return (
21 21
    <Card
22 22
      style={styles.card}
23 -
      onPress={() => (router.push as any)(`/care-actions/${card.raw.id}`)}
23 +
      onPress={() => router.push(`/care-actions/${card.raw.id}`)}
24 24
    >
25 25
      <View style={styles.row}>
26 26
        <View style={[styles.iconWrap, { backgroundColor: meta.iconBackground }]}>
27 -
          <MaterialIcons name={meta.icon as any} size={22} color={meta.color} />
27 +
          <MaterialIcons name={meta.icon} size={22} color={meta.color} />
28 28
        </View>
29 29
        <View style={styles.body}>
30 30
          <View style={styles.titleRow}>
31 31
            <Text style={styles.title}>{card.title}</Text>
32 32
            <Text style={styles.time}>{time}</Text>
60 60
  },
61 61
  title: { fontSize: 15, fontWeight: "600" },
62 62
  time: { fontSize: 13, opacity: 0.45 },
63 63
  summary: { fontSize: 13, opacity: 0.6, lineHeight: 18 },
64 64
});
frontend/components/history/AppointmentHistoryCard.tsx
1 1
import { View, StyleSheet } from "react-native";
2 2
import { Text } from "@/components/ui/Text";
3 3
import { MaterialIcons } from "@expo/vector-icons";
4 4
import { Card } from "@/components/ui/Cards";
5 5
import { Badge } from "@/components/ui/Badge";
17 17
  });
18 18

19 19
  return (
20 20
    <Card
21 21
      style={styles.card}
22 -
      onPress={() => (router.push as any)(`/care-actions/${card.raw.id}`)}
22 +
      onPress={() => router.push(`/care-actions/${card.raw.id}`)}
23 23
    >
24 24
      <View style={styles.row}>
25 25
        <View style={[styles.iconWrap, styles.iconBg]}>
26 26
          <MaterialIcons name="psychology" size={22} color="#1D4ED8" />
27 27
        </View>
36 36
            </Text>
37 37
          ) : null}
38 38
          {card.badge ? (
39 39
            <Badge
40 40
              label={card.badge.label}
41 -
              variant={card.badge.variant as any}
41 +
              variant={card.badge.variant}
42 42
              style={styles.badge}
43 43
            />
44 44
          ) : null}
45 45
        </View>
46 46
      </View>
70 70
  title: { fontSize: 15, fontWeight: "400" },
71 71
  time: { fontSize: 13, opacity: 0.45 },
72 72
  summary: { fontSize: 13, opacity: 0.6, lineHeight: 18, marginBottom: 6 },
73 73
  badge: { alignSelf: "flex-start", marginTop: 4 },
74 74
});
frontend/components/history/MedicineHistoryCard.tsx
1 1
import { View, StyleSheet } from "react-native";
2 2
import { Text } from "@/components/ui/Text";
3 3
import { MaterialIcons } from "@expo/vector-icons";
4 4
import { Card } from "@/components/ui/Cards";
5 5
import type { HistoryCard } from "@/lib/history/types";
16 16
  });
17 17

18 18
  return (
19 19
    <Card
20 20
      style={styles.card}
21 -
      onPress={() => (router.push as any)(`/care-actions/${card.raw.id}`)}
21 +
      onPress={() => router.push(`/care-actions/${card.raw.id}`)}
22 22
    >
23 23
      <View style={styles.row}>
24 24
        <View style={[styles.iconWrap, styles.iconBg]}>
25 25
          <MaterialIcons name="medication" size={22} color="#7C3AED" />
26 26
        </View>
59 59
  },
60 60
  title: { fontSize: 15, fontWeight: "600" },
61 61
  time: { fontSize: 13, opacity: 0.45 },
62 62
  summary: { fontSize: 13, opacity: 0.6, lineHeight: 18 },
63 63
});
frontend/components/history/TriggerCard.tsx
1 1
import { View, StyleSheet } from "react-native";
2 2
import { Text } from '@/components/ui/Text';
3 3
import { MaterialIcons } from "@expo/vector-icons";
4 4
import { Card } from "@/components/ui/Cards";
5 5
import { Badge } from "@/components/ui/Badge";
34 34
              {card.raw.comment}
35 35
            </Text>
36 36
          ) : null}
37 37
          <Badge
38 38
            label={meta.label}
39 -
            variant={meta.variant as any}
39 +
            variant={meta.variant}
40 40
            style={styles.badge}
41 41
          />
42 42
        </View>
43 43
      </View>
44 44
    </Card>
68 68
  title: { fontSize: 15, fontWeight: "600" },
69 69
  time: { fontSize: 13, opacity: 0.45 },
70 70
  summary: { fontSize: 13, opacity: 0.6, lineHeight: 18, marginBottom: 6 },
71 71
  badge: { alignSelf: "flex-start", marginTop: 4 },
72 72
});
frontend/components/ui/CategoryChips.tsx
1 1
import {
2 2
  TouchableOpacity,
3 3
  View,
4 4
  StyleSheet,
5 5
} from 'react-native';
34 34
            key={option.value}
35 35
            onPress={() => onChange(option.value)}
36 36
            activeOpacity={0.7}
37 37
            style={[
38 38
              styles.chip,
39 -
              { width: `${100 / columns - 2}%` as any },
39 +
              { width: `${100 / columns - 2}%` },
40 40
              isActive && styles.chipActive,
41 41
            ]}
42 42
          >
43 43
            <View style={styles.chipInner}>
44 44
              <View style={styles.iconWrapper}>{option.icon}</View>
92 92
  },
93 93
  labelActive: {
94 94
    fontWeight: '600',
95 95
  },
96 96
});
frontend/constants/care-action-categories.ts
1 +
import { MaterialIcons } from "@expo/vector-icons";
1 2
import type { CareAction } from "@/lib/api/types";
2 3

3 4
export type CareActionMeta = {
4 -
  icon: string;
5 +
  icon: keyof typeof MaterialIcons.glyphMap;
5 6
  color: string;
6 7
  iconBackground: string;
7 8
  label: string;
8 9
};
9 10

50 51
      return getActivityCategory(ca.activity?.type);
51 52
    default:
52 53
      return { icon: 'star', color: '#6B7280', iconBackground: '#F3F4F6', label: 'Cuidado' };
53 54
  }
54 55
}
frontend/context/AuthContext.tsx
1 1
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
2 2
import { apiClient, User } from "@/lib/api";
3 3
import { queryClient, storage } from "@/lib/queryClient";
4 4

5 5
interface AuthProviderProps {
25 25
  resetPassword: (token: string, password: string) => Promise<void>;
26 26
  activate: (code: string) => Promise<void>;
27 27
  requestActivateCode: () => Promise<void>;
28 28
}
29 29

30 -
export function sanitizeUser(data: any): User {
30 +
export function sanitizeUser(data: User): User {
31 31
  return {
32 32
    id: data.id,
33 33
    email: data.email,
34 34
    firstName: data.firstName,
35 35
    lastName: data.lastName,
168 168
    [user, isLoading, isAuthenticated],
169 169
  );
170 170

171 171
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
172 172
};
frontend/hooks/useNotificationObserver.ts
1 1
import { useEffect, useRef } from "react";
2 2
import * as Notifications from "expo-notifications";
3 -
import { useRouter, useRootNavigationState } from "expo-router";
3 +
import { useRouter, useRootNavigationState, type Href } from "expo-router";
4 4

5 5
Notifications.setNotificationHandler({
6 6
  handleNotification: async () => ({
7 7
    shouldPlaySound: true,
8 8
    shouldSetBadge: false,
37 37

38 38
      if (lastHandledId.current === id) return;
39 39
      lastHandledId.current = id;
40 40

41 41
      // XXX: Looks so dumb, actually
42 -
      const { screen, ...params } = (data ?? {}) as Record<string, any>;
42 +
      const { screen, ...params } = (data ?? {}) as {
43 +
        screen?: string;
44 +
        [key: string]: string | undefined;
45 +
      };
43 46

44 47
      if (!screen) return;
45 48

46 49
      const payload = { body, title, ...params };
47 50

48 51
      if (coldLaunch) {
49 52
        router.replace({
50 -
          pathname: screen as any,
53 +
          pathname: screen,
51 54
          params: payload,
52 -
        });
55 +
        } as Href);
53 56
      } else {
54 57
        router.push({
55 -
          pathname: screen as any,
58 +
          pathname: screen,
56 59
          params: payload,
57 -
        });
60 +
        } as Href);
58 61
      }
59 62
    }
60 63

61 64
    // Cold launch
62 65
    Notifications.getLastNotificationResponseAsync().then((response) => {
77 80
      isMounted = false;
78 81
      subscription.remove();
79 82
    };
80 83
  }, [routerReady, router]);
81 84
}
frontend/hooks/useUserPreferences.queries.ts
1 1
import { useMutation } from "@tanstack/react-query";
2 -
import { apiClient, UserPreferencesPayload } from "@/lib/api";
2 +
import { apiClient, User, UserPreferencesPayload } from "@/lib/api";
3 3

4 -
export const usePatchUserMe = (onSuccess?: (user: any) => void) => {
4 +
export const usePatchUserMe = (onSuccess?: (user: User) => void) => {
5 5
  return useMutation({
6 6
    mutationFn: (payload: UserPreferencesPayload) => apiClient.patchUserMe(payload),
7 7
    onSuccess: (data) => {
8 8
      onSuccess?.(data.user);
9 9
    },
10 10
  });
11 11
};