eletrotupi / tcc/ commit / d8d5424

frontend: introduce new care actions screens

Pedro Lucas Porcellis porcellis@eletrotupi.com · 4 months ago d8d5424a8ec32b325e17aa210b8b18da460390d4 Browse files
Parents fc5b05d
Changed files
6
Additions
+936
Deletions
0
/dev/null → frontend/app/care-actions/activity.tsx
1 +
import {
2 +
  View,
3 +
  Text,
4 +
  StyleSheet,
5 +
  KeyboardAvoidingView,
6 +
  ScrollView,
7 +
} from "react-native";
8 +
import { useState } from "react";
9 +
import { router } from "expo-router";
10 +

11 +
import { Card } from "@/components/ui/Cards";
12 +
import { Input } from "@/components/ui/Input";
13 +
import { Button } from "@/components/ui/Button";
14 +
import { Section, SectionHeader } from "@/components/ui/Sections";
15 +
import { CategoryChips, CategoryOption } from "@/components/ui/CategoryChips";
16 +
import { Spacing, Typography, Colors, Shadows } from "@/constants/theme";
17 +
import { Ionicons } from "@expo/vector-icons";
18 +
import { useCreateCareAction } from "@/hooks";
19 +
import { ActivityType } from "@/lib/api";
20 +

21 +
const ACTIVITY_TYPES: CategoryOption<ActivityType>[] = [
22 +
  { label: 'Caminhada', value: 'WALK', icon: <Ionicons name="walk-outline" size={16} color={Colors.light.tint} /> },
23 +
  { label: 'Yoga', value: 'YOGA', icon: <Ionicons name="body-outline" size={16} color={Colors.light.tint} /> },
24 +
  { label: 'Academia', value: 'GYM', icon: <Ionicons name="barbell-outline" size={16} color={Colors.light.tint} /> },
25 +
  { label: 'Meditação', value: 'MEDITATION', icon: <Ionicons name="leaf-outline" size={16} color={Colors.light.tint} /> },
26 +
  { label: 'Social', value: 'SOCIAL', icon: <Ionicons name="people-outline" size={16} color={Colors.light.tint} /> },
27 +
  { label: 'Criativo', value: 'CREATIVE', icon: <Ionicons name="color-palette-outline" size={16} color={Colors.light.tint} /> },
28 +
  { label: 'Outro', value: 'OTHER', icon: <Ionicons name="help-outline" size={16} color={Colors.light.tint} /> },
29 +
];
30 +

31 +
export default function ActivityCareAction() {
32 +
  const [activityType, setActivityType] = useState<ActivityType>('WALK');
33 +
  const [activityDuration, setActivityDuration] = useState('');
34 +

35 +
  const createCareAction = useCreateCareAction();
36 +

37 +
  const handleSave = async () => {
38 +
    await createCareAction.mutateAsync({
39 +
      type: 'ACTIVITY',
40 +
      moment: new Date(),
41 +
      activity: {
42 +
        type: activityType,
43 +
        duration: activityDuration ? Number(activityDuration) : undefined,
44 +
      },
45 +
    });
46 +
    router.replace('/(tabs)/actions');
47 +
  };
48 +

49 +
  return (
50 +
    <KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
51 +
      <ScrollView scrollEventThrottle={16}>
52 +
        <View style={styles.container}>
53 +
          <View style={styles.header}>
54 +
            <Text style={styles.headerTitle}>Registrar atividade</Text>
55 +
            <Text style={styles.headerSubtitle}>
56 +
              Qual atividade você realizou?
57 +
            </Text>
58 +
          </View>
59 +

60 +
          <Section>
61 +
            <SectionHeader title="Tipo de atividade" />
62 +
            <CategoryChips
63 +
              options={ACTIVITY_TYPES}
64 +
              active={activityType}
65 +
              onChange={setActivityType}
66 +
            />
67 +
          </Section>
68 +

69 +
          <Section>
70 +
            <SectionHeader title="Duração (min, opcional)" />
71 +
            <Card style={{ padding: Spacing.cardGap }}>
72 +
              <Input
73 +
                label="Duração em minutos"
74 +
                value={activityDuration}
75 +
                onChangeText={setActivityDuration}
76 +
                placeholder="Ex: 30"
77 +
                keyboardType="numeric"
78 +
                variant="darkGhost"
79 +
              />
80 +
            </Card>
81 +
          </Section>
82 +

83 +
          <Button
84 +
            title="Salvar"
85 +
            onPress={handleSave}
86 +
            loading={createCareAction.isPending}
87 +
            style={{ ...Shadows.lg }}
88 +
          />
89 +
        </View>
90 +
      </ScrollView>
91 +
    </KeyboardAvoidingView>
92 +
  );
93 +
}
94 +

