eletrotupi / tcc/ commit / 2bd9b67

frontend: align the badge vocabulary and type the variant prop

Pedro Lucas Porcellis porcellis@eletrotupi.com · 12 days ago 2bd9b6733f955a443d284beecfa664749fcb3900 Browse files
Parents 98f51d9
Changed files
7
Additions
+43
Deletions
-26
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,
131 131
                </Text>
132 132
                <Text style={styles.moodTime}>{formatMoment(moment)}</Text>
133 133
              </Col>
134 134
            </Row>
135 135
            {/* If this is the latest mood entry, show the active badge */}
136 -
            <Badge
137 -
              label="Registro Ativo"
138 -
              backgroundColor={Theme.colors.activeBackground}
139 -
              color={Theme.colors.tint}
140 -
            />
136 +
            <Badge label="Registro Ativo" variant="green" />
141 137
          </Between>
142 138
        </Card>
143 139

144 140
        {/* Core levels */}
145 141
        <Section>
237 233
                    </Card>
238 234
                  </Pressable>
239 235
                );
240 236
              })}
241 237

242 -
              {entry.careActions.map((ca) => {
238 +
              {(entry.careActions ?? []).map((ca) => {
243 239
                const meta = getCareActionMeta(ca);
244 240

245 241
                return (
246 242
                  <Pressable
247 243
                    key={ca.id}
248 -
                    onPress={() => (router.push as any)(`/care-actions/${ca.id}`)}
244 +
                    onPress={() => router.push(`/care-actions/${ca.id}`)}
249 245
                    style={({ pressed }) => [pressed && { opacity: 0.7 }]}
250 246
                  >
251 247
                    <Card style={styles.linkCard}>
252 248
                      <Row style={{ alignItems: 'center', gap: 12 }}>
253 249
                        <View style={[styles.linkIconCircle, { backgroundColor: meta.iconBackground }]}>
254 -
                          <MaterialIcons name={meta.icon as any} size={22} color={meta.color} />
250 +
                          <MaterialIcons name={meta.icon} size={22} color={meta.color} />
255 251
                        </View>
256 252
                        <Col gap={3} style={{ flex: 1 }}>
257 253
                          <Row style={{ alignItems: 'center', gap: 6 }}>
258 254
                            <Text style={styles.linkLabel}>{meta.label}</Text>
259 -
                            <Badge label="Consulta" variant="info" style={undefined} />
255 +
                            <Badge label="Consulta" variant="blue" style={undefined} />
260 256
                          </Row>
261 257
                          <Text style={styles.linkTime}>
262 258
                            {formatMoment(new Date(ca.moment))}
263 259
                          </Text>
264 260
                        </Col>
444 440
    height: "100%",
445 441
    borderRadius: 99,
446 442
  },
447 443

448 444
});
frontend/app/triggers/[id].tsx
1 1
import { useState, useEffect, use } from "react";
2 2
import {
3 3
  StyleSheet,
4 4
  View,
5 5
  ScrollView,
220 220
                    </Card>
221 221
                  </Pressable>
222 222
                );
223 223
              })}
224 224

225 -
              {trigger.careActions.map((ca) => {
225 +
              {(trigger.careActions ?? []).map((ca) => {
226 226
                const meta = getCareActionMeta(ca);
227 227
                return (
228 228
                  <Pressable
229 229
                    key={ca.id}
230 -
                    onPress={() => (router.push as any)(`/care-actions/${ca.id}`)}
230 +
                    onPress={() => router.push(`/care-actions/${ca.id}`)}
231 231
                    style={({ pressed }) => [pressed && { opacity: 0.7 }]}
232 232
                  >
233 233
                    <Card style={styles.linkCard}>
234 234
                      <Row style={{ alignItems: 'center', gap: 12 }}>
235 235
                        <View style={[styles.linkIconCircle, { backgroundColor: meta.iconBackground }]}>
236 -
                          <MaterialIcons name={meta.icon as any} size={22} color={meta.color} />
236 +
                          <MaterialIcons name={meta.icon} size={22} color={meta.color} />
237 237
                        </View>
238 238
                        <Col gap={3} style={{ flex: 1 }}>
239 239
                          <Row style={{ alignItems: 'center', gap: 6 }}>
240 240
                            <Text style={styles.linkLabel}>{meta.label}</Text>
241 -
                            <Badge label="Consulta" variant="info" style={undefined} />
241 +
                            <Badge label="Consulta" variant="blue" style={undefined} />
242 242
                          </Row>
243 243
                          <Text style={styles.linkTime}>
244 244
                            {formatMoment(new Date(ca.moment))}
245 245
                          </Text>
246 246
                        </Col>
435 435
  impactFill: {
436 436
    height: "100%",
437 437
    borderRadius: 99,
438 438
  },
439 439
});
frontend/components/ui/Badge.tsx
1 1
import {
2 2
  StyleSheet,
3 -
  View
3 +
  View,
4 +
  type StyleProp,
5 +
  type ViewStyle,
4 6
} from 'react-native';
5 7
import { Text } from '@/components/ui/Text';
6 8

7 9
// Badge color palettes
8 10
export const badgeVariants = {
12 14
  red:    { bg: "#fee2e2", text: "#991b1b", dot: "#ef4444" },
13 15
  purple: { bg: "#ede9fe", text: "#5b21b6", dot: "#8b5cf6" },
14 16
  gray:   { bg: "#f1f5f9", text: "#475569", dot: "#94a3b8" },
15 17
  orange: { bg: "#ffedd5", text: "#9a3412", dot: "#f97316" },
16 18
  pink:   { bg: "#fce7f3", text: "#9d174d", dot: "#ec4899" },
19 +
  indigo: { bg: "#e0e7ff", text: "#3730a3", dot: "#6366f1" },
20 +
};
21 +

22 +
export type BadgeVariant = keyof typeof badgeVariants;
23 +

24 +
type BadgeProps = {
25 +
  label: string;
26 +
  variant?: BadgeVariant;
27 +
  style?: StyleProp<ViewStyle>;
17 28
};
18 29

19 30
// TODO: Add a subtle variant and allow to drop the dot as well
20 31
// TODO: Experiment with a less round-ish version?
21 -
export const Badge = ({ label, variant = "green", style }) => {
32 +
export const Badge = ({ label, variant = "green", style }: BadgeProps) => {
22 33
  const palette = badgeVariants[variant] ?? badgeVariants.green;
23 34

24 35
  const styles = StyleSheet.create({
25 36
    badge: {
26 37
      flexDirection: "row",
48 59
      <View style={[styles.badgeDot, { backgroundColor: palette.dot }]} />
49 60
      <Text style={[styles.badgeText, { color: palette.text }]}>{label}</Text>
50 61
    </View>
51 62
  );
52 63
}
frontend/constants/trigger-categories.ts
1 1
import { MaterialIcons } from "@expo/vector-icons";
2 2
import { TriggerCategory } from "@/constants/triggers";
3 +
import type { BadgeVariant } from "@/components/ui/Badge";
3 4

4 5
export interface TriggerCategoryDefinition {
5 6
  id: TriggerCategory;
6 7
  label: string;
7 8
  icon: keyof typeof MaterialIcons.glyphMap;
8 9
  color: string;
9 10
  iconBackground: string;
10 -
  variant: string;
11 +
  variant: BadgeVariant;
11 12
}
12 13

13 14
export const TRIGGER_CATEGORY_DEFINITIONS: TriggerCategoryDefinition[] = [
14 15
  { id: "WORK",     label: "Trabalho", icon: "work",            color: "#3b82f6", iconBackground: "#dbeafe", variant: "blue" },
15 16
  { id: "SOCIAL",   label: "Social",   icon: "diversity-1",     color: "#8b5cf6", iconBackground: "#ede9fe", variant: "purple" },
22 23
];
23 24

24 25
export const getTriggerCategory = (id: string): TriggerCategoryDefinition =>
25 26
  TRIGGER_CATEGORY_DEFINITIONS.find((c) => c.id === id) ??
26 27
  TRIGGER_CATEGORY_DEFINITIONS.find((c) => c.id === "OTHER")!;
frontend/lib/history/mappers.ts
1 1
import type { HistoryCard } from "@/lib/history/types";
2 2
import type { MoodEntry, SleepRecord, Trigger, CareAction } from "@/lib/api";
3 3
import { getMood } from "@/constants/moods";
4 4

5 5

25 25
}
26 26

27 27
// Sleep Records
28 28

29 29
function sleepQualityBadge(hours: number): HistoryCard["badge"] {
30 -
  if (hours >= 7) return { label: "Excelente", variant: "success" };
31 -
  if (hours >= 5) return { label: "Regular", variant: "warning" };
30 +
  if (hours >= 7) return { label: "Excelente", variant: "green" };
31 +
  if (hours >= 5) return { label: "Regular", variant: "yellow" };
32 32

33 33
  return {
34 34
    label: "Ruim",
35 -
    variant: "danger",
35 +
    variant: "red",
36 36
  };
37 37
}
38 38

39 39
export function mapSleepToHistoryCard(record: SleepRecord): HistoryCard {
40 40
  console.log("mapping...", record)
127 127
        summary: parts.join(" · ") || "Sem anotações",
128 128
        badge:
129 129
          linkedCount > 0
130 130
            ? {
131 131
                label: `${linkedCount} vínculo${linkedCount > 1 ? "s" : ""}`,
132 -
                variant: "info",
132 +
                variant: "blue",
133 133
              }
134 134
            : undefined,
135 135
        raw: record,
136 136
      };
137 137
    }
160 160
        summary: "",
161 161
        raw: record,
162 162
      };
163 163
  }
164 164
}
frontend/lib/history/types.ts
1 1
import type { MoodEntry, SleepRecord, Trigger, CareAction } from "@/lib/api";
2 +
import type { BadgeVariant } from "@/components/ui/Badge";
2 3

3 4
export type HistoryCategory = "mood" | "sleep" | "trigger" | "care_action";
4 5

5 6
export type CareActionSubtype = "medicine" | "appointment" | "activity";
6 7

7 8
export type HistoryBadge = {
8 9
  label: string;
9 -
  variant: "neutral" | "warning" | "success" | "danger" | "info";
10 +
  variant: BadgeVariant;
10 11
};
11 12

12 -
export type HistoryCard = {
13 +
type HistoryCardBase = {
13 14
  id: string; // "<category>-<id>" to avoid collisions
14 -
  category: HistoryCategory;
15 -
  subtype?: CareActionSubtype; // only set when category === "care_action"
16 15
  timestamp: string; // ISO string — used for sorting and display
17 16
  title: string;
18 17
  summary: string;
19 18
  badge?: HistoryBadge;
20 -
  raw: MoodEntry | SleepRecord | Trigger | CareAction;
21 19
};
20 +

21 +
export type HistoryCard =
22 +
  | (HistoryCardBase & { category: "mood"; raw: MoodEntry })
23 +
  | (HistoryCardBase & { category: "sleep"; raw: SleepRecord })
24 +
  | (HistoryCardBase & { category: "trigger"; raw: Trigger })
25 +
  | (HistoryCardBase & {
26 +
      category: "care_action";
27 +
      subtype?: CareActionSubtype; // only set when category === "care_action"
28 +
      raw: CareAction;
29 +
    });
frontend/lib/utils/time.ts
1 1
import { format, isToday, isYesterday } from 'date-fns';
2 2
import { ptBR } from 'date-fns/locale';
3 +
import type { BadgeVariant } from '@/components/ui/Badge';
3 4

4 5
type TimeBadgeVariant = 'morning' | 'afternoon' | 'night' | 'latenight';
5 6

6 -
export const TIME_BADGES: Record<TimeBadgeVariant, { label: string; variant: string; }> = {
7 +
export const TIME_BADGES: Record<TimeBadgeVariant, { label: string; variant: BadgeVariant; }> = {
7 8
  morning:   { label: 'Manhã',     variant: "green" },
8 9
  afternoon: { label: 'Tarde',     variant: "orange" },
9 10
  night:     { label: 'Noite',     variant: "blue" },
10 11
  latenight: { label: 'Madrugada', variant: "purple" },
11 12
};
39 40
// to prevent timezone offsets from shifting the calendar day
40 41
export const parseDateOnly = (dateStr: string | Date): Date => {
41 42
  const iso = typeof dateStr === 'string' ? dateStr : dateStr.toISOString();
42 43
  return new Date(`${iso.slice(0, 10)}T12:00:00`);
43 44
};