eletrotupi / tcc/ frontend/app/report.tsxa2ea1352da034bd3377e2a3cbfa4c53a75eba523
5.5 KB | 199 lines Raw
1
import React, { useState } from 'react'
2
import {
3
  View,
4
  TouchableOpacity,
5
  StyleSheet,
6
  ScrollView,
7
} from 'react-native'
8
import { Text } from '@/components/ui/Text';
9
import { Stack } from 'expo-router'
10
import { format, subDays, startOfDay, endOfDay } from 'date-fns'
11
import { Paths, File } from 'expo-file-system'
12
import * as Sharing from 'expo-sharing'
13
import { SafeAreaView } from 'react-native-safe-area-context'
14
import { Button } from '@/components/ui/Button'
15
import { Section, SectionHeader } from '@/components/ui/Sections'
16
import { DatePickerField } from '@/components/ui/DatePickerField'
17
import { apiClient, ApiError } from '@/lib/api'
18
import { useToast } from '@/context/ToastContext'
19
import { translateError } from '@/lib/errors/translations'
20
import { Colors, Spacing, BorderRadius } from '@/constants/theme'
21

22
const PRESETS = [
23
  { label: '7 dias', days: 7 },
24
  { label: '30 dias', days: 30 },
25
  { label: '90 dias', days: 90 },
26
  { label: 'Personalizado', days: 0 },
27
]
28

29
export default function ReportScreen() {
30
  const { showToast } = useToast()
31

32
  const [preset, setPreset] = useState(30)
33
  const [startDate, setStartDate] = useState(() => subDays(new Date(), 30))
34
  const [endDate, setEndDate] = useState(() => new Date())
35
  const [loading, setLoading] = useState(false)
36

37
  const isCustom = preset === 0
38

39
  function selectPreset(days: number) {
40
    setPreset(days)
41
    if (days > 0) {
42
      setStartDate(subDays(new Date(), days))
43
      setEndDate(new Date())
44
    }
45
  }
46

47
  async function handleGenerate() {
48
    setLoading(true)
49
    try {
50
      const buffer = await apiClient.generateReport(
51
        startOfDay(startDate).toISOString(),
52
        endOfDay(endDate).toISOString(),
53
      )
54

55
      const fileName = `nexo-report-${format(startDate, 'yyyy-MM')}.pdf`
56
      const file = new File(Paths.cache, fileName)
57
      file.write(new Uint8Array(buffer))
58
      await Sharing.shareAsync(file.uri, {
59
        mimeType: 'application/pdf',
60
        dialogTitle: 'Salvar relatório',
61
      })
62
    } catch (err) {
63
      showToast(
64
        err instanceof ApiError ? translateError(err.message) : 'Erro ao gerar relatório',
65
        'error',
66
      )
67
    } finally {
68
      setLoading(false)
69
    }
70
  }
71

72
  return (
73
    <SafeAreaView style={styles.safe} edges={['bottom']}>
74
      <Stack.Screen options={{ title: 'Relatório' }} />
75
      <ScrollView
76
        style={styles.scroll}
77
        contentContainerStyle={styles.content}
78
      >
79
        <View style={styles.intro}>
80
          <Text style={styles.introTitle}>Seu relatório de humor</Text>
81
          <Text style={styles.introBody}>
82
            Reúne os registros do período escolhido — humores, sentimentos e
83
            níveis de ansiedade, estresse e energia — num PDF pronto para
84
            compartilhar com seu terapeuta ou guardar só pra você.
85
          </Text>
86
        </View>
87

88
        <Section>
89
          <SectionHeader title="Período" />
90
          <View style={styles.chips}>
91
            {PRESETS.map((p) => (
92
              <TouchableOpacity
93
                key={p.days}
94
                style={[styles.chip, preset === p.days && styles.chipActive]}
95
                onPress={() => selectPreset(p.days)}
96
                activeOpacity={0.7}
97
              >
98
                <Text
99
                  style={[
100
                    styles.chipText,
101
                    preset === p.days && styles.chipTextActive,
102
                  ]}
103
                >
104
                  {p.label}
105
                </Text>
106
              </TouchableOpacity>
107
            ))}
108
          </View>
109
        </Section>
110

111
        {isCustom && (
112
          <Section>
113
            <SectionHeader title="Intervalo" />
114
            <DatePickerField
115
              label="De"
116
              initialDate={startDate}
117
              onChange={setStartDate}
118
              maximumDate={endDate}
119
            />
120
            <View style={{ height: Spacing.cardGap }} />
121
            <DatePickerField
122
              label="Até"
123
              initialDate={endDate}
124
              onChange={setEndDate}
125
              minimumDate={startDate}
126
              maximumDate={new Date()}
127
            />
128
          </Section>
129
        )}
130

131
        <Button
132
          title="Gerar relatório"
133
          variant="primary"
134
          size="large"
135
          loading={loading}
136
          onPress={handleGenerate}
137
          style={{ marginTop: Spacing.sectionGap }}
138
        />
139
      </ScrollView>
140
    </SafeAreaView>
141
  )
142
}
143

144
const styles = StyleSheet.create({
145
  safe: {
146
    flex: 1,
147
    backgroundColor: Colors.light.background,
148
  },
149
  scroll: {
150
    flex: 1,
151
  },
152
  content: {
153
    paddingHorizontal: Spacing.containerPadding,
154
    paddingTop: Spacing.containerPadding,
155
    paddingBottom: Spacing.sectionGap,
156
  },
157
  intro: {
158
    backgroundColor: Colors.light.surface,
159
    borderRadius: BorderRadius.md,
160
    padding: Spacing.cardGap,
161
    marginBottom: Spacing.cardGap,
162
  },
163
  introTitle: {
164
    fontSize: 15,
165
    fontWeight: '600',
166
    color: Colors.light.text,
167
    marginBottom: 6,
168
  },
169
  introBody: {
170
    fontSize: 13,
171
    lineHeight: 20,
172
    color: Colors.light.textSecondary,
173
  },
174
  chips: {
175
    flexDirection: 'row',
176
    flexWrap: 'wrap',
177
    gap: 8,
178
  },
179
  chip: {
180
    paddingVertical: 8,
181
    paddingHorizontal: 16,
182
    borderRadius: BorderRadius.full,
183
    borderWidth: 1,
184
    borderColor: Colors.light.borderDashed,
185
    backgroundColor: Colors.light.surface,
186
  },
187
  chipActive: {
188
    backgroundColor: Colors.light.tint,
189
    borderColor: Colors.light.tint,
190
  },
191
  chipText: {
192
    fontSize: 14,
193
    fontWeight: '500',
194
    color: Colors.light.text,
195
  },
196
  chipTextActive: {
197
    color: Colors.light.background,
198
  },
199
})