95 +
const styles = StyleSheet.create({
96 +
  keyboardView: {
97 +
    flex: 1,
98 +
  },
99 +
  container: {
100 +
    gap: 24,
101 +
    paddingHorizontal: Spacing.containerPadding,
102 +
    paddingVertical: Spacing.sectionGap,
103 +
    marginBottom: 50,
104 +
  },
105 +
  header: {
106 +
    gap: 8,
107 +
  },
108 +
  headerTitle: {
109 +
    ...Typography.headlineMd,
110 +
  },
111 +
  headerSubtitle: {
112 +
    ...Typography.bodyMd,
113 +
    color: Colors.light.textSecondary,
114 +
  },
115 +
});
/dev/null → frontend/app/care-actions/appointment.tsx
1 +
import {
2 +
  View,
3 +
  Text,
4 +
  StyleSheet,
5 +
  KeyboardAvoidingView,
6 +
  ScrollView,
7 +
  TouchableOpacity,
8 +
} from "react-native";
9 +
import { useState } from "react";
10 +
import { router } from "expo-router";
11 +

12 +
import { Card } from "@/components/ui/Cards";
13 +
import { TextArea } from "@/components/ui/Input";
14 +
import { Button } from "@/components/ui/Button";
15 +
import { Section, SectionHeader } from "@/components/ui/Sections";
16 +
import { CategoryChips, CategoryOption } from "@/components/ui/CategoryChips";
17 +
import { Spacing, Typography, Colors, BorderRadius, Shadows } from "@/constants/theme";
18 +
import { Ionicons } from "@expo/vector-icons";
19 +
import { useCreateCareAction } from "@/hooks";
20 +
import { AppointmentType } from "@/lib/api";
21 +

22 +
const APPOINTMENT_TYPES: CategoryOption<AppointmentType>[] = [
23 +
  { label: 'Analista', value: 'ANALYST', icon: <Ionicons name="person-outline" size={16} color={Colors.light.tint} /> },
24 +
  { label: 'Psiquiatra', value: 'PSYCHIATRIST', icon: <Ionicons name="medical-outline" size={16} color={Colors.light.tint} /> },
25 +
  { label: 'Clínico', value: 'GP', icon: <Ionicons name="fitness-outline" size={16} color={Colors.light.tint} /> },
26 +
  { label: 'Nutricionista', value: 'NUTRITIONIST', icon: <Ionicons name="nutrition-outline" size={16} color={Colors.light.tint} /> },
27 +
  { label: 'Outro', value: 'OTHER', icon: <Ionicons name="help-outline" size={16} color={Colors.light.tint} /> },
28 +
];
29 +

30 +
const DURATIONS = [30, 45, 50, 60];
31 +

32 +
export default function AppointmentCareAction() {
33 +
  const [appointmentType, setAppointmentType] = useState<AppointmentType>('ANALYST');
34 +
  const [appointmentDuration, setAppointmentDuration] = useState<number>(50);
35 +
  const [appointmentNote, setAppointmentNote] = useState('');
36 +

37 +
  const createCareAction = useCreateCareAction();
38 +

39 +
  const handleSave = async () => {
40 +
    await createCareAction.mutateAsync({
41 +
      type: 'APPOINTMENT',
42 +
      moment: new Date(),
43 +
      appointment: {
44 +
        type: appointmentType,
45 +
        duration: appointmentDuration,
46 +
        note: appointmentNote || undefined,
47 +
      },
48 +
    });
49 +
    router.replace('/care-actions/post-appointment');
50 +
  };
51 +

52 +
  return (
53 +
    <KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
54 +
      <ScrollView scrollEventThrottle={16}>
55 +
        <View style={styles.container}>
56 +
          <View style={styles.header}>
57 +
            <Text style={styles.headerTitle}>Registrar consulta</Text>
58 +
            <Text style={styles.headerSubtitle}>
59 +
              Informe os detalhes da consulta ou sessão
60 +
            </Text>
61 +
          </View>
62 +

63 +
          <Section>
64 +
            <SectionHeader title="Tipo de consulta" />
65 +
            <CategoryChips
66 +
              options={APPOINTMENT_TYPES}
67 +
              active={appointmentType}
68 +
              onChange={setAppointmentType}
69 +
            />
70 +
          </Section>
71 +

72 +
          <Section>
73 +
            <SectionHeader title="Duração (min)" />
74 +
            <View style={styles.durationRow}>
75 +
              {DURATIONS.map((d) => (
76 +
                <TouchableOpacity
77 +
                  key={d}
78 +
                  style={[
79 +
                    styles.durationChip,
80 +
                    appointmentDuration === d && styles.durationChipActive,
81 +
                  ]}
82 +
                  onPress={() => setAppointmentDuration(d)}
83 +
                  activeOpacity={0.7}
84 +
                >
85 +
                  <Text
86 +
                    style={[
87 +
                      styles.durationLabel,
88 +
                      appointmentDuration === d && styles.durationLabelActive,
89 +
                    ]}
90 +
                  >
91 +
                    {d}
92 +
                  </Text>
93 +
                </TouchableOpacity>
94 +
              ))}
95 +
            </View>
96 +
          </Section>
97 +

98 +
          <Section>
99 +
            <SectionHeader title="Notas (opcional)" />
100 +
            <Card style={{ padding: Spacing.cardGap }}>
101 +
              <TextArea
102 +
                label="Como foi a consulta?"
103 +
                variant="darkGhost"
104 +
                value={appointmentNote}
105 +
                onChangeText={setAppointmentNote}
106 +
                minRows={4}
107 +
                placeholder="Descreva brevemente..."
108 +
              />
109 +
            </Card>
110 +
          </Section>
111 +

112 +
          <Button
113 +
            title="Salvar"
114 +
            onPress={handleSave}
115 +
            loading={createCareAction.isPending}
116 +
            style={{ ...Shadows.lg }}
117 +
          />
118 +
        </View>
119 +
      </ScrollView>
120 +
    </KeyboardAvoidingView>
121 +
  );
122 +
}
123 +

