Minor UI improvements.

This commit is contained in:
Patrick Fic
2025-11-24 10:25:24 -08:00
parent e069fd1170
commit 46b4523ed8
10 changed files with 590 additions and 27 deletions

View File

@@ -6,7 +6,7 @@
"scheme": "imex-mobile-scheme", "scheme": "imex-mobile-scheme",
"userInterfaceStyle": "automatic", "userInterfaceStyle": "automatic",
"extra": { "extra": {
"expover": "21", "expover": "22",
"eas": { "eas": {
"projectId": "ffe01f3a-d507-4698-82cd-da1f1cad450b" "projectId": "ffe01f3a-d507-4698-82cd-da1f1cad450b"
} }

View File

@@ -50,6 +50,7 @@ function JobsStack({ openImagePicker }) {
alignSelf: "center", alignSelf: "center",
justifyContent: "center", justifyContent: "center",
}} }}
mode="outlined"
onPress={handleUpload} onPress={handleUpload}
> >
{t("general.labels.upload")} {t("general.labels.upload")}

View File

@@ -7,6 +7,7 @@ export default function DataLabelComponent({
label, label,
content, content,
dateTime, dateTime,
noTextWrap,
...restProps ...restProps
}) { }) {
let theContent = content; let theContent = content;
@@ -19,7 +20,8 @@ export default function DataLabelComponent({
return ( return (
<View key={key} {...rest} style={{ margin: 4, ...restProps.style }}> <View key={key} {...rest} style={{ margin: 4, ...restProps.style }}>
<Text style={{ fontWeight: "bold" }}>{label}</Text> <Text style={{ fontWeight: "bold" }}>{label}</Text>
<Text>{theContent}</Text>
{noTextWrap ? content : <Text>{theContent}</Text>}
</View> </View>
); );
} }

View File

@@ -5,6 +5,7 @@ import React, { useCallback, useEffect, useState } from "react";
import { FlatList, RefreshControl, TouchableOpacity, View } from "react-native"; import { FlatList, RefreshControl, TouchableOpacity, View } from "react-native";
import ImageView from "react-native-image-viewing"; import ImageView from "react-native-image-viewing";
import { ActivityIndicator, Text } from "react-native-paper"; import { ActivityIndicator, Text } from "react-native-paper";
import { SafeAreaView } from "react-native-safe-area-context";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import env from "../../env"; import env from "../../env";
@@ -149,13 +150,15 @@ export function JobDocumentsComponent({ bodyshop }) {
)} )}
/> />
<ImageView <SafeAreaView>
onRequestClose={() => setPreviewVisible(false)} <ImageView
visible={previewVisible} onRequestClose={() => setPreviewVisible(false)}
images={fullphotos} visible={previewVisible}
imageIndex={imgIndex} images={fullphotos}
swipeToCloseEnabled={true} imageIndex={imgIndex}
/> swipeToCloseEnabled={true}
/>
</SafeAreaView>
</View> </View>
); );
} }

View File

