eletrotupi / tcc/ commit / d8d5424

frontend: introduce new care actions screens

Pedro Lucas Porcellis porcellis@eletrotupi.com 1 month ago d8d5424a8ec32b325e17aa210b8b18da460390d4
Parents: fc5b05d
6 file(s) changed
  • frontend/app/care-actions/activity.tsx +115 -0
  • frontend/app/care-actions/appointment.tsx +171 -0
  • frontend/app/care-actions/medicine-new.tsx +211 -0
  • frontend/app/care-actions/medicine.tsx +129 -0
  • frontend/app/care-actions/new.tsx +125 -0
  • frontend/app/care-actions/post-appointment.tsx +185 -0
frontend/app/care-actions/activity.tsx
@@ -0,0 +1,115 @@
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 + });
frontend/app/care-actions/appointment.tsx
@@ -0,0 +1,171 @@
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 + });
frontend/app/care-actions/medicine-new.tsx
@@ -0,0 +1,211 @@
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 + });
frontend/app/care-actions/medicine.tsx
@@ -0,0 +1,129 @@
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 + });
frontend/app/care-actions/new.tsx
@@ -0,0 +1,125 @@
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 + });
frontend/app/care-actions/post-appointment.tsx
@@ -0,0 +1,185 @@
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 + });