124 +
const styles = StyleSheet.create({
125 +
  keyboardView: {
126 +
    flex: 1,
127 +
  },
128 +
  container: {
129 +
    gap: 24,
130 +
    paddingHorizontal: Spacing.containerPadding,
131 +
    paddingVertical: Spacing.sectionGap,
132 +
    marginBottom: 50,
133 +
  },
134 +
  header: {
135 +
    gap: 8,
136 +
  },
137 +
  headerTitle: {
138 +
    ...Typography.headlineMd,
139 +
  },
140 +
  headerSubtitle: {
141 +
    ...Typography.bodyMd,
142 +
    color: Colors.light.textSecondary,
143 +
  },
144 +
  durationRow: {
145 +
    flexDirection: 'row',
146 +
    gap: 10,
147 +
    flexWrap: 'wrap',
148 +
  },
149 +
  durationChip: {
150 +
    paddingHorizontal: 20,
151 +
    paddingVertical: 12,
152 +
    borderRadius: BorderRadius.full,
153 +
    backgroundColor: '#fff',
154 +
    borderWidth: 1.5,
155 +
    borderColor: Colors.light.divider,
156 +
    minWidth: 64,
157 +
    alignItems: 'center',
158 +
  },
159 +
  durationChipActive: {
160 +
    backgroundColor: Colors.light.tint + '1A',
161 +
    borderColor: Colors.light.tint,
162 +
  },
163 +
  durationLabel: {
164 +
    fontSize: 16,
165 +
    fontWeight: '600',
166 +
    color: Colors.light.text,
167 +
  },
168 +
  durationLabelActive: {
169 +
    color: Colors.light.text,
170 +
  },
171 +
});
/dev/null → frontend/app/care-actions/medicine-new.tsx
1 +
import {
2 +
  View,
3 +
  Text,
4 +
  StyleSheet,
5 +
  KeyboardAvoidingView,
6 +
  ScrollView,
7 +
  TouchableOpacity,
8 +
} from "react-native";
9 +
import { useState } from "react";
10 +
import { router } from "expo-router";
11 +

12 +
import { Card } from "@/components/ui/Cards";
13 +
import { Input } from "@/components/ui/Input";
14 +
import { Button } from "@/components/ui/Button";
15 +
import { Section, SectionHeader } from "@/components/ui/Sections";
16 +
import { Spacing, Typography, Colors, BorderRadius, Shadows } from "@/constants/theme";
17 +
import { TimePicker } from "@/components/ui/TimePicker";
18 +
import { useCreateCareAction } from "@/hooks";
19 +
import { MedicinePeriodicity } from "@/lib/api";
20 +

21 +
const PERIODICITY_OPTIONS: { label: string; value: MedicinePeriodicity }[] = [
22 +
  { label: 'Uma vez', value: 'ONCE' },
23 +
  { label: 'Diário', value: 'DAILY' },
24 +
  { label: '2x ao dia', value: 'TWICE_DAILY' },
25 +
  { label: '3x ao dia', value: 'THREE_TIMES_DAILY' },
26 +
  { label: 'Semanal', value: 'WEEKLY' },
27 +
  { label: 'Quinzenal', value: 'BIWEEKLY' },
28 +
  { label: 'Mensal', value: 'MONTHLY' },
29 +
];
30 +

31 +
function getTimePickerCount(p: MedicinePeriodicity): number {
32 +
  switch (p) {
33 +
    case 'ONCE': return 0;
34 +
    case 'DAILY': return 1;
35 +
    case 'TWICE_DAILY': return 2;
36 +
    case 'THREE_TIMES_DAILY': return 3;
37 +
    case 'WEEKLY':
38 +
    case 'BIWEEKLY':
39 +
    case 'MONTHLY': return 1;
40 +
  }
41 +
}
42 +