@@ -107,7 +107,7 @@ export const JobStatusSelector = ({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
root: { root: {
alignSelf: "flex-start", alignSelf: "flex-start",
paddingTop: 4, paddingTop: 8,
}, },
trigger: { trigger: {
minWidth: 140, minWidth: 140,

View File

@@ -1,3 +1,4 @@
import env from "@/env";
import { GET_JOB_TOMBSTONE } from "@/graphql/jobs.queries"; import { GET_JOB_TOMBSTONE } from "@/graphql/jobs.queries";
import { selectBodyshop } from "@/redux/user/user.selectors"; import { selectBodyshop } from "@/redux/user/user.selectors";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
@@ -5,15 +6,23 @@ import { useLocalSearchParams, useRouter } from "expo-router";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
Platform,
RefreshControl, RefreshControl,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
TouchableOpacity, TouchableOpacity,
View View,
} from "react-native"; } from "react-native";
import { ActivityIndicator, Card, Chip, Text } from "react-native-paper"; import {
ActivityIndicator,
Button,
Card,
Chip,
Text,
} from "react-native-paper";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { firstNames, getRandomIndex, lastNames } from "../../util/demodata";
import DataLabelComponent from "../data-label/data-label"; import DataLabelComponent from "../data-label/data-label";
import ErrorDisplay from "../error/error-display"; import ErrorDisplay from "../error/error-display";
import { JobStatusSelector } from "../job-status-selector/job-status-selector"; import { JobStatusSelector } from "../job-status-selector/job-status-selector";
@@ -122,8 +131,12 @@ function JobTombstone({ bodyshop }) {
{job.inproduction && ( {job.inproduction && (
<DataLabelComponent <DataLabelComponent
label={t("objects.jobs.fields.inproduction")} label={t("objects.jobs.fields.inproduction")}
noTextWrap
content={ content={
<Chip mode="outlined"> <Chip
style={{ marginTop: 8, maxWidth: "75%" }}
mode="outlined"
>
{t("objects.jobs.labels.inproduction")} {t("objects.jobs.labels.inproduction")}
</Chip> </Chip>
} }
@@ -143,7 +156,12 @@ function JobTombstone({ bodyshop }) {
label={t("objects.jobs.fields.related_ros")} label={t("objects.jobs.fields.related_ros")}
content={ content={
<View <View
style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }} style={{
flexDirection: "row",
flexWrap: "wrap",
gap: 8,
paddingTop: 8,
}}
> >
{job.vehicle?.jobs {job.vehicle?.jobs
?.filter((ro) => ro.id !== job.id) ?.filter((ro) => ro.id !== job.id)
@@ -159,7 +177,9 @@ function JobTombstone({ bodyshop }) {
}} }}
key={ro.id} key={ro.id}
> >
<Chip mode="outlined">{ro.ro_number || "N/A"}</Chip> <Button comp mode="outlined">
{ro.ro_number || "N/A"}
</Button>
</TouchableOpacity> </TouchableOpacity>
))} ))}
</View> </View>
@@ -178,21 +198,33 @@ function JobTombstone({ bodyshop }) {
<View style={localStyles.twoColumnCardColumn}> <View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent <DataLabelComponent
label={t("objects.jobs.fields.owner")} label={t("objects.jobs.fields.owner")}
content={`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${ content={
job.ownr_co_nm || "" env.DEMO_MODE
}`} ? `${firstNames[getRandomIndex()] || ""} ${
lastNames[getRandomIndex()] || ""
} ${job.ownr_co_nm || ""}`
: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`
}
/> />
<DataLabelComponent <DataLabelComponent
label={t("objects.jobs.fields.vehicle")} label={t("objects.jobs.fields.vehicle")}
content={`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ content={
job.v_model_desc || "" env.DEMO_MODE
} - ${job.v_vin}`} ? `${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
job.v_model_desc || ""
} - 1GNDX33L46D168902`
: `${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
job.v_model_desc || ""
} - ${job.v_vin}`
}
/> />
</View> </View>
<View style={localStyles.twoColumnCardColumn}> <View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent <DataLabelComponent
label={t("objects.jobs.fields.ins_co_nm")} label={t("objects.jobs.fields.ins_co_nm")}
content={job.ins_co_nm} content={env.DEMO_MODE ? "ABC Ins." : job.ins_co_nm}
/> />
<DataLabelComponent <DataLabelComponent
label={t("objects.jobs.fields.clm_no")} label={t("objects.jobs.fields.clm_no")}
@@ -281,7 +313,7 @@ function JobTombstone({ bodyshop }) {
</Card.Content> </Card.Content>
</Card> </Card>
<View <View
style={{ height: 64 }} //Spacer style={{ height: Platform.OS === "ios" ? 64 : 96 }} //Spacer
/> />
</ScrollView> </ScrollView>
); );

View File

@@ -1,9 +1,11 @@
import env from "@/env";
import { firstNames, getRandomIndex, lastNames } from "@/util/demodata";
import * as Haptics from "expo-haptics"; import * as Haptics from "expo-haptics";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import React, { memo, useCallback } from "react"; import React, { memo, useCallback } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Pressable, StyleSheet, View } from "react-native"; import { Pressable, StyleSheet, View } from "react-native";
import { Chip, IconButton, Text, useTheme } from "react-native-paper"; import { IconButton, Text, useTheme } from "react-native-paper";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.analytics"; import { logImEXEvent } from "../../firebase/firebase.analytics";
@@ -35,7 +37,11 @@ function JobListItemComponent({ openImagePicker, item }) {
openImagePicker(item.id); openImagePicker(item.id);
}, [openImagePicker, item.id]); }, [openImagePicker, item.id]);
const ownerName = `${item.ownr_fn || ""} ${item.ownr_ln || ""}`.trim(); const ownerName = env.DEMO_MODE
? `${firstNames[getRandomIndex()] || ""} ${
lastNames[getRandomIndex()] || ""
}`
: `${item.ownr_fn || ""} ${item.ownr_ln || ""}`.trim();
const company = item.ownr_co_nm || ""; const company = item.ownr_co_nm || "";
const vehicle = `${item.v_model_yr || ""} ${item.v_make_desc || ""} ${ const vehicle = `${item.v_model_yr || ""} ${item.v_make_desc || ""} ${
item.v_model_desc || "" item.v_model_desc || ""
@@ -92,8 +98,9 @@ function JobListItemComponent({ openImagePicker, item }) {
> >
{vehicle} {vehicle}
</Text> </Text>
</View>
<Chip style>{item.status}</Chip> <View style={styles.body}>
<Text variant="bodyMedium">{item.status}</Text>
</View> </View>
</View> </View>
<IconButton <IconButton

1
env.js
View File

@@ -3,6 +3,7 @@ import * as Updates from "expo-updates";
const ENV = { const ENV = {
test: { test: {
DEMO_MODE: true,
API_URL: "https://api.test.imex.online", API_URL: "https://api.test.imex.online",
uri: "https://db.test.bodyshop.app/v1/graphql", uri: "https://db.test.bodyshop.app/v1/graphql",
wsuri: "wss://db.test.bodyshop.app/v1/graphql", wsuri: "wss://db.test.bodyshop.app/v1/graphql",

View File

@@ -1 +1,4 @@
USE VERSION 16 of NODE LTS. USE VERSION 16 of NODE LTS.
✗ eas update --channel test --message "Message"

514
util/demodata.js Normal file
View File

@@ -0,0 +1,514 @@
const firstNames = [
'Jevon',
'Asa',
'Kaylan',
'Duane',
'Dewayne',
'Nicole',
'Elsa',
'Jamari',
'Tasia',
'Devonte',
'Lazaro',
'Maiya',
'Mikayla',
'Amy',
'Karolina',
'David',
'Nyasia',
'Evelin',
'Naya',
'Trisha',
'Hasan',
'Brenden',
'Braiden',
'Jan',
'Sharon',
'Darrian',
'Kayle',
'Kenny',
'Lewis',
'Keyla',
'Keshon',
'Stephen',
'Drake',
'Hayley',
'Edmund',
'Jerrod',
'Lara',
'Jaydon',
'Jami',
'Myra',
'Breana',
'Rita',
'Bronson',
'Bailee',
'Edwin',
'Savana',
'Rylan',
'Tyron',
'Kayla',
'Destin',
'Kenia',
'Aya',
'Joseluis',
'Joshua',
'Dameon',
'Janae',
'Shantel',
'Kaliyah',
'August',
'Bernard',
'Desire',
'Presley',
'Benito',
'Kayleigh',
'Keeley',
'Carley',
'Marquis',
'Tristin',
'Leona',
'Viridiana',
'Marilyn',
'Alex',
'Elle',
'Treyton',
'Marlena',
'Jack',
'Monika',
'George',
'Luna',
'Keanna',
'Yolanda',
'Joanna',
'Don',
'Jalyn',
'Jamal',
'Jerome',
'Rosemary',
'Abraham',
'Denise',
'Darien',
'Nia',
'Kyndall',
'Charlie',
'Myranda',
'Kody',
'Abrianna',
'Rylee',
'Makenna',
'Rowan',
'Heath',
'Dasia',
'Lucas',
'Lisa',
'Mackenna',
'Deasia',
'Ignacio',
'Lynsey',
'Tracy',
'Kareem',
'Jocelynn',
'Sterling',
'Mikala',
'Jaylen',
'Kiya',
'Jesse',
'Shaniya',
'Aditya',
'Chasity',
'Alora',
'Marjorie',
'Stone',
'Cheyenne',
'Troy',
'Abril',
'Julianne',
'Aliah',
'Leticia',
'Betty',
'Mercy',
'Breanna',
'Ciera',
'Agustin',
'Tyrese',
'Devante',
'Oscar',
'Carmen',
'Kamren',
'Kaycee',
'Anisha',
'Adrian',
'Veronica',
'Micheal',
'Ibrahim',
'Lukas',
'Brea',
'Dyllan',
'Sabrina',
'Trace',
'Bailey',
'Evan',
'Abel',
'Damian',
'Jaxon',
'Alexi',
'Ralph',
'Sadie',
'Carlos',
'Zachary',
'Katrina',
'Lester',
'Alek',
'Amani',
'Stewart',
'Kian',
'Brycen',
'Marco',
'Ari',
'Beau',
'Adolfo',
'Lisbeth',
'Virginia',
'Coby',
'Kory',
'William',
'Malorie',
'Hakeem',
'Hayden',
'Marian',
'Georgina',
'Kyron',
'Antonia',
'Verania',
'Nayeli',
'Efren',
'Haleigh',
'Demond',
'Aryanna',
'Carrie',
'Estevan',
'Daisha',
'Trey',
'Shelbi',
'Sade',
'Bridger',
'Carleigh',
'Angel',
'Domenic',
'Eliza',
'Aliza',
'Ruben',
'Stevie',
'Marisa',
'Tiffani',
'Jasmyn',
'Savanah',
'Harley',
'Samson',
'Yazmine',
'Phoenix',
'Landon',
'Zackery',
'Hannah',
'Kristine',
'Autum',
'Jameson',
'Anne',
'Katia',
'Max',
'Alden',
'Brylee',
'Marie',
'Aidan',
'Jeremy',
'Antonio',
'Madeleine',
'Slade',
'Jazmin',
'Stanley',
'Jocelyne',
'Daniel',
'Warren',
'Demetrius',
'Kassandra',
'Alfonso',
'Shelbie',
'Sydney',
'Kaitlyn',
'Mohammed',
'Keira',
'Rodrigo',
'Trever',
'Trenten',
'Analise',
'Monserrat',
'Semaj',
'Erykah',
'Mayra',
'Zoey',
'Hunter',
'Lea'
]
const lastNames = [
'Newberry',
'Parrott',
'Jarrell',
'Myers',
'Wertz',
'Mohr',
'Borders',
'Romeo',
'Carbajal',
'Winchester',
'Pearce',
'Egan',
'Ashworth',
'Sams',
'Cortes',
'Nash',
'Dent',
'Ellis',
'Schmidt',
'Seibert',
'Carson',
'Locklear',
'Lloyd',
'Chapa',
'Pope',
'Su',
'Way',
'Ralston',
'Vincent',
'Aaron',
'Barney',
'Fraser',
'Allen',
'McLean',
'Montez',
'Desimone',
'Viera',
'Jones',
'Quiroz',
'Lawson',
'Correa',
'Kidwell',
'Rahman',
'Graham',
'Lane',
'Heredia',
'Pepper',
'Agnew',
'Dickinson',
'Slone',
'Barrett',
'Swift',
'Doan',
'Arredondo',
'Kaur',
'Bollinger',
'Whiting',
'Jimenez',
'Griffiths',
'Echols',
'Belanger',
'Arriaga',
'Morrow',
'Comer',
'Deleon',
'Bivens',
'Keefe',
'Greenberg',
'Valle',
'Howland',
'Koehler',
'Vest',
'Hendrix',
'Moser',
'Girard',
'Blue',
'Shirley',
'Colwell',
'Reddy',
'Ambrose',
'Briscoe',
'Costello',
'Damron',
'Craven',
'Lin',
'Pena',
'Ziegler',
'Benner',
'Tolbert',
'Oldham',
'Gaytan',
'Calderon',
'Conte',
'Warren',
'Wiseman',
'Bronson',
'Dickerson',
'Wyatt',
'Mann',
'Shields',
'Glover',
'Zielinski',
'Regalado',
'Munson',
'McNeil',
'Scarborough',
'Joyce',
'Booth',
'Dorn',
'McCall',
'Handley',
'Goldsmith',
'Beauchamp',
'Santos',
'Doyle',
'McGovern',
'Levin',
'Tisdale',
'Holcomb',
'Westmoreland',
'Pollock',
'Padron',
'Swanson',
'Willson',
'Galvan',
'Bowden',
'Klein',
'Mcmillian',
'Doherty',
'Hofer',
'Crabtree',
'Zambrano',
'Brower',
'Crockett',
'Levesque',
'Falk',
'Worrell',
'McCombs',
'Joyner',
'Epstein',
'Marquardt',
'Tatum',
'Cervantes',
'Lai',
'Bloom',
'Payton',
'York',
'Keel',
'Lim',
'Francisco',
'Barbour',
'Dumas',
'Garner',
'Stinson',
'Stepp',
'Condon',
'Marsh',
'Ryder',
'Valdez',
'Darnell',
'Batista',
'Lovelace',
'Braun',
'Meza',
'Neely',
'Johnston',
'Trevino',
'Luther',
'Noonan',
'Shannon',
'Thorne',
'Neal',
'Cleary',
'Bowles',
'Hannon',
'Farrell',
'Sanford',
'Tackett',
'Chase',
'Ohara',
'Tipton',
'Nunn',
'Klinger',
'Munoz',
'Carey',
'Damico',
'Carroll',
'Ewing',
'Prater',
'Beers',
'Shultz',
'McCord',
'Huston',
'Fitzpatrick',
'Smalley',
'Shaver',
'Espinosa',
'Leblanc',
'Esquivel',
'Carvalho',
'Volk',
'Trimble',
'Story',
'Aguirre',
'Feldman',
'Pedraza',
'Heinz',
'Ladd',
'Zeigler',
'Hedrick',
'Tanner',
'Mata',
'Messina',
'Pedersen',
'Neff',
'Bullock',
'Ferrell',
'Benavides',
'Delvalle',
'Barnhill',
'Schott',
'Shelby',
'Reynoso',
'Reaves',
'Abernathy',
'Aiken',
'Conklin',
'Curry',
'Worley',
'Juarez',
'Landry',
'Wenger',
'Woo',
'Guzman',
'McMahan',
'Alcala',
'Dubose',
'Hardin',
'Sisson',
'Gordon',
'McCullough',
'Presley',
'Mora',
'Raney',
'Jamison',
'Bartley',
'Wilcox',
'Madden',
'Mast',
'Smith'
]
const countOfnames = firstNames.length;
const getRandomIndex = () => {
return Math.floor(Math.random() * (countOfnames - 0 + 1)) + 0;
}
export { countOfnames, firstNames, getRandomIndex, lastNames };