From 3066478ea7acb451551b7586e1c1f6a7bf676a97 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:01:16 +0000 Subject: [PATCH] refactor(phonebook): extract components and types to reduce screen complexity Extracted `ContactRow` and `ContactEditModal` into standalone files in `src/components/`, and moved related types and constants (`Contact`, `CATEGORIES`, `CATEGORY_COLORS`, `genId`) to `src/types/phonebook.ts`. This vastly reduces the size and complexity of `PhonebookScreen.tsx` while preserving all existing functionality and improving readability. Co-authored-by: TargetMisser <52361977+TargetMisser@users.noreply.github.com> --- package-lock.json | 51 +++-- src/components/ContactEditModal.tsx | 180 ++++++++++++++++++ src/components/ContactRow.tsx | 85 +++++++++ src/screens/PhonebookScreen.tsx | 282 +--------------------------- src/types/phonebook.ts | 22 +++ 5 files changed, 315 insertions(+), 305 deletions(-) create mode 100644 src/components/ContactEditModal.tsx create mode 100644 src/components/ContactRow.tsx create mode 100644 src/types/phonebook.ts diff --git a/package-lock.json b/package-lock.json index a3bbe99..9b54474 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@expo/vector-icons": "^15.0.3", "@react-native-async-storage/async-storage": "2.2.0", - "@react-native-picker/picker": "2.11.1", + "@react-native-picker/picker": "2.11.4", "@types/tesseract.js": "^0.0.2", "expo": "~54.0.0", "expo-blur": "~15.0.8", @@ -23,18 +23,19 @@ "expo-linear-gradient": "~15.0.8", "expo-location": "~19.0.8", "expo-notifications": "~0.32.16", + "expo-secure-store": "~15.0.5", "expo-status-bar": "~3.0.9", "react": "19.1.0", "react-native": "0.81.5", "react-native-android-widget": "^0.20.1", "react-native-calendars": "^1.1314.0", - "react-native-webview": "13.15.0", + "react-native-webview": "13.16.1", "tesseract.js": "^7.0.0" }, "devDependencies": { "@react-native-community/cli": "^20.1.3", "@types/react": "~19.1.10", - "pdfjs-dist": "^5.5.207", + "pdfjs-dist": "^5.6.205", "typescript": "~5.9.2" } }, @@ -80,7 +81,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1492,7 +1492,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -2431,7 +2430,6 @@ "integrity": "sha512-sLo8cu9JyFNfuuF1C+8NJ4DHE/PEFaXGd4enkcxi/OJjGG8+sOQrdjNQ4i+cVh/2c+ah1mEMwsYjc3z0+/MqSg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-clean": "20.1.3", "@react-native-community/cli-config": "20.1.3", @@ -2996,9 +2994,9 @@ } }, "node_modules/@react-native-picker/picker": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.1.tgz", - "integrity": "sha512-ThklnkK4fV3yynnIIRBkxxjxR4IFbdMNJVF6tlLdOJ/zEFUEFUEdXY0KmH0iYzMwY8W4/InWsLiA7AkpAbnexA==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.4.tgz", + "integrity": "sha512-Kf8h1AMnBo54b1fdiVylP2P/iFcZqzpMYcglC28EEFB1DEnOjsNr6Ucqc+3R9e91vHxEDnhZFbYDmAe79P2gjA==", "license": "MIT", "workspaces": [ "example" @@ -3399,7 +3397,6 @@ "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4109,7 +4106,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5014,7 +5010,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz", "integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.23", @@ -5130,7 +5125,6 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -5239,6 +5233,15 @@ "react-native": "*" } }, + "node_modules/expo-secure-store": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-15.0.8.tgz", + "integrity": "sha512-lHnzvRajBu4u+P99+0GEMijQMFCOYpWRO4dWsXSuMt77+THPIGjzNvVKrGSl6mMrLsfVaKL8BpwYZLGlgA+zAw==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-server": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-1.0.5.tgz", @@ -8412,16 +8415,16 @@ } }, "node_modules/pdfjs-dist": { - "version": "5.5.207", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.5.207.tgz", - "integrity": "sha512-WMqqw06w1vUt9ZfT0gOFhMf3wHsWhaCrxGrckGs5Cci6ybDW87IvPaOd2pnBwT6BJuP/CzXDZxjFgmSULLdsdw==", + "version": "5.6.205", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.6.205.tgz", + "integrity": "sha512-tlUj+2IDa7G1SbvBNN74UHRLJybZDWYom+k6p5KIZl7huBvsA4APi6mKL+zCxd3tLjN5hOOEE9Tv7VdzO88pfg==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=20.19.0 || >=22.13.0 || >=24" }, "optionalDependencies": { - "@napi-rs/canvas": "^0.1.95", + "@napi-rs/canvas": "^0.1.96", "node-readable-to-web-readable-stream": "^0.4.2" } }, @@ -8436,7 +8439,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8716,7 +8718,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8742,7 +8743,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", @@ -8849,11 +8849,10 @@ "license": "MIT" }, "node_modules/react-native-webview": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz", - "integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==", + "version": "13.16.1", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.1.tgz", + "integrity": "sha512-If0eHhoEdOYDcHsX+xBFwHMbWBGK1BvGDQDQdVkwtSIXiq1uiqjkpWVP2uQ1as94J0CzvFE9PUNDuhiX0Z6ubw==", "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" @@ -8952,7 +8951,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10211,9 +10209,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/components/ContactEditModal.tsx b/src/components/ContactEditModal.tsx new file mode 100644 index 0000000..b8568fc --- /dev/null +++ b/src/components/ContactEditModal.tsx @@ -0,0 +1,180 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { + View, Text, StyleSheet, TouchableOpacity, ScrollView, + TextInput, Alert, Modal, Platform, KeyboardAvoidingView, +} from 'react-native'; +import { MaterialIcons } from '@expo/vector-icons'; +import { useAppTheme, type ThemeColors } from '../context/ThemeContext'; +import { useLanguage } from '../context/LanguageContext'; +import { Contact, CATEGORIES, CATEGORY_COLORS, genId } from '../types/phonebook'; + +interface EditModalProps { + visible: boolean; + contact: Partial | null; + onSave: (c: Contact) => void; + onClose: () => void; +} + +function makeModalStyles(c: ThemeColors) { + return StyleSheet.create({ + overlay: { + flex: 1, justifyContent: 'flex-end', + backgroundColor: 'rgba(15,23,42,0.5)', + }, + scrollContent: { flexGrow: 1, justifyContent: 'flex-end' }, + sheet: { + backgroundColor: c.card, + borderTopLeftRadius: 24, borderTopRightRadius: 24, + padding: 20, paddingBottom: 36, maxHeight: '92%', + }, + handle: { + width: 40, height: 4, borderRadius: 2, + backgroundColor: c.border, + alignSelf: 'center', marginBottom: 18, + }, + title: { fontSize: 18, fontWeight: '700', color: c.primaryDark, marginBottom: 16 }, + label: { fontSize: 12, fontWeight: '600', color: c.textSub, marginBottom: 6, marginTop: 12 }, + input: { + borderWidth: 1.5, borderColor: c.border, borderRadius: 12, + paddingHorizontal: 14, paddingVertical: 11, + fontSize: 15, color: c.text, backgroundColor: c.cardSecondary, + }, + catRow: { marginBottom: 4 }, + catChip: { + paddingHorizontal: 14, paddingVertical: 7, + borderRadius: 20, borderWidth: 1.5, borderColor: c.border, + marginRight: 8, backgroundColor: c.card, + }, + catTxt: { fontSize: 12, fontWeight: '600', color: c.textSub }, + actions: { flexDirection: 'row', gap: 10, marginTop: 20 }, + cancelBtn: { + flex: 1, borderWidth: 1.5, borderColor: c.border, + borderRadius: 12, paddingVertical: 13, alignItems: 'center', + }, + cancelTxt: { fontSize: 14, fontWeight: '600', color: c.textSub }, + saveBtn: { + flex: 2, backgroundColor: c.primary, + borderRadius: 12, paddingVertical: 13, + flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, + }, + saveTxt: { fontSize: 14, fontWeight: '700', color: '#fff' }, + }); +} + +export function ContactEditModal({ visible, contact, onSave, onClose }: EditModalProps) { + const { colors } = useAppTheme(); + const { t } = useLanguage(); + const modalStyles = useMemo(() => makeModalStyles(colors), [colors]); + const [name, setName] = useState(''); + const [number, setNumber] = useState(''); + const [category, setCategory] = useState(CATEGORIES[0]); + const [note, setNote] = useState(''); + + useEffect(() => { + if (contact) { + setName(contact.name ?? ''); + setNumber(contact.number ?? ''); + setCategory(contact.category ?? CATEGORIES[0]); + setNote(contact.note ?? ''); + } else { + setName(''); setNumber(''); setCategory(CATEGORIES[0]); setNote(''); + } + }, [contact, visible]); + + const handleSave = () => { + if (!name.trim() || !number.trim()) { + Alert.alert(t('contactErrReqTitle'), t('contactErrReqMsg')); + return; + } + onSave({ + id: contact?.id ?? genId(), + name: name.trim(), + number: number.trim(), + category, + note: note.trim(), + }); + }; + + return ( + + + + + + + + {contact?.id ? t('contactModalEdit') : t('contactModalNew')} + + + {/* Nome */} + {t('contactNameLabel')} + + + {/* Numero */} + {t('contactNumberLabel')} + + + {/* Categoria */} + {t('contactCatLabel')} + + {CATEGORIES.map(cat => ( + setCategory(cat)} + > + + {cat} + + + ))} + + + {/* Nota */} + {t('contactNoteLabel')} + + + {/* Azioni */} + + + Annulla + + + + Salva + + + + + + + ); +} diff --git a/src/components/ContactRow.tsx b/src/components/ContactRow.tsx new file mode 100644 index 0000000..41d5c63 --- /dev/null +++ b/src/components/ContactRow.tsx @@ -0,0 +1,85 @@ +import React, { useMemo } from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Linking, Alert } from 'react-native'; +import { MaterialIcons } from '@expo/vector-icons'; +import { useAppTheme, type ThemeColors } from '../context/ThemeContext'; +import { useLanguage } from '../context/LanguageContext'; +import { Contact, CATEGORY_COLORS } from '../types/phonebook'; + +function makeRowStyles(c: ThemeColors) { + return StyleSheet.create({ + card: { + flexDirection: 'row', alignItems: 'center', + backgroundColor: c.card, + borderRadius: 14, padding: 14, marginBottom: 10, + shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, + shadowOpacity: c.isDark ? 0 : 0.05, shadowRadius: 4, elevation: c.isDark ? 0 : 2, borderWidth: c.isDark ? 1 : 0, borderColor: c.border, + }, + dot: { width: 4, borderRadius: 2, alignSelf: 'stretch', marginRight: 12 }, + info: { flex: 1 }, + topRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 3 }, + name: { fontSize: 14, fontWeight: '700', color: c.text, flex: 1 }, + badge: { + paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, + }, + badgeTxt: { fontSize: 10, fontWeight: '700' }, + number: { fontSize: 13, color: c.textSub, fontWeight: '500' }, + note: { fontSize: 11, color: c.textMuted, marginTop: 2 }, + callBtn: { + width: 36, height: 36, borderRadius: 18, + justifyContent: 'center', alignItems: 'center', marginLeft: 8, + }, + editBtn: { padding: 6, marginLeft: 2 }, + }); +} + +interface ContactRowProps { + contact: Contact; + onEdit: (c: Contact) => void; + onDelete: (id: string) => void; +} + +export function ContactRow({ contact, onEdit, onDelete }: ContactRowProps) { + const { colors } = useAppTheme(); + const { t } = useLanguage(); + const rowStyles = useMemo(() => makeRowStyles(colors), [colors]); + const color = CATEGORY_COLORS[contact.category] ?? '#64748B'; + + const call = () => { + const url = `tel:${contact.number.replace(/\s/g, '')}`; + Linking.canOpenURL(url).then(ok => { + if (ok) Linking.openURL(url); + }); + }; + + const confirmDelete = () => { + Alert.alert(t('contactDeleteTitle'), `${t('contactDeleteTitle')} "${contact.name}"?`, [ + { text: 'Annulla', style: 'cancel' }, + { text: t('contactDeleteConfirm'), style: 'destructive', onPress: () => onDelete(contact.id) }, + ]); + }; + + return ( + + + + + {contact.name} + + {contact.category} + + + {contact.number} + {!!contact.note && {contact.note}} + + + + + onEdit(contact)}> + + + + + + + ); +} diff --git a/src/screens/PhonebookScreen.tsx b/src/screens/PhonebookScreen.tsx index b176f21..021ed49 100644 --- a/src/screens/PhonebookScreen.tsx +++ b/src/screens/PhonebookScreen.tsx @@ -1,292 +1,18 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, - TextInput, Alert, Modal, Linking, Platform, KeyboardAvoidingView, + TextInput } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { MaterialIcons } from '@expo/vector-icons'; import { useAppTheme, type ThemeColors } from '../context/ThemeContext'; import { useLanguage } from '../context/LanguageContext'; +import { Contact, CATEGORIES, CATEGORY_COLORS } from '../types/phonebook'; +import { ContactRow } from '../components/ContactRow'; +import { ContactEditModal as EditModal } from '../components/ContactEditModal'; const STORAGE_KEY = 'aerostaff_phonebook_v1'; -type Contact = { - id: string; - name: string; - number: string; - category: string; - note: string; -}; - -const CATEGORIES = ['Ops', 'Handling', 'Compagnia', 'Aeroporto', 'Hotel', 'Altro']; - -const CATEGORY_COLORS: Record = { - 'Ops': '#F47B16', - 'Handling': '#16A34A', - 'Compagnia': '#FF6600', - 'Aeroporto': '#7C3AED', - 'Hotel': '#DB2777', - 'Altro': '#64748B', -}; - -function genId() { - return Math.random().toString(36).slice(2) + Date.now().toString(36); -} - -// ─── Modal di aggiunta/modifica ─────────────────────────────────────────────── -interface EditModalProps { - visible: boolean; - contact: Partial | null; - onSave: (c: Contact) => void; - onClose: () => void; -} - -function makeModalStyles(c: ThemeColors) { - return StyleSheet.create({ - overlay: { - flex: 1, justifyContent: 'flex-end', - backgroundColor: 'rgba(15,23,42,0.5)', - }, - scrollContent: { flexGrow: 1, justifyContent: 'flex-end' }, - sheet: { - backgroundColor: c.card, - borderTopLeftRadius: 24, borderTopRightRadius: 24, - padding: 20, paddingBottom: 36, maxHeight: '92%', - }, - handle: { - width: 40, height: 4, borderRadius: 2, - backgroundColor: c.border, - alignSelf: 'center', marginBottom: 18, - }, - title: { fontSize: 18, fontWeight: '700', color: c.primaryDark, marginBottom: 16 }, - label: { fontSize: 12, fontWeight: '600', color: c.textSub, marginBottom: 6, marginTop: 12 }, - input: { - borderWidth: 1.5, borderColor: c.border, borderRadius: 12, - paddingHorizontal: 14, paddingVertical: 11, - fontSize: 15, color: c.text, backgroundColor: c.cardSecondary, - }, - catRow: { marginBottom: 4 }, - catChip: { - paddingHorizontal: 14, paddingVertical: 7, - borderRadius: 20, borderWidth: 1.5, borderColor: c.border, - marginRight: 8, backgroundColor: c.card, - }, - catTxt: { fontSize: 12, fontWeight: '600', color: c.textSub }, - actions: { flexDirection: 'row', gap: 10, marginTop: 20 }, - cancelBtn: { - flex: 1, borderWidth: 1.5, borderColor: c.border, - borderRadius: 12, paddingVertical: 13, alignItems: 'center', - }, - cancelTxt: { fontSize: 14, fontWeight: '600', color: c.textSub }, - saveBtn: { - flex: 2, backgroundColor: c.primary, - borderRadius: 12, paddingVertical: 13, - flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, - }, - saveTxt: { fontSize: 14, fontWeight: '700', color: '#fff' }, - }); -} - -function EditModal({ visible, contact, onSave, onClose }: EditModalProps) { - const { colors } = useAppTheme(); - const { t } = useLanguage(); - const modalStyles = useMemo(() => makeModalStyles(colors), [colors]); - const [name, setName] = useState(''); - const [number, setNumber] = useState(''); - const [category, setCategory] = useState(CATEGORIES[0]); - const [note, setNote] = useState(''); - - useEffect(() => { - if (contact) { - setName(contact.name ?? ''); - setNumber(contact.number ?? ''); - setCategory(contact.category ?? CATEGORIES[0]); - setNote(contact.note ?? ''); - } else { - setName(''); setNumber(''); setCategory(CATEGORIES[0]); setNote(''); - } - }, [contact, visible]); - - const handleSave = () => { - if (!name.trim() || !number.trim()) { - Alert.alert(t('contactErrReqTitle'), t('contactErrReqMsg')); - return; - } - onSave({ - id: contact?.id ?? genId(), - name: name.trim(), - number: number.trim(), - category, - note: note.trim(), - }); - }; - - return ( - - - - - - - - {contact?.id ? t('contactModalEdit') : t('contactModalNew')} - - - {/* Nome */} - {t('contactNameLabel')} - - - {/* Numero */} - {t('contactNumberLabel')} - - - {/* Categoria */} - {t('contactCatLabel')} - - {CATEGORIES.map(cat => ( - setCategory(cat)} - > - - {cat} - - - ))} - - - {/* Nota */} - {t('contactNoteLabel')} - - - {/* Azioni */} - - - Annulla - - - - Salva - - - - - - - ); -} - - -// ─── Riga contatto ──────────────────────────────────────────────────────────── -function makeRowStyles(c: ThemeColors) { - return StyleSheet.create({ - card: { - flexDirection: 'row', alignItems: 'center', - backgroundColor: c.card, - borderRadius: 14, padding: 14, marginBottom: 10, - shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, - shadowOpacity: c.isDark ? 0 : 0.05, shadowRadius: 4, elevation: c.isDark ? 0 : 2, borderWidth: c.isDark ? 1 : 0, borderColor: c.border, - }, - dot: { width: 4, borderRadius: 2, alignSelf: 'stretch', marginRight: 12 }, - info: { flex: 1 }, - topRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 3 }, - name: { fontSize: 14, fontWeight: '700', color: c.text, flex: 1 }, - badge: { - paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, - }, - badgeTxt: { fontSize: 10, fontWeight: '700' }, - number: { fontSize: 13, color: c.textSub, fontWeight: '500' }, - note: { fontSize: 11, color: c.textMuted, marginTop: 2 }, - callBtn: { - width: 36, height: 36, borderRadius: 18, - justifyContent: 'center', alignItems: 'center', marginLeft: 8, - }, - editBtn: { padding: 6, marginLeft: 2 }, - }); -} - -interface ContactRowProps { - contact: Contact; - onEdit: (c: Contact) => void; - onDelete: (id: string) => void; -} - -function ContactRow({ contact, onEdit, onDelete }: ContactRowProps) { - const { colors } = useAppTheme(); - const { t } = useLanguage(); - const rowStyles = useMemo(() => makeRowStyles(colors), [colors]); - const color = CATEGORY_COLORS[contact.category] ?? '#64748B'; - - const call = () => { - const url = `tel:${contact.number.replace(/\s/g, '')}`; - Linking.canOpenURL(url).then(ok => { - if (ok) Linking.openURL(url); - }); - }; - - const confirmDelete = () => { - Alert.alert(t('contactDeleteTitle'), `${t('contactDeleteTitle')} "${contact.name}"?`, [ - { text: 'Annulla', style: 'cancel' }, - { text: t('contactDeleteConfirm'), style: 'destructive', onPress: () => onDelete(contact.id) }, - ]); - }; - - return ( - - - - - {contact.name} - - {contact.category} - - - {contact.number} - {!!contact.note && {contact.note}} - - - - - onEdit(contact)}> - - - - - - - ); -} - - // ─── Main Screen ────────────────────────────────────────────────────────────── function makeStyles(c: ThemeColors) { return StyleSheet.create({ diff --git a/src/types/phonebook.ts b/src/types/phonebook.ts new file mode 100644 index 0000000..fdc916f --- /dev/null +++ b/src/types/phonebook.ts @@ -0,0 +1,22 @@ +export type Contact = { + id: string; + name: string; + number: string; + category: string; + note: string; +}; + +export const CATEGORIES = ['Ops', 'Handling', 'Compagnia', 'Aeroporto', 'Hotel', 'Altro']; + +export const CATEGORY_COLORS: Record = { + 'Ops': '#F47B16', + 'Handling': '#16A34A', + 'Compagnia': '#FF6600', + 'Aeroporto': '#7C3AED', + 'Hotel': '#DB2777', + 'Altro': '#64748B', +}; + +export function genId() { + return Math.random().toString(36).slice(2) + Date.now().toString(36); +}