43 +
export default function MedicineNewCareAction() {
44 +
  const [medicineName, setMedicineName] = useState('');
45 +
  const [medicineDosage, setMedicineDosage] = useState('');
46 +
  const [medicinePeriodicity, setMedicinePeriodicity] = useState<MedicinePeriodicity>('ONCE');
47 +
  const [scheduledTimes, setScheduledTimes] = useState<Date[]>([]);
48 +

49 +
  const createCareAction = useCreateCareAction();
50 +

51 +
  const handleSave = async () => {
52 +
    const count = getTimePickerCount(medicinePeriodicity);
53 +
    const scheduledAt = count > 0
54 +
      ? scheduledTimes.slice(0, count)
55 +
          .map(d => `${d.getHours().toString().padStart(2,'0')}:${d.getMinutes().toString().padStart(2,'0')}`)
56 +
          .join(',')
57 +
      : undefined;
58 +

59 +
    await createCareAction.mutateAsync({
60 +
      type: 'MEDICINE',
61 +
      moment: new Date(),
62 +
      medicine: {
63 +
        name: medicineName,
64 +
        dosage: medicineDosage,
65 +
        periodicity: medicinePeriodicity,
66 +
        scheduledAt,
67 +
      },
68 +
    });
69 +
    router.replace('/(tabs)/actions');
70 +
  };
71 +

72 +
  return (
73 +
    <KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
74 +
      <ScrollView scrollEventThrottle={16}>
75 +
        <View style={styles.container}>
76 +
          <View style={styles.header}>
77 +
            <Text style={styles.headerTitle}>Novo medicamento</Text>
78 +
            <Text style={styles.headerSubtitle}>
79 +
              Informe os detalhes do medicamento
80 +
            </Text>
81 +
          </View>
82 +

83 +
          <Section>
84 +
            <SectionHeader title="Detalhes" />
85 +
            <Card style={{ padding: Spacing.cardGap }}>
86 +
              <Input
87 +
                label="Nome"
88 +
                value={medicineName}
89 +
                onChangeText={setMedicineName}
90 +
                placeholder="Ex: Fluoxetina"
91 +
                variant="darkGhost"
92 +
              />
93 +
              <Input
94 +
                label="Dosagem"
95 +
                value={medicineDosage}
96 +
                onChangeText={setMedicineDosage}
97 +
                placeholder="Ex: 20mg"
98 +
                variant="darkGhost"
99 +
              />
100 +
            </Card>
101 +
          </Section>
102 +

103 +
          <Section>
104 +
            <SectionHeader title="Frequência" />
105 +
            <View style={styles.periodicityRow}>
106 +
              {PERIODICITY_OPTIONS.map((opt) => (
107 +
                <TouchableOpacity
108 +
                  key={opt.value}
109 +
                  style={[
110 +
                    styles.periodicityChip,
111 +
                    medicinePeriodicity === opt.value && styles.periodicityChipActive,
112 +
                  ]}
113 +
                  onPress={() => {
114 +
                    setMedicinePeriodicity(opt.value);
115 +
                    const count = getTimePickerCount(opt.value);
116 +
                    setScheduledTimes(prev =>
117 +
                      count === 0 ? [] :
118 +
                      count > prev.length ? [...prev, ...Array(count - prev.length).fill(new Date())] :
119 +
                      prev.slice(0, count)
120 +
                    );
121 +
                  }}
122 +
                  activeOpacity={0.7}
123 +
                >
124 +
                  <Text
125 +
                    style={[
126 +
                      styles.periodicityChipLabel,
127 +
                      medicinePeriodicity === opt.value && styles.periodicityChipLabelActive,
128 +
                    ]}
129 +
                  >
130 +
                    {opt.label}
131 +
                  </Text>
132 +
                </TouchableOpacity>
133 +
              ))}
134 +
            </View>
135 +
          </Section>
136 +

137 +
          {scheduledTimes.length > 0 && (
138 +
            <Section>
139 +
              <SectionHeader title={scheduledTimes.length === 1 ? 'Horário' : 'Horários'} />
140 +
              {scheduledTimes.map((t, i) => (
141 +
                <View style={{ marginTop: 10 }} key={i}>
142 +
                  <TimePicker
143 +
                    value={t}
144 +
                    onChange={(date) =>
145 +
                      setScheduledTimes(prev => prev.map((v, idx) => idx === i ? date : v))
146 +
                    }
147 +
                  />
148 +
                </View>
149 +
              ))}
150 +
            </Section>
151 +
          )}
152 +

153 +
          <Button
154 +
            title="Salvar"
155 +
            onPress={handleSave}
156 +
            loading={createCareAction.isPending}
157 +
            disabled={!medicineName || !medicineDosage}
158 +
          />
159 +
        </View>
160 +
      </ScrollView>
161 +
    </KeyboardAvoidingView>
162 +
  );
163 +
}
164 +

