@nextsparkjs/mobile 0.1.0-beta.146 → 0.1.0-beta.148

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,310 +0,0 @@
1
- /**
2
- * Home/Dashboard Screen
3
- */
4
-
5
- import { useState } from 'react'
6
- import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native'
7
- import { router } from 'expo-router'
8
- import { useAuth } from '@nextsparkjs/mobile'
9
- import { useTasks } from '@/src/entities/tasks'
10
- import { useCustomers } from '@/src/entities/customers'
11
- import { Colors } from '@/src/constants/colors'
12
- import { Button } from '@/src/components/ui'
13
- import {
14
- Progress,
15
- Tabs,
16
- TabsList,
17
- TabsTrigger,
18
- TabsContent,
19
- Accordion,
20
- AccordionItem,
21
- AccordionTrigger,
22
- AccordionContent,
23
- Slider,
24
- } from '@nextsparkjs/ui'
25
-
26
- export default function HomeScreen() {
27
- const { user, team } = useAuth()
28
- const { data: tasksData } = useTasks()
29
- const { data: customersData } = useCustomers()
30
- const [activeTab, setActiveTab] = useState('tab1')
31
- const [sliderValue, setSliderValue] = useState([50])
32
-
33
- const firstName = user?.name?.split(' ')[0] || 'Usuario'
34
- const taskCount = tasksData?.data?.length || 0
35
- const customerCount = customersData?.data?.length || 0
36
-
37
- // Count tasks by status
38
- const todoTasks = tasksData?.data?.filter((t) => t.status === 'todo').length || 0
39
- const inProgressTasks =
40
- tasksData?.data?.filter((t) => t.status === 'in-progress').length || 0
41
-
42
- return (
43
- <ScrollView style={styles.container}>
44
- {/* Welcome Header */}
45
- <View style={styles.header}>
46
- <Text style={styles.pageTitle}>Dashboard</Text>
47
- <Text style={styles.welcomeText}>Bienvenido de vuelta, {firstName}</Text>
48
- </View>
49
-
50
- {/* Stats Cards */}
51
- <View style={styles.cardsContainer}>
52
- {/* Account Status Card */}
53
- <View style={styles.card}>
54
- <View style={styles.cardHeader}>
55
- <Text style={styles.cardLabel}>Estado de Cuenta</Text>
56
- <Text style={styles.cardIcon}>📈</Text>
57
- </View>
58
- <Text style={styles.cardValue}>Activo</Text>
59
- <View style={styles.badge}>
60
- <Text style={styles.badgeText}>verificado</Text>
61
- </View>
62
- </View>
63
-
64
- {/* Plan Card */}
65
- <View style={styles.card}>
66
- <View style={styles.cardHeader}>
67
- <Text style={styles.cardLabel}>Plan</Text>
68
- <Text style={styles.cardIcon}>💳</Text>
69
- </View>
70
- <Text style={styles.cardValue}>Gratis</Text>
71
- <Button variant="link" size="sm" style={{ alignSelf: 'flex-start', height: 'auto', paddingHorizontal: 0 }}>
72
- Actualizar plan →
73
- </Button>
74
- </View>
75
-
76
- {/* Tasks Summary Card */}
77
- <TouchableOpacity
78
- style={styles.card}
79
- onPress={() => router.push('/(app)/tasks')}
80
- activeOpacity={0.7}
81
- >
82
- <View style={styles.cardHeader}>
83
- <Text style={styles.cardLabel}>Mis Tareas</Text>
84
- <Text style={styles.cardIcon}>☑</Text>
85
- </View>
86
- <Text style={styles.cardValue}>{taskCount}</Text>
87
- <View style={styles.statsRow}>
88
- <Text style={styles.statItem}>
89
- <Text style={styles.statNumber}>{todoTasks}</Text> pendientes
90
- </Text>
91
- <Text style={styles.statItem}>
92
- <Text style={styles.statNumber}>{inProgressTasks}</Text> en progreso
93
- </Text>
94
- </View>
95
- </TouchableOpacity>
96
-
97
- {/* Customers Card */}
98
- <TouchableOpacity
99
- style={styles.card}
100
- onPress={() => router.push('/(app)/customers')}
101
- activeOpacity={0.7}
102
- >
103
- <View style={styles.cardHeader}>
104
- <Text style={styles.cardLabel}>Clientes</Text>
105
- <Text style={styles.cardIcon}>👥</Text>
106
- </View>
107
- <Text style={styles.cardValue}>{customerCount}</Text>
108
- <Text style={styles.cardLink}>Ver todos →</Text>
109
- </TouchableOpacity>
110
-
111
- {/* Team Card */}
112
- {team && (
113
- <View style={styles.card}>
114
- <View style={styles.cardHeader}>
115
- <Text style={styles.cardLabel}>Equipo Actual</Text>
116
- <Text style={styles.cardIcon}>🏢</Text>
117
- </View>
118
- <Text style={styles.cardValue}>{team.name}</Text>
119
- <View style={styles.badge}>
120
- <Text style={styles.badgeText}>{team.role}</Text>
121
- </View>
122
- </View>
123
- )}
124
- </View>
125
-
126
- {/* === TEST COMPONENTS - Phase 3 Migration === */}
127
- <View style={styles.testSection}>
128
- <Text style={styles.testTitle}>Component Test (Phase 3)</Text>
129
-
130
- {/* Progress Test */}
131
- <View style={styles.testCard}>
132
- <Text style={styles.testLabel}>Progress Component</Text>
133
- <Progress value={30} style={{ marginBottom: 8 }} />
134
- <Progress value={60} style={{ marginBottom: 8 }} />
135
- <Progress value={90} />
136
- </View>
137
-
138
- {/* Tabs Test */}
139
- <View style={styles.testCard}>
140
- <Text style={styles.testLabel}>Tabs Component</Text>
141
- <Tabs value={activeTab} onValueChange={setActiveTab}>
142
- <TabsList>
143
- <TabsTrigger value="tab1">Tab 1</TabsTrigger>
144
- <TabsTrigger value="tab2">Tab 2</TabsTrigger>
145
- <TabsTrigger value="tab3">Tab 3</TabsTrigger>
146
- </TabsList>
147
- <TabsContent value="tab1">
148
- <Text style={styles.testText}>Content for Tab 1</Text>
149
- </TabsContent>
150
- <TabsContent value="tab2">
151
- <Text style={styles.testText}>Content for Tab 2</Text>
152
- </TabsContent>
153
- <TabsContent value="tab3">
154
- <Text style={styles.testText}>Content for Tab 3</Text>
155
- </TabsContent>
156
- </Tabs>
157
- </View>
158
-
159
- {/* Accordion Test */}
160
- <View style={styles.testCard}>
161
- <Text style={styles.testLabel}>Accordion Component</Text>
162
- <Accordion type="single" collapsible>
163
- <AccordionItem value="item1">
164
- <AccordionTrigger>Section 1</AccordionTrigger>
165
- <AccordionContent>
166
- <Text style={styles.testText}>This is the content for section 1.</Text>
167
- </AccordionContent>
168
- </AccordionItem>
169
- <AccordionItem value="item2">
170
- <AccordionTrigger>Section 2</AccordionTrigger>
171
- <AccordionContent>
172
- <Text style={styles.testText}>This is the content for section 2.</Text>
173
- </AccordionContent>
174
- </AccordionItem>
175
- </Accordion>
176
- </View>
177
-
178
- {/* Slider Test */}
179
- <View style={styles.testCard}>
180
- <Text style={styles.testLabel}>Slider Component</Text>
181
- <Text style={styles.testText}>Value: {sliderValue[0]}</Text>
182
- <Slider
183
- value={sliderValue}
184
- onValueChange={setSliderValue}
185
- min={0}
186
- max={100}
187
- step={1}
188
- />
189
- </View>
190
- </View>
191
- {/* === END TEST COMPONENTS === */}
192
-
193
- <View style={styles.spacer} />
194
- </ScrollView>
195
- )
196
- }
197
-
198
- const styles = StyleSheet.create({
199
- container: {
200
- flex: 1,
201
- backgroundColor: Colors.backgroundSecondary,
202
- },
203
- header: {
204
- padding: 20,
205
- paddingBottom: 12,
206
- },
207
- pageTitle: {
208
- fontSize: 28,
209
- fontWeight: '700',
210
- color: Colors.foreground,
211
- marginBottom: 4,
212
- },
213
- welcomeText: {
214
- fontSize: 15,
215
- color: Colors.foregroundSecondary,
216
- },
217
- cardsContainer: {
218
- paddingHorizontal: 16,
219
- gap: 12,
220
- },
221
- card: {
222
- backgroundColor: Colors.card,
223
- borderRadius: 12,
224
- padding: 20,
225
- borderWidth: 1,
226
- borderColor: Colors.border,
227
- },
228
- cardHeader: {
229
- flexDirection: 'row',
230
- justifyContent: 'space-between',
231
- alignItems: 'center',
232
- marginBottom: 12,
233
- },
234
- cardLabel: {
235
- fontSize: 14,
236
- color: Colors.foregroundSecondary,
237
- fontWeight: '500',
238
- },
239
- cardIcon: {
240
- fontSize: 18,
241
- },
242
- cardValue: {
243
- fontSize: 24,
244
- fontWeight: '700',
245
- color: Colors.foreground,
246
- marginBottom: 8,
247
- },
248
- badge: {
249
- alignSelf: 'flex-start',
250
- backgroundColor: Colors.backgroundTertiary,
251
- paddingHorizontal: 10,
252
- paddingVertical: 4,
253
- borderRadius: 6,
254
- },
255
- badgeText: {
256
- fontSize: 12,
257
- color: Colors.foregroundSecondary,
258
- fontWeight: '500',
259
- },
260
- cardLink: {
261
- fontSize: 14,
262
- color: Colors.foreground,
263
- fontWeight: '500',
264
- },
265
- statsRow: {
266
- flexDirection: 'row',
267
- gap: 16,
268
- },
269
- statItem: {
270
- fontSize: 13,
271
- color: Colors.foregroundSecondary,
272
- },
273
- statNumber: {
274
- fontWeight: '600',
275
- color: Colors.foreground,
276
- },
277
- spacer: {
278
- height: 40,
279
- },
280
- // Test section styles
281
- testSection: {
282
- padding: 16,
283
- marginTop: 16,
284
- },
285
- testTitle: {
286
- fontSize: 20,
287
- fontWeight: '700',
288
- color: Colors.foreground,
289
- marginBottom: 16,
290
- },
291
- testCard: {
292
- backgroundColor: Colors.card,
293
- borderRadius: 12,
294
- padding: 16,
295
- marginBottom: 12,
296
- borderWidth: 1,
297
- borderColor: Colors.border,
298
- },
299
- testLabel: {
300
- fontSize: 14,
301
- fontWeight: '600',
302
- color: Colors.foregroundSecondary,
303
- marginBottom: 12,
304
- },
305
- testText: {
306
- fontSize: 14,
307
- color: Colors.foreground,
308
- paddingVertical: 8,
309
- },
310
- })
@@ -1,242 +0,0 @@
1
- /**
2
- * Notifications Screen
3
- * Displays mock notifications data
4
- */
5
-
6
- import { useState, useMemo } from "react";
7
- import { View, FlatList, Pressable, RefreshControl } from "react-native";
8
- import { router } from "expo-router";
9
- import { Text, Card, Badge, Separator, Button } from "@/src/components/ui";
10
- import { cn } from "@/src/lib/utils";
11
- import notificationsData from "@/src/data/notifications.mock.json";
12
-
13
- interface Notification {
14
- id: string;
15
- type: string;
16
- title: string;
17
- message: string;
18
- read: boolean;
19
- createdAt: string;
20
- data: Record<string, unknown>;
21
- }
22
-
23
- const NOTIFICATION_ICONS: Record<string, string> = {
24
- task_assigned: "📋",
25
- task_completed: "✅",
26
- task_due_soon: "⏰",
27
- task_overdue: "🚨",
28
- task_comment: "💬",
29
- customer_added: "👤",
30
- team_invite: "👥",
31
- system: "⚙️",
32
- };
33
-
34
- const NOTIFICATION_COLORS: Record<string, string> = {
35
- task_assigned: "bg-blue-100",
36
- task_completed: "bg-green-100",
37
- task_due_soon: "bg-amber-100",
38
- task_overdue: "bg-red-100",
39
- task_comment: "bg-purple-100",
40
- customer_added: "bg-cyan-100",
41
- team_invite: "bg-indigo-100",
42
- system: "bg-gray-100",
43
- };
44
-
45
- function formatTimeAgo(dateString: string): string {
46
- const date = new Date(dateString);
47
- const now = new Date();
48
- const diffMs = now.getTime() - date.getTime();
49
- const diffMins = Math.floor(diffMs / 60000);
50
- const diffHours = Math.floor(diffMs / 3600000);
51
- const diffDays = Math.floor(diffMs / 86400000);
52
-
53
- if (diffMins < 60) {
54
- return `hace ${diffMins} min`;
55
- } else if (diffHours < 24) {
56
- return `hace ${diffHours}h`;
57
- } else if (diffDays === 1) {
58
- return "ayer";
59
- } else if (diffDays < 7) {
60
- return `hace ${diffDays} días`;
61
- } else {
62
- return date.toLocaleDateString("es-ES", {
63
- day: "numeric",
64
- month: "short",
65
- });
66
- }
67
- }
68
-
69
- function NotificationItem({
70
- notification,
71
- onPress,
72
- onMarkAsRead,
73
- }: {
74
- notification: Notification;
75
- onPress: () => void;
76
- onMarkAsRead: () => void;
77
- }) {
78
- const icon = NOTIFICATION_ICONS[notification.type] || "🔔";
79
- const bgColor = NOTIFICATION_COLORS[notification.type] || "bg-gray-100";
80
-
81
- return (
82
- <Pressable
83
- onPress={onPress}
84
- onLongPress={onMarkAsRead}
85
- className={cn(
86
- "mx-4 my-1 rounded-xl border bg-card p-4",
87
- notification.read ? "border-border opacity-70" : "border-primary/20"
88
- )}
89
- >
90
- <View className="flex-row gap-3">
91
- {/* Icon */}
92
- <View
93
- className={cn(
94
- "h-10 w-10 items-center justify-center rounded-full",
95
- bgColor
96
- )}
97
- >
98
- <Text className="text-lg">{icon}</Text>
99
- </View>
100
-
101
- {/* Content */}
102
- <View className="flex-1">
103
- <View className="flex-row items-start justify-between">
104
- <Text
105
- variant="subheading"
106
- className={cn("flex-1", !notification.read && "font-bold")}
107
- numberOfLines={1}
108
- >
109
- {notification.title}
110
- </Text>
111
- {!notification.read && (
112
- <View className="ml-2 h-2 w-2 rounded-full bg-primary" />
113
- )}
114
- </View>
115
-
116
- <Text variant="muted" className="mt-1" numberOfLines={2}>
117
- {notification.message}
118
- </Text>
119
-
120
- <Text variant="muted" size="xs" className="mt-2">
121
- {formatTimeAgo(notification.createdAt)}
122
- </Text>
123
- </View>
124
- </View>
125
- </Pressable>
126
- );
127
- }
128
-
129
- export default function NotificationsScreen() {
130
- const [notifications, setNotifications] = useState<Notification[]>(
131
- notificationsData.notifications as Notification[]
132
- );
133
- const [refreshing, setRefreshing] = useState(false);
134
-
135
- const unreadCount = useMemo(
136
- () => notifications.filter((n) => !n.read).length,
137
- [notifications]
138
- );
139
-
140
- const handleRefresh = async () => {
141
- setRefreshing(true);
142
- // Simulate refresh delay
143
- await new Promise((resolve) => setTimeout(resolve, 1000));
144
- setRefreshing(false);
145
- };
146
-
147
- const handleNotificationPress = (notification: Notification) => {
148
- // Mark as read
149
- setNotifications((prev) =>
150
- prev.map((n) => (n.id === notification.id ? { ...n, read: true } : n))
151
- );
152
-
153
- // Navigate based on type
154
- if (notification.type.startsWith("task_") && notification.data.taskId) {
155
- router.push(`/(app)/task/${notification.data.taskId}`);
156
- } else if (notification.type === "customer_added" && notification.data.customerId) {
157
- router.push(`/(app)/customer/${notification.data.customerId}`);
158
- }
159
- };
160
-
161
- const handleMarkAsRead = (notificationId: string) => {
162
- setNotifications((prev) =>
163
- prev.map((n) => (n.id === notificationId ? { ...n, read: true } : n))
164
- );
165
- };
166
-
167
- const handleMarkAllAsRead = () => {
168
- setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
169
- };
170
-
171
- const handleClearAll = () => {
172
- setNotifications([]);
173
- };
174
-
175
- return (
176
- <View className="flex-1 bg-secondary">
177
- {/* Header */}
178
- <View className="flex-row items-center justify-between bg-background px-4 py-3 border-b border-border">
179
- <Pressable onPress={() => router.back()} className="p-2 -ml-2">
180
- <Text size="xl">←</Text>
181
- </Pressable>
182
- <Text variant="heading" size="lg">
183
- Notificaciones
184
- </Text>
185
- <View className="w-10" />
186
- </View>
187
-
188
- {/* Actions Bar */}
189
- {notifications.length > 0 && (
190
- <View className="flex-row items-center justify-between px-4 py-2 bg-background border-b border-border">
191
- <View className="flex-row items-center gap-2">
192
- {unreadCount > 0 && (
193
- <Badge variant="default">
194
- {unreadCount} sin leer
195
- </Badge>
196
- )}
197
- </View>
198
- <View className="flex-row gap-2">
199
- {unreadCount > 0 && (
200
- <Button variant="link" size="sm" onPress={handleMarkAllAsRead}>
201
- Marcar todo leído
202
- </Button>
203
- )}
204
- <Button variant="link" size="sm" onPress={handleClearAll} textStyle={{ color: '#ef4444' }}>
205
- Limpiar
206
- </Button>
207
- </View>
208
- </View>
209
- )}
210
-
211
- {/* Notifications List */}
212
- {notifications.length === 0 ? (
213
- <View className="flex-1 items-center justify-center p-8">
214
- <Text className="text-6xl mb-4">🔔</Text>
215
- <Text variant="heading" size="lg" className="text-center">
216
- Sin notificaciones
217
- </Text>
218
- <Text variant="muted" className="text-center mt-2">
219
- Cuando recibas notificaciones, aparecerán aquí
220
- </Text>
221
- </View>
222
- ) : (
223
- <FlatList
224
- data={notifications}
225
- keyExtractor={(item) => item.id}
226
- renderItem={({ item }) => (
227
- <NotificationItem
228
- notification={item}
229
- onPress={() => handleNotificationPress(item)}
230
- onMarkAsRead={() => handleMarkAsRead(item.id)}
231
- />
232
- )}
233
- contentContainerStyle={{ paddingVertical: 8 }}
234
- refreshControl={
235
- <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
236
- }
237
- ItemSeparatorComponent={() => <View className="h-1" />}
238
- />
239
- )}
240
- </View>
241
- );
242
- }