diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc8b5b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +# generated native folders +/ios +/android diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..7f4c450 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..4c058f6 --- /dev/null +++ b/App.tsx @@ -0,0 +1,223 @@ +import React, { useState } from 'react'; +import { + AppRegistry, + View, + Text, + FlatList, + TouchableOpacity, + StyleSheet, + ScrollView, + TextInput, + Alert +} from 'react-native'; + +// 10 PATIENTS DATA +const DATA = [ + {"id":"1","patientName":"John Doe","phone":"+91-9876543210","reason":"Teeth cleaning","timestamp":"Jan 16, 9:30 AM","status":"New","messages":["Need teeth cleaning appointment"]}, + {"id":"2","patientName":"Jane Smith","phone":"+91-9876543211","reason":"Root canal pain","timestamp":"Jan 16, 10:15 AM","status":"New","messages":["Severe tooth pain, urgent"]}, + {"id":"3","patientName":"Raj Patel","phone":"+91-9876543212","reason":"Braces consult","timestamp":"Jan 15, 2:30 PM","status":"In Progress","messages":["Braces treatment inquiry"]}, + {"id":"4","patientName":"Priya Sharma","phone":"+91-9876543213","reason":"Regular checkup","timestamp":"Jan 14, 11:00 AM","status":"Done","messages":["Routine checkup completed"]}, + {"id":"5","patientName":"Amit Kumar","phone":"+91-9876543214","reason":"Tooth extraction","timestamp":"Jan 16, 1:45 PM","status":"New","messages":["Need extraction consultation"]}, + {"id":"6","patientName":"Sita Devi","phone":"+91-9876543215","reason":"Filling needed","timestamp":"Jan 13, 3:20 PM","status":"New","messages":["Cavity filling required"]}, + {"id":"7","patientName":"Ravi Gupta","phone":"+91-9876543216","reason":"Whitening","timestamp":"Jan 12, 4:10 PM","status":"In Progress","messages":["Teeth whitening inquiry"]}, + {"id":"8","patientName":"Neha Reddy","phone":"+91-9876543217","reason":"Wisdom tooth","timestamp":"Jan 15, 5:30 PM","status":"New","messages":["Wisdom tooth pain"]}, + {"id":"9","patientName":"Vikram Singh","phone":"+91-9876543218","reason":"Dental x-ray","timestamp":"Jan 14, 10:45 AM","status":"Done","messages":["X-ray results ready"]}, + {"id":"10","patientName":"Lakshmi Nair","phone":"+91-9876543219","reason":"Emergency","timestamp":"Jan 16, 8:20 AM","status":"New","messages":["Emergency appointment needed"]} +]; + +const getStatusColor = (status: string) => { + switch (status) { + case 'New': return '#10B981'; + case 'In Progress': return '#F59E0B'; + case 'Done': return '#8B5CF6'; + default: return '#6B7280'; + } +}; + +export default function App() { + const [currentScreen, setCurrentScreen] = useState('Inbox'); + const [selectedPatient, setSelectedPatient] = useState(DATA[0]); + const [summaryText, setSummaryText] = useState(''); + const [selectedOutcome, setSelectedOutcome] = useState('Scheduled'); + + // INBOX SCREEN + if (currentScreen === 'Inbox') { + const renderItem = ({ item }: { item: any }) => ( + { + setSelectedPatient(item); + setCurrentScreen('Detail'); + }} + activeOpacity={0.7} + > + + {item.patientName} + {item.reason} + {item.timestamp} + + + {item.status} + + + ); + + return ( + + 🦷 Dental Inbox (10 Patients) + item.id} + contentContainerStyle={styles.list} + /> + + ); + } + + // DETAIL SCREEN + if (currentScreen === 'Detail') { + return ( + + setCurrentScreen('Inbox')} + > + ← Back to Inbox + + + + {selectedPatient.patientName} + {selectedPatient.phone} + {selectedPatient.reason} + Created: {selectedPatient.timestamp} + + + + πŸ’¬ Messages + {selectedPatient.messages.map((msg: string, index: number) => ( + + {msg} + + ))} + + + setCurrentScreen('Summary')} + > + πŸ“ Create Summary + + + ); + } + + // SUMMARY SCREEN + if (currentScreen === 'Summary') { + const saveSummary = () => { + if (!summaryText.trim()) { + Alert.alert('❌ Error', 'Please add a summary'); + return; + } + + Alert.alert( + 'βœ… Success!', + `Summary saved for ${selectedPatient.patientName}!\nStatus updated to "Done"`, + [{ text: 'OK', onPress: () => setCurrentScreen('Inbox') }] + ); + }; + + return ( + + setCurrentScreen('Inbox')} + > + ← Back to Inbox + + + + {selectedPatient.patientName} + {selectedPatient.reason} + + + + πŸ“ Call Summary + + + + + πŸ“Š Outcome + {['Scheduled', 'Left Voicemail', 'Needs Follow-up', 'Not Interested'].map((option) => ( + setSelectedOutcome(option)} + > + + {option} + + + ))} + + + + πŸ’Ύ Save & Mark Done + + + ); + } +} + +AppRegistry.registerComponent('main', () => App); + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#f8fafc' }, + header: { fontSize: 24, fontWeight: 'bold', color: 'white', backgroundColor: '#10B981', padding: 20, textAlign: 'center' }, + list: { padding: 16, paddingBottom: 20 }, + item: { + flexDirection: 'row', + backgroundColor: 'white', + padding: 20, + marginBottom: 12, + borderRadius: 16, + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.1, + shadowRadius: 12, + elevation: 4, + alignItems: 'center', + }, + content: { flex: 1 }, + patientName: { fontSize: 20, fontWeight: 'bold', marginBottom: 4, color: '#1e293b' }, + reason: { fontSize: 16, color: '#64748b', marginBottom: 4 }, + timestamp: { fontSize: 14, color: '#94a3b8' }, + status: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, minWidth: 90, alignItems: 'center' }, + statusText: { color: 'white', fontSize: 12, fontWeight: 'bold', textTransform: 'uppercase' }, + backButton: { backgroundColor: '#f1f5f9', padding: 12, borderRadius: 12, margin: 16, alignItems: 'center' }, + backButtonText: { color: '#64748b', fontSize: 16, fontWeight: '600' }, + card: { backgroundColor: 'white', padding: 24, borderRadius: 16, marginBottom: 16, marginHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 12, elevation: 4 }, + phone: { fontSize: 18, color: '#10B981', marginBottom: 8 }, + messagesCard: { backgroundColor: 'white', padding: 24, borderRadius: 16, marginBottom: 16, marginHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 12, elevation: 4 }, + sectionTitle: { fontSize: 20, fontWeight: 'bold', marginBottom: 16, color: '#1e293b' }, + message: { backgroundColor: '#f1f5f9', padding: 12, borderRadius: 12, marginBottom: 8 }, + messageText: { fontSize: 16, color: '#475569' }, + createSummaryBtn: { backgroundColor: '#10B981', padding: 20, borderRadius: 16, alignItems: 'center', marginBottom: 20, marginHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 12, elevation: 4 }, + createSummaryText: { color: 'white', fontSize: 18, fontWeight: 'bold' }, + patientHeader: { backgroundColor: 'white', padding: 24, borderRadius: 16, marginBottom: 16, marginHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 12, elevation: 4 }, + patientReason: { fontSize: 18, color: '#64748b', marginBottom: 8 }, + textarea: { borderWidth: 1, borderColor: '#e2e8f0', borderRadius: 12, padding: 16, fontSize: 16, minHeight: 120, backgroundColor: '#fafbfc', textAlignVertical: 'top', marginHorizontal: 16 }, + outcomeBtn: { backgroundColor: '#f8fafc', padding: 16, borderRadius: 12, marginBottom: 8, borderWidth: 2, borderColor: '#e2e8f0', marginHorizontal: 16 }, + outcomeBtnSelected: { backgroundColor: '#10B981', borderColor: '#059669' }, + outcomeBtnText: { fontSize: 16, color: '#64748b', textAlign: 'center', fontWeight: '500' }, + outcomeBtnTextSelected: { color: 'white', fontWeight: 'bold' }, + saveBtn: { backgroundColor: '#8B5CF6', padding: 20, borderRadius: 16, alignItems: 'center', marginBottom: 20, marginHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 12, elevation: 4 }, + saveBtnText: { color: 'white', fontSize: 18, fontWeight: 'bold' } +}); diff --git a/README.md b/README.md deleted file mode 100644 index 11273f0..0000000 --- a/README.md +++ /dev/null @@ -1,200 +0,0 @@ -# Savvy Agents – React Native Take‑Home Assignment - ---- - -## πŸš€ Getting Started - -### How to Submit - -1. **Fork this repository** to your own GitHub account -2. **Clone your fork** locally -3. **Create a new branch** from `main` (e.g., `feat/your-name-submission`) -4. Build the app with incremental commits on your branch -5. **Push your branch** to your forked repo -6. **Create a Pull Request** from your branch to `main` in your fork - -> ⚠️ **Important**: Do NOT push directly to `main`. Always work on a feature branch and submit via Pull Request. - ---- - -## πŸ“š Learning Resources - -If you're new to Git or React Native, we recommend completing these courses before starting: - -### Git & Version Control -- **[Git Me Some Version Control](https://laracasts.com/series/git-me-some-version-control)** (Laracasts) -- *YouTube Alternative*: [Git and GitHub for Beginners - Crash Course](https://www.youtube.com/watch?v=RGOj5yH7evk) (freeCodeCamp) - -### React Native & Expo -- **[Build Mobile Apps with React Native and Expo](https://laracasts.com/series/build-mobile-apps-with-react-native-and-expo)** (Laracasts) -- *YouTube Alternatives*: - - [React Native Tutorial for Beginners](https://www.youtube.com/watch?v=0-S5a0eXPoc) (Programming with Mosh) - - [React Native Crash Course](https://www.youtube.com/watch?v=VozPNrt-LfE) (Traversy Media) - -> πŸ’‘ If you don't have access to Laracasts, the YouTube alternatives cover the same fundamentals. - ---- - -## Overview - -This assignment is designed to evaluate your fundamentals in **React Native**, your ability to learn independently, and how you structure, commit, and explain your code. - -You will build a small **mobile app** that simulates a dental office inbox where calls/conversations are listed, viewed, and summarized. - -This is **not a trick assignment**. We care more about **clarity, progress, and good habits** than perfection. - ---- - -## What You’ll Build - -A simple React Native app with **3 screens**: - -### 1. Inbox Screen - -* Displays a list of conversations (use fake data from a local JSON file). -* Each item should show: - - * Patient name - * Reason for call - * Timestamp - * Status (New / In Progress / Done) -* Tapping a conversation opens the detail screen. - -### 2. Conversation Detail Screen - -* Shows full details of the conversation: - - * Patient name - * Phone number - * Reason for call - * Created date/time -* Shows a transcript/messages list (fake messages from JSON). -* Button: **β€œCreate Summary”** β†’ navigates to Summary screen. - -### 3. Summary Screen - -* Form to create a call summary: - - * Summary notes (multiline text input) - * Outcome (Scheduled / Left Voicemail / Needs Follow‑up / Not Interested) - * Next action date (simple text or date picker) -* Save button: - - * Saves the summary locally - * Marks the conversation as **Done** - * Navigates back to Inbox - ---- - -## Data & Persistence - -* Start with a local `data.json` file containing ~10 conversations. -* On app load: - - * Load conversations from JSON - * Overlay any saved updates from local storage -* Use **AsyncStorage** (or equivalent) to persist: - - * Summary data - * Updated conversation status -* Data must persist after app reload. - ---- - -## Tech Stack - -You may choose either: - -**Option A (Recommended)** - -* Expo -* React Native -* React Navigation -* AsyncStorage - -**Option B** - -* React Native CLI -* React Navigation -* AsyncStorage - -Notes: - -* Functional components + hooks only -* TypeScript is optional but encouraged -* No backend required - ---- - -## Git & Workflow (Very Important) - -You must **push code incrementally** to GitHub. - -Minimum expected commits (example): - -1. `chore: initialize react native app` -2. `feat: add inbox screen with conversation list` -3. `feat: add navigation and conversation detail screen` -4. `feat: add summary screen and form` -5. `feat: persist data using asyncstorage` -6. `ui: improve styling and status badge` -7. `fix: handle loading and empty states` -8. `docs: add readme and setup instructions` - -Additional requirements: - -* Push code regularly (at least 2–3 times per week) -* Use clear, meaningful commit messages -* Create at least **one pull request** into `main` (even if it’s your own repo) - ---- - -## UI Expectations - -* Clean and readable UI -* Consistent spacing and typography -* Status should be visually distinguishable (badge or label) -* No need for fancy animations - ---- - -## Stretch Goals (Optional – pick 1 or 2) - -* Search conversations by patient name -* Filter by status (All / New / Done) -* Sort by newest -* Basic form validation -* Ability to edit a saved summary - ---- - -## Deliverables - -1. **GitHub repository link** (with visible commit history) -2. **README** containing: - - * Setup instructions - * Brief explanation of app structure - * What you would improve if you had more time -3. **Short demo video (2–4 minutes)** showing: - - * App navigation - * Creating and saving a summary - * Persistence after reload - ---- - -## Time & Expectations - -* Expected effort: **6–10 hours for MVP** -* With polish/stretch goals: **10–20 hours** -* Work at your own pace. Once you feel the assignment is complete and polished, share the GitHub repository URL. - -We care most about: - -* Learning ability -* Code clarity -* Git discipline -* Communication - -Good luck β€” treat this like a real task πŸš€ diff --git a/app.json b/app.json new file mode 100644 index 0000000..3389ed7 --- /dev/null +++ b/app.json @@ -0,0 +1,10 @@ +{ + "expo": { + "name": "Dental Inbox", + "slug": "dental-inbox", + "version": "1.0.0", + "sdkVersion": "54.0.0", + "platforms": ["ios", "android", "web"], + "assetBundlePatterns": ["**/*"] + } +} diff --git a/data.json b/data.json deleted file mode 100644 index 5e2b954..0000000 --- a/data.json +++ /dev/null @@ -1,182 +0,0 @@ -{ - "conversations": [ - { - "id": "1", - "patientName": "Sarah Johnson", - "phone": "512-555-0142", - "reason": "Teeth cleaning appointment", - "createdAt": "2025-01-03T09:15:00Z", - "status": "New", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "Hi, I want to schedule a teeth cleaning.", - "timestamp": "2025-01-03T09:15:00Z" - }, - { - "id": "m2", - "sender": "assistant", - "text": "Sure! Do you have any preferred dates?", - "timestamp": "2025-01-03T09:16:00Z" - } - ] - }, - { - "id": "2", - "patientName": "Michael Chen", - "phone": "737-555-2218", - "reason": "Tooth pain", - "createdAt": "2025-01-04T11:42:00Z", - "status": "In Progress", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "I have a sharp pain in my lower molar.", - "timestamp": "2025-01-04T11:42:00Z" - }, - { - "id": "m2", - "sender": "assistant", - "text": "Sorry to hear that. Is the pain constant or only when biting?", - "timestamp": "2025-01-04T11:44:00Z" - } - ] - }, - { - "id": "3", - "patientName": "Emily Rodriguez", - "phone": "210-555-9081", - "reason": "Insurance question", - "createdAt": "2025-01-05T14:05:00Z", - "status": "New", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "Do you accept Delta Dental PPO?", - "timestamp": "2025-01-05T14:05:00Z" - } - ] - }, - { - "id": "4", - "patientName": "David Miller", - "phone": "469-555-6623", - "reason": "Crown follow-up", - "createdAt": "2025-01-06T10:20:00Z", - "status": "Done", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "I had a crown placed last week and have some sensitivity.", - "timestamp": "2025-01-06T10:20:00Z" - }, - { - "id": "m2", - "sender": "assistant", - "text": "That can be normal. We can schedule a quick follow-up if needed.", - "timestamp": "2025-01-06T10:22:00Z" - } - ] - }, - { - "id": "5", - "patientName": "Aisha Patel", - "phone": "512-555-7749", - "reason": "New patient appointment", - "createdAt": "2025-01-07T16:30:00Z", - "status": "New", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "I’m a new patient and would like to book an appointment.", - "timestamp": "2025-01-07T16:30:00Z" - } - ] - }, - { - "id": "6", - "patientName": "Robert Thompson", - "phone": "830-555-1190", - "reason": "Reschedule appointment", - "createdAt": "2025-01-08T08:55:00Z", - "status": "In Progress", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "I need to reschedule my appointment tomorrow.", - "timestamp": "2025-01-08T08:55:00Z" - } - ] - }, - { - "id": "7", - "patientName": "Jessica Nguyen", - "phone": "972-555-4821", - "reason": "Whitening consultation", - "createdAt": "2025-01-09T13:10:00Z", - "status": "New", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "Do you offer teeth whitening?", - "timestamp": "2025-01-09T13:10:00Z" - } - ] - }, - { - "id": "8", - "patientName": "Carlos Martinez", - "phone": "956-555-3304", - "reason": "Broken filling", - "createdAt": "2025-01-10T17:45:00Z", - "status": "In Progress", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "My filling fell out while eating.", - "timestamp": "2025-01-10T17:45:00Z" - } - ] - }, - { - "id": "9", - "patientName": "Lauren Wilson", - "phone": "512-555-6012", - "reason": "Billing question", - "createdAt": "2025-01-11T12:00:00Z", - "status": "New", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "I have a question about my last invoice.", - "timestamp": "2025-01-11T12:00:00Z" - } - ] - }, - { - "id": "10", - "patientName": "Brian O'Connor", - "phone": "214-555-9987", - "reason": "Emergency appointment", - "createdAt": "2025-01-12T07:25:00Z", - "status": "New", - "messages": [ - { - "id": "m1", - "sender": "patient", - "text": "I chipped my tooth this morning and need to be seen ASAP.", - "timestamp": "2025-01-12T07:25:00Z" - } - ] - } - ] -} diff --git a/package.json b/package.json new file mode 100644 index 0000000..abb30ef --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "react-native-assignment", + "version": "1.0.0", + "main": "App.tsx", + "scripts": { + "start": "expo start", + "android": "expo start --android", + "ios": "expo start --ios" + }, + "dependencies": { + "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/native": "^6.1.9", + "@react-navigation/stack": "^6.3.20", + "@types/react": "~19.1.10", + "expo": "~54.0.31", + "react": "19.1.0", + "react-native": "0.81.5", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", + "typescript": "~5.9.2" + }, + "devDependencies": { + "@babel/core": "^7.20.0" + } +} diff --git a/src/data.json.txt b/src/data.json.txt new file mode 100644 index 0000000..fde8574 --- /dev/null +++ b/src/data.json.txt @@ -0,0 +1,12 @@ +[ + {"id":"1","patientName":"John Doe","phone":"+91-9876543210","reason":"Teeth cleaning","timestamp":"Jan 16, 9:30 AM","status":"New","messages":["Need teeth cleaning appointment","Routine cleaning scheduled"]}, + {"id":"2","patientName":"Jane Smith","phone":"+91-9876543211","reason":"Root canal pain","timestamp":"Jan 16, 10:15 AM","status":"New","messages":["Severe tooth pain, urgent","Root canal needed"]}, + {"id":"3","patientName":"Raj Patel","phone":"+91-9876543212","reason":"Braces consult","timestamp":"Jan 15, 2:30 PM","status":"In Progress","messages":["Braces treatment inquiry","Consultation ongoing"]}, + {"id":"4","patientName":"Priya Sharma","phone":"+91-9876543213","reason":"Regular checkup","timestamp":"Jan 14, 11:00 AM","status":"Done","messages":["Routine checkup completed","All good"]}, + {"id":"5","patientName":"Amit Kumar","phone":"+91-9876543214","reason":"Tooth extraction","timestamp":"Jan 16, 1:45 PM","status":"New","messages":["Need extraction consultation","Tooth pain increasing"]}, + {"id":"6","patientName":"Sita Devi","phone":"+91-9876543215","reason":"Filling needed","timestamp":"Jan 13, 3:20 PM","status":"New","messages":["Cavity filling required","Right side molar"]}, + {"id":"7","patientName":"Ravi Gupta","phone":"+91-9876543216","reason":"Whitening","timestamp":"Jan 12, 4:10 PM","status":"In Progress","messages":["Teeth whitening inquiry","Treatment started"]}, + {"id":"8","patientName":"Neha Reddy","phone":"+91-9876543217","reason":"Wisdom tooth","timestamp":"Jan 15, 5:30 PM","status":"New","messages":["Wisdom tooth pain","Lower right side"]}, + {"id":"9","patientName":"Vikram Singh","phone":"+91-9876543218","reason":"Dental x-ray","timestamp":"Jan 14, 10:45 AM","status":"Done","messages":["X-ray results ready","No issues found"]}, + {"id":"10","patientName":"Lakshmi Nair","phone":"+91-9876543219","reason":"Emergency","timestamp":"Jan 16, 8:20 AM","status":"New","messages":["Emergency appointment needed","Broken tooth"]} +] diff --git a/src/hooks/useConversations.ts.txt b/src/hooks/useConversations.ts.txt new file mode 100644 index 0000000..1a5df5e --- /dev/null +++ b/src/hooks/useConversations.ts.txt @@ -0,0 +1,25 @@ +import { useEffect, useState } from 'react'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import data from '../data.json'; + +export const useConversations = () => { + const [conversations, setConversations] = useState(data); + + useEffect(() => { + const loadConversations = async () => { + try { + const saved = await AsyncStorage.getItem('conversations'); + if (saved) { + setConversations(JSON.parse(saved)); + } + } catch (error) { + console.log('Failed to load conversations:', error); + } + }; + loadConversations(); + }, []); + + return conversations; +}; + + diff --git a/src/screens/DetailScreen.tsx.txt b/src/screens/DetailScreen.tsx.txt new file mode 100644 index 0000000..8aa6520 --- /dev/null +++ b/src/screens/DetailScreen.tsx.txt @@ -0,0 +1,110 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, SafeAreaView } from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { RouteProp, useRoute, useNavigation } from '@react-navigation/native'; + +type RootStackParamList = { + Detail: { conversationId: string }; + Summary: { conversationId: string }; +}; + +type Props = { + route: RouteProp; +}; + +export default function DetailScreen({ route }: Props) { + const navigation = useNavigation(); + const { conversationId } = route.params; + const [conversation, setConversation] = useState(null); + + useEffect(() => { + loadConversation(); + }, []); + + const loadConversation = async () => { + try { + const saved = await AsyncStorage.getItem('conversations'); + const conversations = saved ? JSON.parse(saved) : require('../data.json'); + const conv = conversations.find((c: any) => c.id === conversationId); + setConversation(conv); + } catch (error) { + console.log('Load conversation failed'); + } + }; + + if (!conversation) return null; + + return ( + + + + {conversation.patientName} + + πŸ“ž Phone: + {conversation.phone} + + + 🎯 Reason: + {conversation.reason} + + + πŸ“… Time: + {conversation.timestamp} + + + πŸ“Š Status: + {conversation.status} + + + + πŸ’¬ Messages: + {conversation.messages.map((msg: string, index: number) => ( + + {msg} + + ))} + + + + + navigation.navigate('Summary', { conversationId })} + > + πŸ“ Create Summary + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#f8fafc' }, + scrollView: { flex: 1, padding: 20 }, + card: { + backgroundColor: 'white', + padding: 24, + borderRadius: 20, + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.1, + shadowRadius: 12, + elevation: 4, + }, + patientName: { fontSize: 28, fontWeight: 'bold', color: '#1e293b', marginBottom: 24, textAlign: 'center' }, + infoRow: { flexDirection: 'row', marginBottom: 16, alignItems: 'center' }, + label: { fontSize: 16, fontWeight: '600', color: '#64748b', minWidth: 100 }, + info: { fontSize: 16, color: '#1e293b', flex: 1 }, + status: { fontSize: 16, fontWeight: 'bold' }, + section: { marginTop: 24 }, + sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#1e293b', marginBottom: 16 }, + message: { backgroundColor: '#f1f5f9', padding: 16, borderRadius: 12, marginBottom: 12 }, + messageText: { fontSize: 16, color: '#1e293b' }, + createSummaryButton: { + backgroundColor: '#10B981', + padding: 20, + borderRadius: 16, + margin: 20, + alignItems: 'center' + }, + createSummaryButtonText: { color: 'white', fontSize: 18, fontWeight: 'bold' } +}); diff --git a/src/screens/InboxScreen.tsx.txt b/src/screens/InboxScreen.tsx.txt new file mode 100644 index 0000000..4f00875 --- /dev/null +++ b/src/screens/InboxScreen.tsx.txt @@ -0,0 +1,129 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + FlatList, + TouchableOpacity, + StyleSheet, + Alert +} from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import data from '../data.json'; + +const getStatusColor = (status: string) => { + switch (status) { + case 'New': return '#10B981'; + case 'In Progress': return '#F59E0B'; + case 'Done': return '#8B5CF6'; + default: return '#6B7280'; + } +}; + +export default function InboxScreen({ navigation }: any) { + const [conversations, setConversations] = useState(data); + + useEffect(() => { + loadConversations(); + }, []); + + const loadConversations = async () => { + try { + const saved = await AsyncStorage.getItem('conversations'); + if (saved) { + setConversations(JSON.parse(saved)); + } + } catch (error) { + console.log('Load error:', error); + } + }; + + const handlePress = (conversation: any) => { + console.log('Navigating to Detail:', conversation.id); + navigation.navigate('Detail', { conversation }); + }; + + const renderItem = ({ item }: { item: any }) => ( + handlePress(item)} + activeOpacity={0.7} + > + + {item.patientName} + {item.reason} + {item.timestamp} + + + {item.status} + + + ); + + return ( + + item.id} + contentContainerStyle={styles.list} + showsVerticalScrollIndicator={false} + /> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f8fafc', + paddingTop: 10 + }, + list: { + padding: 16, + paddingBottom: 20 + }, + item: { + flexDirection: 'row', + backgroundColor: 'white', + padding: 20, + marginBottom: 12, + borderRadius: 16, + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.1, + shadowRadius: 12, + elevation: 4, + alignItems: 'center', + }, + content: { + flex: 1 + }, + patientName: { + fontSize: 20, + fontWeight: 'bold', + marginBottom: 4, + color: '#1e293b' + }, + reason: { + fontSize: 16, + color: '#64748b', + marginBottom: 4 + }, + timestamp: { + fontSize: 14, + color: '#94a3b8' + }, + status: { + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 20, + minWidth: 90, + alignItems: 'center' + }, + statusText: { + color: 'white', + fontSize: 12, + fontWeight: 'bold', + textTransform: 'uppercase' + } +}); diff --git a/src/screens/SummaryScreen.tsx.txt b/src/screens/SummaryScreen.tsx.txt new file mode 100644 index 0000000..da40c1a --- /dev/null +++ b/src/screens/SummaryScreen.tsx.txt @@ -0,0 +1,323 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + TextInput, + TouchableOpacity, + StyleSheet, + ScrollView, + Alert, + Switch +} from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +export default function SummaryScreen({ route, navigation }: any) { + const { conversation } = route.params; + const [summary, setSummary] = useState(''); + const [outcome, setOutcome] = useState('Scheduled'); + const [nextActionDate, setNextActionDate] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + // Load existing summary if available + useEffect(() => { + loadSummary(); + }, []); + + const loadSummary = async () => { + try { + const saved = await AsyncStorage.getItem('conversations'); + if (saved) { + const conversations = JSON.parse(saved); + const found = conversations.find((c: any) => c.id === conversation.id); + if (found?.summary) { + setSummary(found.summary.notes || ''); + setOutcome(found.summary.outcome || 'Scheduled'); + setNextActionDate(found.summary.nextActionDate || ''); + } + } + } catch (error) { + console.log('Load summary error:', error); + } + }; + + const saveSummary = async () => { + if (!summary.trim()) { + Alert.alert('❌ Error', 'Please add a summary of the call'); + return; + } + + setIsLoading(true); + try { + // Create summary object + const summaryData = { + notes: summary, + outcome, + nextActionDate: nextActionDate || 'None', + savedAt: new Date().toLocaleString('en-IN', { + day: '2-digit', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }) + }; + + // Update conversation + const updatedConversation = { + ...conversation, + status: 'Done', + summary: summaryData + }; + + // Save to AsyncStorage + const saved = await AsyncStorage.getItem('conversations'); + const conversations = saved ? JSON.parse(saved) : []; + + const existingIndex = conversations.findIndex((c: any) => c.id === conversation.id); + if (existingIndex !== -1) { + conversations[existingIndex] = updatedConversation; + } else { + conversations.push(updatedConversation); + } + + await AsyncStorage.setItem('conversations', JSON.stringify(conversations)); + + Alert.alert( + 'βœ… Success!', + `Summary saved for ${conversation.patientName}\nStatus updated to "Done"`, + [{ text: 'OK', onPress: () => navigation.navigate('Inbox') }] + ); + } catch (error) { + Alert.alert('❌ Error', 'Failed to save summary. Please try again.'); + console.log('Save error:', error); + } finally { + setIsLoading(false); + } + }; + + return ( + + {/* Patient Info Header */} + + {conversation.patientName} + {conversation.reason} + πŸ“ž {conversation.phone} + + + {/* Summary Notes */} + + πŸ“ Call Summary + + + + {/* Outcome Selection */} + + πŸ“Š Call Outcome + + {[ + { label: 'βœ… Scheduled', value: 'Scheduled', color: '#10B981' }, + { label: 'πŸ“ž Left Voicemail', value: 'Left Voicemail', color: '#F59E0B' }, + { label: '⏳ Needs Follow-up', value: 'Needs Follow-up', color: '#3B82F6' }, + { label: '❌ Not Interested', value: 'Not Interested', color: '#EF4444' } + ].map((option) => ( + setOutcome(option.value)} + disabled={isLoading} + > + + {option.label} + + + ))} + + + + {/* Next Action */} + + πŸ“… Next Action Date + + Leave empty if no follow-up needed + + + {/* Save Button */} + + + {isLoading ? 'πŸ’Ύ Saving...' : 'πŸ’Ύ Save Summary & Mark Done'} + + + + {/* Cancel Button */} + navigation.goBack()} + disabled={isLoading} + > + ❌ Cancel + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f8fafc', + padding: 16, + paddingTop: 10, + }, + patientHeader: { + backgroundColor: 'white', + padding: 24, + borderRadius: 16, + marginBottom: 16, + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.1, + shadowRadius: 12, + elevation: 4, + }, + patientName: { + fontSize: 28, + fontWeight: 'bold', + color: '#1e293b', + marginBottom: 4, + }, + patientReason: { + fontSize: 18, + color: '#64748b', + marginBottom: 8, + }, + patientPhone: { + fontSize: 16, + color: '#10B981', + fontWeight: '500', + }, + card: { + backgroundColor: 'white', + padding: 20, + borderRadius: 16, + marginBottom: 16, + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.1, + shadowRadius: 12, + elevation: 4, + }, + sectionTitle: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 12, + color: '#1e293b', + }, + textarea: { + borderWidth: 1, + borderColor: '#e2e8f0', + borderRadius: 12, + padding: 16, + fontSize: 16, + minHeight: 120, + backgroundColor: '#fafbfc', + }, + input: { + borderWidth: 1, + borderColor: '#e2e8f0', + borderRadius: 12, + padding: 16, + fontSize: 16, + backgroundColor: '#fafbfc', + }, + outcomeContainer: { + gap: 12, + }, + outcomeBtn: { + backgroundColor: '#f8fafc', + padding: 16, + borderRadius: 12, + borderWidth: 2, + borderColor: '#e2e8f0', + }, + outcomeBtnSelected: { + backgroundColor: '#10B981', + borderColor: '#059669', + shadowColor: '#10B981', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 3, + }, + outcomeBtnText: { + fontSize: 16, + color: '#64748b', + textAlign: 'center', + fontWeight: '500', + }, + outcomeBtnTextSelected: { + color: 'white', + fontWeight: 'bold', + }, + helperText: { + fontSize: 14, + color: '#94a3b8', + marginTop: 8, + }, + saveBtn: { + backgroundColor: '#8B5CF6', + padding: 20, + borderRadius: 16, + alignItems: 'center', + marginBottom: 12, + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.2, + shadowRadius: 12, + elevation: 4, + }, + saveBtnDisabled: { + backgroundColor: '#a5b4fc', + }, + saveBtnText: { + color: 'white', + fontSize: 18, + fontWeight: 'bold', + }, + cancelBtn: { + backgroundColor: '#f1f5f9', + padding: 16, + borderRadius: 12, + borderWidth: 1, + borderColor: '#e2e8f0', + alignItems: 'center', + }, + cancelBtnText: { + color: '#64748b', + fontSize: 16, + fontWeight: '600', + }, +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0e6371f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": {}, + "extends": "expo/tsconfig.base" +}