165 +
const styles = StyleSheet.create({
166 +
  keyboardView: {
167 +
    flex: 1,
168 +
  },
169 +
  container: {
170 +
    gap: 24,
171 +
    paddingHorizontal: Spacing.containerPadding,
172 +
    paddingVertical: Spacing.sectionGap,
173 +
    marginBottom: 50,
174 +
  },
175 +
  header: {
176 +
    gap: 8,
177 +
  },
178 +
  headerTitle: {
179 +
    ...Typography.headlineMd,
180 +
  },
181 +
  headerSubtitle: {
182 +
    ...Typography.bodyMd,
183 +
    color: Colors.light.textSecondary,
184 +
  },
185 +
  periodicityRow: {
186 +
    flexDirection: 'row',
187 +
    flexWrap: 'wrap',
188 +
    gap: 8,
189 +
  },
190 +
  periodicityChip: {
191 +
    paddingHorizontal: 16,
192 +
    paddingVertical: 10,
193 +
    borderRadius: BorderRadius.full,
194 +
    backgroundColor: '#fff',
195 +
    borderWidth: 1.5,
196 +
    borderColor: Colors.light.divider,
197 +
  },
198 +
  periodicityChipActive: {
199 +
    backgroundColor: Colors.light.tint + '1A',
200 +
    borderColor: Colors.light.tint,
201 +
  },
202 +
  periodicityChipLabel: {
203 +
    fontSize: 14,
204 +
    fontWeight: '500',
205 +
    color: Colors.light.text,
206 +
  },
207 +
  periodicityChipLabelActive: {
208 +
    fontWeight: '600',
209 +
    color: Colors.light.text,
210 +
  },
211 +
});
/dev/null → frontend/app/care-actions/medicine.tsx
1 +
import {
2 +
  View,
3 +
  Text,
4 +
  StyleSheet,
5 +
  KeyboardAvoidingView,
6 +
  ScrollView,
7 +
  TouchableOpacity,
8 +
} from "react-native";
9 +
import { router } from "expo-router";
10 +

11 +
import { Button } from "@/components/ui/Button";
12 +
import { Section, SectionHeader } from "@/components/ui/Sections";
13 +
import { Spacing, Typography, Colors, BorderRadius, Shadows } from "@/constants/theme";
14 +
import { Ionicons } from "@expo/vector-icons";
15 +
import { useMedicineRegimens, useCreateCareAction } from "@/hooks";
16 +

17 +
export default function MedicineCareAction() {
18 +
  const { data: regimensData, isLoading: isLoadingRegimens } = useMedicineRegimens();
19 +
  const createCareAction = useCreateCareAction();
20 +

21 +
  const regimens = regimensData?.regimens ?? [];
22 +

23 +
  const handleSelectRegimen = async (regimenId: number) => {
24 +
    await createCareAction.mutateAsync({
25 +
      type: 'MEDICINE',
26 +
      moment: new Date(),
27 +
      regimenId,
28 +
    });
29 +
    router.replace('/(tabs)/actions');
30 +
  };
31 +

32 +
  return (
33 +
    <KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
34 +
      <ScrollView scrollEventThrottle={16}>
35 +
        <View style={styles.container}>
36 +
          <View style={styles.header}>
37 +
            <Text style={styles.headerTitle}>Qual medicamento?</Text>
38 +
            <Text style={styles.headerSubtitle}>
39 +
              Selecione um medicamento existente ou registre um novo
40 +
            </Text>
41 +
          </View>
42 +

43 +
          {isLoadingRegimens && (
44 +
            <Text style={styles.loadingText}>Carregando medicamentos...</Text>
45 +
          )}
46 +

47 +
          {!isLoadingRegimens && regimens.length > 0 && (
48 +
            <Section>
49 +
              <SectionHeader title="Medicamentos cadastrados" />
50 +
              {regimens.map((regimen) => (
51 +
                <TouchableOpacity
52 +
                  key={regimen.id}
53 +
                  style={styles.regimenChip}
54 +
                  activeOpacity={0.7}
55 +
                  onPress={() => handleSelectRegimen(regimen.id)}
56 +
                  disabled={createCareAction.isPending}
57 +
                >
58 +
                  <Ionicons name="bandage-outline" size={18} color={Colors.light.tint} />
59 +
                  <View style={styles.regimenChipText}>
60 +
                    <Text style={styles.regimenChipName}>{regimen.name}</Text>
61 +
                    <Text style={styles.regimenChipDosage}>{regimen.dosage}</Text>
62 +
                  </View>
63 +
                  <Ionicons name="chevron-forward" size={16} color={Colors.light.disabled} />
64 +
                </TouchableOpacity>
65 +
              ))}
66 +
            </Section>
67 +
          )}
68 +

69 +
          <Button
70 +
            title="Cadastrar outro medicamento"
71 +
            variant="dashed"
72 +
            onPress={() => router.push('/care-actions/medicine-new')}
73 +
          />
74 +
        </View>
75 +
      </ScrollView>
76 +
    </KeyboardAvoidingView>
77 +
  );
78 +
}
79 +

