Basic job details.

This commit is contained in:
Patrick Fic
2025-10-08 15:30:17 -07:00
parent 00626328c4
commit 83993be284
10 changed files with 308 additions and 211 deletions

View File

@@ -6,7 +6,7 @@ import { Icon, Label, NativeTabs } from "expo-router/unstable-native-tabs";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { ActivityIndicator, View } from "react-native";
import { MD3LightTheme, Provider as PaperProvider } from "react-native-paper";
import { Provider as PaperProvider } from "react-native-paper";
import { connect, Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { createStructuredSelector } from "reselect";
@@ -74,14 +74,52 @@ const ConnectedAppContent = connect(
mapDispatchToProps
)(AppContent);
//Custom values were used as the overrides did not work.
const theme = {
...MD3LightTheme,
colors: {
...MD3LightTheme.colors,
primary: "#1890ff",
accent: "tomato",
primary: "#005fae",
onPrimary: "#ffffff",
primaryContainer: "#d4e3ff",
onPrimaryContainer: "#001c3a",
secondary: "#545f71",
onSecondary: "#ffffff",
secondaryContainer: "#d8e3f8",
onSecondaryContainer: "#111c2b",
tertiary: "#00658d",
onTertiary: "#ffffff",
tertiaryContainer: "#c6e7ff",
onTertiaryContainer: "#001e2d",
error: "#ba1a1a",
onError: "#ffffff",
errorContainer: "#ffdad6",
onErrorContainer: "#410002",
background: "#fdfcff",
onBackground: "#1a1c1e",
surface: "#fdfcff",
onSurface: "#1a1c1e",
surfaceVariant: "#e0e2ec",
onSurfaceVariant: "#43474e",
outline: "#74777f",
outlineVariant: "#c3c6cf",
shadow: "#000000",
scrim: "#000000",
inverseSurface: "#2f3033",
inverseOnSurface: "#f1f0f4",
inversePrimary: "#a5c8ff",
elevation: {
level0: "transparent",
level1: "#f0f4fb",
level2: "#e9eff9",
level3: "#e1ebf6",
level4: "#dfe9f5",
level5: "#dae6f4",
},
surfaceDisabled: "rgba(26, 28, 30, 0.12)",
onSurfaceDisabled: "rgba(26, 28, 30, 0.38)",
backdrop: "rgba(45, 49, 56, 0.4)",
},
};
// ...existing code...
export default function AppLayout() {
return (

View File

@@ -2,7 +2,7 @@ import FontAwesome from "@expo/vector-icons/FontAwesome";
import { Tabs } from "expo-router";
import { useTranslation } from "react-i18next";
function JobTabLayout() {
function JobTabLayout(props) {
const { t } = useTranslation();
return (

View File

@@ -1,22 +1,4 @@
import { useLocalSearchParams } from "expo-router";
import { StyleSheet, Text, View } from "react-native";
import JobDetail from "../../../components/job-detail/job-detail";
import JobTombstone from "../../../components/job-tombstone/job-tombstone";
export default function JobDetailScreen() {
const params = useLocalSearchParams();
return (
<View style={styles.container}>
<Text>Job Details for Job ID: {JSON.stringify(params)}</Text>
<JobDetail />
</View>
);
return <JobTombstone />;
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});

View File

@@ -1,10 +1,5 @@
import { Text, View } from "react-native";
import JobLinesComponent from "../../../components/job-lines/job-lines";
function JobLines() {
return (
<View>
<Text>Job Lines</Text>
</View>
);
return <JobLinesComponent />;
}
export default JobLines;

View File

@@ -1,9 +1,7 @@
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { Stack, useRouter } from "expo-router";
function JobsStack() {
const router = useRouter();
const params = useLocalSearchParams();
console.log("*** ~ JobsStack ~ params:", params);
return (
<Stack

View File

@@ -1,4 +1,3 @@
import React from "react";
import { useTranslation } from "react-i18next";
import {
RefreshControl,
@@ -8,7 +7,7 @@ import {
View,
} from "react-native";
import { Card, Headline, Subheading } from "react-native-paper";
import DataLabelComponent from "../data-label/data-label.component";
import DataLabelComponent from "../../components/data-label/data-label";
import StyleRepeater from "../style-repeater/style-repeater";
import styles from "../styles";

View File

@@ -1,173 +0,0 @@
import React from "react";
import { useTranslation } from "react-i18next";
import {
RefreshControl,
ScrollView,
StyleSheet,
Text,
View,
} from "react-native";
import { Card, Headline, Subheading } from "react-native-paper";
import DataLabelComponent from "../data-label/data-label.component";
import StyleRepeater from "../style-repeater/style-repeater";
import styles from "../styles";
export default function JobTombstone({ job, loading, refetch }) {
const { t } = useTranslation();
if (!job) {
<Card>
<Text>Job is not defined.</Text>
</Card>;
}
const onRefresh = async () => {
return refetch();
};
return (
<ScrollView
style={styles.cardBackground}
refreshControl={
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
}
>
<StyleRepeater childStyle={{ margin: 4 }}>
<Card>
<Card.Title title={t("jobdetail.labels.jobinfo")} />
<Card.Content>
<Headline>{job.status}</Headline>
{job.inproduction && (
<Subheading>{t("objects.jobs.labels.inproduction")}</Subheading>
)}
{job.inproduction &&
job.production_vars &&
!!job.production_vars.note && (
<Subheading>{job.production_vars.note}</Subheading>
)}
</Card.Content>
</Card>
<Card>
<Card.Title title={t("jobdetail.labels.claiminformation")} />
<Card.Content style={localStyles.twoColumnCard}>
<View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent
label={t("objects.jobs.fields.owner")}
content={`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`}
/>
<DataLabelComponent
label={t("objects.jobs.fields.vehicle")}
content={
<View>
<Text>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
job.v_model_desc || ""
}`}</Text>
<Text>{job.v_vin}</Text>
</View>
}
/>
</View>
<View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent
label={t("objects.jobs.fields.ins_co_nm")}
content={job.ins_co_nm}
/>
<DataLabelComponent
label={t("objects.jobs.fields.clm_no")}
content={job.clm_no}
/>
</View>
</Card.Content>
</Card>
<Card>
<Card.Title title={t("jobdetail.labels.employeeassignments")} />
<Card.Content>
<DataLabelComponent
label={t("objects.jobs.fields.employee_body")}
content={`${
(job.employee_body_rel && job.employee_body_rel.first_name) ||
""
} ${
(job.employee_body_rel && job.employee_body_rel.last_name) || ""
}`}
/>
<DataLabelComponent
label={t("objects.jobs.fields.employee_prep")}
content={`${
(job.employee_prep_rel && job.employee_prep_rel.first_name) ||
""
} ${
(job.employee_prep_rel && job.employee_prep_rel.last_name) || ""
}`}
/>
<DataLabelComponent
label={t("objects.jobs.fields.employee_refinish")}
content={`${
(job.employee_refinish_rel &&
job.employee_refinish_rel.first_name) ||
""
} ${
(job.employee_refinish_rel &&
job.employee_refinish_rel.last_name) ||
""
}`}
/>
<DataLabelComponent
label={t("objects.jobs.fields.employee_csr")}
content={`${
(job.employee_csr_rel && job.employee_csr_rel.first_name) || ""
} ${
(job.employee_csr_rel && job.employee_csr_rel.last_name) || ""
}`}
/>
</Card.Content>
</Card>
<Card>
<Card.Title title={t("jobdetail.labels.dates")} />
<Card.Content style={localStyles.twoColumnCard}>
<View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent
label={t("objects.jobs.fields.scheduled_in")}
content={job.scheduled_in}
dateTime
/>
<DataLabelComponent
label={t("objects.jobs.fields.actual_in")}
content={job.actual_in}
dateTime
/>
</View>
<View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent
label={t("objects.jobs.fields.scheduled_completion")}
content={job.scheduled_completion}
dateTime
/>
<DataLabelComponent
label={t("objects.jobs.fields.scheduled_delivery")}
content={job.scheduled_delivery}
dateTime
/>
</View>
</Card.Content>
</Card>
</StyleRepeater>
</ScrollView>
);
}
const localStyles = StyleSheet.create({
twoColumnCard: { display: "flex", flexDirection: "row" },
twoColumnCardColumn: { flex: 1 },
status: {
textAlign: "center",
flexDirection: "row",
justifyContent: "center",
},
inproduction: {
textAlign: "center",
flexDirection: "row",
justifyContent: "center",
},
});

View File

@@ -0,0 +1,72 @@
import { GET_JOB_BY_PK } from "@/graphql/jobs.queries";
import { useQuery } from "@apollo/client";
import { useGlobalSearchParams } from "expo-router";
import React from "react";
import { useTranslation } from "react-i18next";
import { ScrollView, Text } from "react-native";
import { ActivityIndicator, Card, DataTable } from "react-native-paper";
export default function JobLines(props) {
const { jobId } = useGlobalSearchParams();
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
variables: {
id: jobId,
},
skip: !jobId,
});
const { t } = useTranslation();
const onRefresh = async () => {
return refetch();
};
if (loading) {
return <ActivityIndicator />;
}
if (!data?.jobs_by_pk) {
return (
<Card>
<Text>Job is not defined.</Text>
</Card>
);
}
const job = data.jobs_by_pk;
return (
<ScrollView>
<DataTable>
<DataTable.Header>
<DataTable.Title> {t("jobdetail.labels.lines_desc")}</DataTable.Title>
<DataTable.Title>
{t("jobdetail.labels.lines_lbr_ty")}
</DataTable.Title>
<DataTable.Title numeric>
{t("jobdetail.labels.lines_lb_hrs")}
</DataTable.Title>
<DataTable.Title>
{t("jobdetail.labels.lines_part_type")}
</DataTable.Title>
<DataTable.Title numeric>
{t("jobdetail.labels.lines_qty")}
</DataTable.Title>
</DataTable.Header>
{job.joblines.map((item) => (
<DataTable.Row key={item.id}>
<DataTable.Cell style={{ flex: 4 }}>
{item.line_desc}
</DataTable.Cell>
<DataTable.Cell>
{item.mod_lbr_ty && t(`jobdetail.lbr_types.${item.mod_lbr_ty}`)}
</DataTable.Cell>
<DataTable.Cell numeric>{item.mod_lb_hrs}</DataTable.Cell>
<DataTable.Cell>
{item.part_type && t(`jobdetail.part_types.${item.part_type}`)}
</DataTable.Cell>
<DataTable.Cell numeric>{item.part_qty}</DataTable.Cell>
</DataTable.Row>
))}
</DataTable>
</ScrollView>
);
}

View File

@@ -0,0 +1,186 @@
import { GET_JOB_BY_PK } from "@/graphql/jobs.queries";
import { useQuery } from "@apollo/client";
import { useLocalSearchParams } from "expo-router";
import React from "react";
import { useTranslation } from "react-i18next";
import {
RefreshControl,
ScrollView,
StyleSheet,
Text,
View,
} from "react-native";
import { ActivityIndicator, Card } from "react-native-paper";
import DataLabelComponent from "../data-label/data-label";
export default function JobTombstone() {
const { jobId } = useLocalSearchParams();
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
variables: {
id: jobId,
},
skip: !jobId,
});
const { t } = useTranslation();
const onRefresh = async () => {
return refetch();
};
if (loading) {
return <ActivityIndicator />;
}
if (!data.jobs_by_pk) {
return (
<Card>
<Text>Job is not defined.</Text>
</Card>
);
}
const job = data.jobs_by_pk;
return (
<ScrollView
contentContainerStyle={{
rowGap: 16,
padding: 10,
}}
refreshControl={
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
}
>
<Card>
<Card.Title title={t("jobdetail.labels.jobinfo")} />
<Card.Content>
<Text>{job.status}</Text>
{job.inproduction && (
<Text>{t("objects.jobs.labels.inproduction")}</Text>
)}
{job.inproduction &&
job.production_vars &&
!!job.production_vars.note && (
<Text>{job.production_vars.note}</Text>
)}
</Card.Content>
</Card>
<Card>
<Card.Title title={t("jobdetail.labels.claiminformation")} />
<Card.Content style={localStyles.twoColumnCard}>
<View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent
label={t("objects.jobs.fields.owner")}
content={`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`}
/>
<DataLabelComponent
label={t("objects.jobs.fields.vehicle")}
content={
<View>
<Text>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
job.v_model_desc || ""
}`}</Text>
<Text>{job.v_vin}</Text>
</View>
}
/>
</View>
<View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent
label={t("objects.jobs.fields.ins_co_nm")}
content={job.ins_co_nm}
/>
<DataLabelComponent
label={t("objects.jobs.fields.clm_no")}
content={job.clm_no}
/>
</View>
</Card.Content>
</Card>
<Card>
<Card.Title title={t("jobdetail.labels.employeeassignments")} />
<Card.Content>
<DataLabelComponent
label={t("objects.jobs.fields.employee_body")}
content={`${
(job.employee_body_rel && job.employee_body_rel.first_name) || ""
} ${
(job.employee_body_rel && job.employee_body_rel.last_name) || ""
}`}
/>
<DataLabelComponent
label={t("objects.jobs.fields.employee_prep")}
content={`${
(job.employee_prep_rel && job.employee_prep_rel.first_name) || ""
} ${
(job.employee_prep_rel && job.employee_prep_rel.last_name) || ""
}`}
/>
<DataLabelComponent
label={t("objects.jobs.fields.employee_refinish")}
content={`${
(job.employee_refinish_rel &&
job.employee_refinish_rel.first_name) ||
""
} ${
(job.employee_refinish_rel &&
job.employee_refinish_rel.last_name) ||
""
}`}
/>
<DataLabelComponent
label={t("objects.jobs.fields.employee_csr")}
content={`${
(job.employee_csr_rel && job.employee_csr_rel.first_name) || ""
} ${
(job.employee_csr_rel && job.employee_csr_rel.last_name) || ""
}`}
/>
</Card.Content>
</Card>
<Card>
<Card.Title title={t("jobdetail.labels.dates")} />
<Card.Content style={localStyles.twoColumnCard}>
<View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent
label={t("objects.jobs.fields.scheduled_in")}
content={job.scheduled_in}
dateTime
/>
<DataLabelComponent
label={t("objects.jobs.fields.actual_in")}
content={job.actual_in}
dateTime
/>
</View>
<View style={localStyles.twoColumnCardColumn}>
<DataLabelComponent
label={t("objects.jobs.fields.scheduled_completion")}
content={job.scheduled_completion}
dateTime
/>
<DataLabelComponent
label={t("objects.jobs.fields.scheduled_delivery")}
content={job.scheduled_delivery}
dateTime
/>
</View>
</Card.Content>
</Card>
</ScrollView>
);
}
const localStyles = StyleSheet.create({
twoColumnCard: { display: "flex", flexDirection: "row" },
twoColumnCardColumn: { flex: 1 },
status: {
textAlign: "center",
flexDirection: "row",
justifyContent: "center",
},
inproduction: {
textAlign: "center",
flexDirection: "row",
justifyContent: "center",
},
});