80 +
const styles = StyleSheet.create({
81 +
  keyboardView: {
82 +
    flex: 1,
83 +
  },
84 +
  container: {
85 +
    gap: 24,
86 +
    paddingHorizontal: Spacing.containerPadding,
87 +
    paddingVertical: Spacing.sectionGap,
88 +
    marginBottom: 50,
89 +
  },
90 +
  header: {
91 +
    gap: 8,
92 +
  },
93 +
  headerTitle: {
94 +
    ...Typography.headlineMd,
95 +
  },
96 +
  headerSubtitle: {
97 +
    ...Typography.bodyMd,
98 +
    color: Colors.light.textSecondary,
99 +
  },
100 +
  loadingText: {
101 +
    ...Typography.bodyMd,
102 +
    color: Colors.light.textSecondary,
103 +
    textAlign: 'center',
104 +
  },
105 +
  regimenChip: {
106 +
    flexDirection: 'row',
107 +
    alignItems: 'center',
108 +
    backgroundColor: '#fff',
109 +
    borderRadius: BorderRadius.lg,
110 +
    padding: Spacing.cardGap,
111 +
    borderWidth: 1,
112 +
    borderColor: Colors.light.divider,
113 +
    gap: 12,
114 +
    marginBottom: 8,
115 +
    ...Shadows.sm,
116 +
  },
117 +
  regimenChipText: {
118 +
    flex: 1,
119 +
    gap: 2,
120 +
  },
121 +
  regimenChipName: {
122 +
    ...Typography.bodyMd,
123 +
    fontWeight: '600',
124 +
  },
125 +
  regimenChipDosage: {
126 +
    ...Typography.labelSm,
127 +
    color: Colors.light.textSecondary,
128 +
  },
129 +
});
/dev/null → frontend/app/care-actions/new.tsx
1 +
import {
2 +
  View,
3 +
  Text,
4 +
  StyleSheet,
5 +
  KeyboardAvoidingView,
6 +
  ScrollView,
7 +
  TouchableOpacity,
8 +
} from "react-native";
9 +
import { router } from "expo-router";
10 +

11 +
import { Spacing, Typography, Colors, BorderRadius, Shadows } from "@/constants/theme";
12 +
import { Ionicons } from "@expo/vector-icons";
13 +

14 +
export default function NewCareAction() {
15 +
  return (
16 +
    <KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
17 +
      <ScrollView scrollEventThrottle={16}>
18 +
        <View style={styles.container}>
19 +
          <View style={styles.header}>
20 +
            <Text style={styles.headerTitle}>Que tipo de ação?</Text>
21 +
            <Text style={styles.headerSubtitle}>
22 +
              Selecione o tipo de ação de cuidado que deseja registrar
23 +
            </Text>
24 +
          </View>
25 +

26 +
          <TouchableOpacity
27 +
            style={styles.typeCard}
28 +
            activeOpacity={0.7}
29 +
            onPress={() => router.push('/care-actions/medicine')}
30 +
          >
31 +
            <View style={[styles.typeIcon, { backgroundColor: '#E7FDEF' }]}>
32 +
              <Ionicons name="bandage-outline" size={24} color="#13EC5B" />
33 +
            </View>
34 +
            <View style={styles.typeTextWrap}>
35 +
              <Text style={styles.typeTitle}>Medicamento</Text>
36 +
              <Text style={styles.typeDesc}>Registre a tomada de um medicamento</Text>
37 +
            </View>
38 +
            <Ionicons name="chevron-forward" size={20} color={Colors.light.disabled} />
39 +
          </TouchableOpacity>
40 +

41 +
          <TouchableOpacity
42 +
            style={styles.typeCard}
43 +
            activeOpacity={0.7}
44 +
            onPress={() => router.push('/care-actions/appointment')}
45 +
          >
46 +
            <View style={[styles.typeIcon, { backgroundColor: '#EFF6FF' }]}>
47 +
              <Ionicons name="medkit-outline" size={24} color="#2563EB" />
48 +
            </View>
49 +
            <View style={styles.typeTextWrap}>
50 +
              <Text style={styles.typeTitle}>Consulta</Text>
51 +
              <Text style={styles.typeDesc}>Registre uma consulta ou sessão</Text>
52 +
            </View>
53 +
            <Ionicons name="chevron-forward" size={20} color={Colors.light.disabled} />
54 +
          </TouchableOpacity>
55 +

56 +
          <TouchableOpacity
57 +
            style={styles.typeCard}
58 +
            activeOpacity={0.7}
59 +
            onPress={() => router.push('/care-actions/activity')}
60 +
          >
61 +
            <View style={[styles.typeIcon, { backgroundColor: '#FFF7ED' }]}>
62 +
              <Ionicons name="walk-outline" size={24} color="#EA580C" />
63 +
            </View>
64 +
            <View style={styles.typeTextWrap}>
65 +
              <Text style={styles.typeTitle}>Atividade</Text>
66 +
              <Text style={styles.typeDesc}>Registre uma atividade física ou mental</Text>
67 +
            </View>
68 +
            <Ionicons name="chevron-forward" size={20} color={Colors.light.disabled} />
69 +
          </TouchableOpacity>
70 +
        </View>
71 +
      </ScrollView>
72 +
    </KeyboardAvoidingView>
73 +
  );
74 +
}
75 +

76 +
const styles = StyleSheet.create({
77 +
  keyboardView: {
78 +
    flex: 1,
79 +
  },
80 +
  container: {
81 +
    gap: 24,
82 +
    paddingHorizontal: Spacing.containerPadding,
83 +
    paddingVertical: Spacing.sectionGap,
84 +
    marginBottom: 50,
85 +
  },
86 +
  header: {
87 +
    gap: 8,
88 +
  },
89 +
  headerTitle: {
90 +
    ...Typography.headlineMd,
91 +
  },
92 +
  headerSubtitle: {
93 +
    ...Typography.bodyMd,
94 +
    color: Colors.light.textSecondary,
95 +
  },
96 +
  typeCard: {
97 +
    flexDirection: 'row',
98 +
    alignItems: 'center',
99 +
    backgroundColor: '#fff',
100 +
    borderRadius: BorderRadius.lg,
101 +
    padding: Spacing.cardGap,
102 +
    borderWidth: 1,
103 +
    borderColor: Colors.light.divider,
104 +
    gap: 12,
105 +
    ...Shadows.sm,
106 +
  },
107 +
  typeIcon: {
108 +
    width: 48,
109 +
    height: 48,
110 +
    borderRadius: BorderRadius.md,
111 +
    alignItems: 'center',
112 +
    justifyContent: 'center',
113 +
  },
114 +
  typeTextWrap: {
115 +
    flex: 1,
116 +
    gap: 2,
117 +
  },
118 +
  typeTitle: {
119 +
    ...Typography.bodyLg,
120 +
  },
121 +
  typeDesc: {
122 +
    ...Typography.labelSm,
123 +
    color: Colors.light.textSecondary,
124 +
  },
125 +
});
/dev/null → frontend/app/care-actions/post-appointment.tsx
1 +
import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from "react-native";
2 +
import { router } from "expo-router";
3 +
import { Ionicons } from "@expo/vector-icons";
4 +
import { SafeAreaView } from "react-native-safe-area-context";
5 +

6 +
import { Button } from "@/components/ui/Button";
7 +
import { Card } from "@/components/ui/Cards";
8 +
import { Colors, Spacing, Typography, BorderRadius, Shadows } from "@/constants/theme";
9 +

10 +
export default function PostAppointment() {
11 +
  return (
12 +
    <SafeAreaView style={styles.safe}>
13 +
      <ScrollView
14 +
        contentContainerStyle={styles.scrollContent}
15 +
        scrollEventThrottle={16}
16 +
        showsVerticalScrollIndicator={false}
17 +
      >
18 +
        {/* Confirmation block */}
19 +
        <View style={styles.confirmationBlock}>
20 +
          <View style={styles.outerRing}>
21 +
            <View style={styles.innerCircle}>
22 +
              <Ionicons name="checkmark" size={32} color="#fff" />
23 +
            </View>
24 +
          </View>
25 +
          <Text style={styles.confirmTitle}>Consulta Salva!</Text>
26 +
          <Text style={styles.confirmSubtitle}>
27 +
            Sua consulta foi registrada com sucesso.
28 +
          </Text>
29 +
        </View>
30 +

31 +
        {/* Follow-up card */}
32 +
        <Card style={styles.followUpCard}>
33 +
          <Text style={styles.followUpTitle}>Algo surgiu na sessão?</Text>
34 +
          <Text style={styles.followUpSubtitle}>
35 +
            Aproveite para registrar enquanto está fresco
36 +
          </Text>
37 +

38 +
          <TouchableOpacity
39 +
            style={styles.optionRow}
40 +
            activeOpacity={0.7}
41 +
            onPress={() => router.replace("/triggers/new?category=THERAPY")}
42 +
          >
43 +
            <View style={[styles.optionIcon, { backgroundColor: "#ede9fe" }]}>
44 +
              <Ionicons name="flash-outline" size={22} color="#7c3aed" />
45 +
            </View>
46 +
            <View style={styles.optionText}>
47 +
              <Text style={styles.optionTitle}>Criar um gatilho</Text>
48 +
              <Text style={styles.optionDesc}>
49 +
                Registre algo que emergiu na sessão
50 +
              </Text>
51 +
            </View>
52 +
            <Ionicons
53 +
              name="chevron-forward"
54 +
              size={18}
55 +
              color={Colors.light.disabled}
56 +
            />
57 +
          </TouchableOpacity>
58 +

59 +
          <TouchableOpacity
60 +
            style={styles.optionRow}
61 +
            activeOpacity={0.7}
62 +
            onPress={() => router.replace("/new/entry")}
63 +
          >
64 +
            <View style={[styles.optionIcon, { backgroundColor: "#fef9c3" }]}>
65 +
              <Ionicons name="heart-outline" size={22} color="#ca8a04" />
66 +
            </View>
67 +
            <View style={styles.optionText}>
68 +
              <Text style={styles.optionTitle}>Registrar humor</Text>
69 +
              <Text style={styles.optionDesc}>
70 +
                Como você está se sentindo agora?
71 +
              </Text>
72 +
            </View>
73 +
            <Ionicons
74 +
              name="chevron-forward"
75 +
              size={18}
76 +
              color={Colors.light.disabled}
77 +
            />
78 +
          </TouchableOpacity>
79 +
        </Card>
80 +

81 +
        {/* Concluir ghost button */}
82 +
        <Button
83 +
          title="Concluir"
84 +
          variant="ghost"
85 +
          onPress={() => router.replace("/(tabs)/actions")}
86 +
          style={styles.concludeButton}
87 +
        />
88 +
      </ScrollView>
89 +
    </SafeAreaView>
90 +
  );
91 +
}
92 +

93 +
const styles = StyleSheet.create({
94 +
  safe: {
95 +
    flex: 1,
96 +
    backgroundColor: Colors.light.background,
97 +
  },
98 +
  scrollContent: {
99 +
    paddingHorizontal: Spacing.containerPadding,
100 +
    paddingTop: 48,
101 +
    paddingBottom: 36,
102 +
  },
103 +
  confirmationBlock: {
104 +
    alignItems: "center",
105 +
    marginBottom: Spacing.sectionGap,
106 +
    gap: 12,
107 +
  },
108 +
  outerRing: {
109 +
    width: 88,
110 +
    height: 88,
111 +
    borderRadius: 44,
112 +
    backgroundColor: "#E7FDEF",
113 +
    alignItems: "center",
114 +
    justifyContent: "center",
115 +
  },
116 +
  innerCircle: {
117 +
    width: 64,
118 +
    height: 64,
119 +
    borderRadius: 32,
120 +
    backgroundColor: Colors.light.tint,
121 +
    alignItems: "center",
122 +
    justifyContent: "center",
123 +
  },
124 +
  confirmTitle: {
125 +
    ...Typography.headlineMd,
126 +
    fontWeight: "700",
127 +
    color: Colors.light.text,
128 +
    textAlign: "center",
129 +
  },
130 +
  confirmSubtitle: {
131 +
    ...Typography.bodyMd,
132 +
    color: Colors.light.textSecondary,
133 +
    textAlign: "center",
134 +
  },
135 +
  followUpCard: {
136 +
    padding: Spacing.cardGap,
137 +
    marginBottom: Spacing.sectionGap,
138 +
    gap: 12,
139 +
  },
140 +
  followUpTitle: {
141 +
    ...Typography.headlineMd,
142 +
    fontWeight: "700",
143 +
    color: Colors.light.text,
144 +
    marginBottom: 2,
145 +
  },
146 +
  followUpSubtitle: {
147 +
    ...Typography.bodyMd,
148 +
    color: Colors.light.textSecondary,
149 +
    marginBottom: 8,
150 +
  },
151 +
  optionRow: {
152 +
    flexDirection: "row",
153 +
    alignItems: "center",
154 +
    padding: Spacing.cardGap,
155 +
    backgroundColor: "#fff",
156 +
    borderRadius: BorderRadius.lg,
157 +
    borderWidth: 1,
158 +
    borderColor: Colors.light.divider,
159 +
    gap: 12,
160 +
    ...Shadows.sm,
161 +
  },
162 +
  optionIcon: {
163 +
    width: 44,
164 +
    height: 44,
165 +
    borderRadius: BorderRadius.md,
166 +
    alignItems: "center",
167 +
    justifyContent: "center",
168 +
  },
169 +
  optionText: {
170 +
    flex: 1,
171 +
    gap: 2,
172 +
  },
173 +
  optionTitle: {
174 +
    ...Typography.bodyMd,
175 +
    fontWeight: "600",
176 +
    color: Colors.light.text,
177 +
  },
178 +
  optionDesc: {
179 +
    ...Typography.labelSm,
180 +
    color: Colors.light.textSecondary,
181 +
  },
182 +
  concludeButton: {
183 +
    marginTop: 8,
184 +
  },
185 +
});