Add custom image loader and start job status.

This commit is contained in:
Patrick Fic
2025-10-28 14:52:54 -07:00
parent 38c2b0af5a
commit 9f65ca3c2f
7 changed files with 132 additions and 13 deletions

View File

@@ -0,0 +1,43 @@
import { useMemo, useState } from "react";
import { Image } from "react-native";
import { ActivityIndicator } from "react-native-paper";
const ImageLoader = ({ style, source, ...props }) => {
const [loading, setLoading] = useState(true);
const handleLoadStart = () => {
setLoading(true);
};
const handleLoadEnd = () => {
setLoading(false);
};
const handleLoad = () => {
setLoading(false);
};
const memorizedImage = useMemo(
() => (
<Image
{...props}
style={style}
source={source}
//onLoadStart={handleLoadStart}
onLoadEnd={handleLoadEnd}
onLoad={handleLoad}
/>
),
[source]
);
if (loading)
return (
<>
<ActivityIndicator style={{ ...style, position: "absolute" }} />
{memorizedImage}
</>
);
return memorizedImage;
};
export default ImageLoader;

View File

@@ -2,13 +2,7 @@ import cleanAxios from "@/util/CleanAxios";
import axios from "axios"; import axios from "axios";
import { useGlobalSearchParams } from "expo-router"; import { useGlobalSearchParams } from "expo-router";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { import { FlatList, RefreshControl, TouchableOpacity, View } from "react-native";
FlatList,
Image,
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 { connect } from "react-redux"; import { connect } from "react-redux";
@@ -17,6 +11,7 @@ import env from "../../env";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DetermineFileType } from "../../util/document-upload.utility"; import { DetermineFileType } from "../../util/document-upload.utility";
import ErrorDisplay from "../error/error-display"; import ErrorDisplay from "../error/error-display";
import ImageLoader from "./image-loader";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -142,7 +137,7 @@ export function JobDocumentsComponent({ bodyshop }) {
setPreviewVisible(true); setPreviewVisible(true);
}} }}
> >
<Image <ImageLoader
style={{ flex: 1 }} style={{ flex: 1 }}
resizeMode="cover" resizeMode="cover"
source={{ source={{

View File

@@ -1,13 +1,29 @@
import { GET_JOB_BY_PK } from "@/graphql/jobs.queries"; import { GET_JOB_BY_PK } from "@/graphql/jobs.queries";
import { selectBodyshop } from "@/redux/user/user.selectors";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
import React from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { RefreshControl, ScrollView, StyleSheet, View } from "react-native"; import { RefreshControl, ScrollView, StyleSheet, View } from "react-native";
import { ActivityIndicator, Card, Text, useTheme } from "react-native-paper"; import {
ActivityIndicator,
Button,
Card,
Menu,
Text,
useTheme,
} from "react-native-paper";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import DataLabelComponent from "../data-label/data-label"; import DataLabelComponent from "../data-label/data-label";
export default function JobTombstone() { const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(JobTombstone);
function JobTombstone({ bodyshop }) {
const { jobId } = useLocalSearchParams(); const { jobId } = useLocalSearchParams();
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, { const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
variables: { variables: {
@@ -15,6 +31,10 @@ export default function JobTombstone() {
}, },
skip: !jobId, skip: !jobId,
}); });
const [visible, setVisible] = useState(false);
const openMenu = () => setVisible(true);
const closeMenu = () => setVisible(false);
console.log("JobTombstone render", visible);
const theme = useTheme(); const theme = useTheme();
@@ -22,6 +42,42 @@ export default function JobTombstone() {
const onRefresh = async () => { const onRefresh = async () => {
return refetch(); return refetch();
}; };
const [availableStatuses, setAvailableStatuses] = useState([]);
useEffect(() => {
if (!job || !bodyshop) return;
const { md_ro_statuses } = bodyshop;
const {
pre_production_statuses,
production_statuses,
post_production_statuses,
statuses,
default_invoiced,
default_exported,
} = md_ro_statuses;
// Handle non-parts entry scenarios based on job status
if (pre_production_statuses.includes(job.status)) {
setAvailableStatuses(pre_production_statuses);
} else if (production_statuses.includes(job.status)) {
setAvailableStatuses(production_statuses);
} else if (post_production_statuses.includes(job.status)) {
// Filter out invoiced and exported statuses for post-production
setAvailableStatuses(
post_production_statuses.filter(
(status) => status !== default_invoiced && status !== default_exported
)
);
} else {
// Default to all statuses if no specific restrictions apply
console.log(
"Status didn't match any restrictions. Allowing all status changes."
);
setAvailableStatuses(statuses);
}
}, [job, bodyshop, setAvailableStatuses]);
if (loading) { if (loading) {
return <ActivityIndicator size="large" style={{ flex: 1 }} />; return <ActivityIndicator size="large" style={{ flex: 1 }} />;
} }
@@ -50,6 +106,15 @@ export default function JobTombstone() {
/> />
<Card.Content> <Card.Content>
<Text>{job.status}</Text> <Text>{job.status}</Text>
<Menu
visible={visible}
onDismiss={closeMenu}
anchor={<Button onPress={openMenu}>{job.status}</Button>}
>
{availableStatuses.map((status) => (
<Menu.Item key={status} onPress={() => {}} title={status} />
))}
</Menu>
{job.inproduction && ( {job.inproduction && (
<Text>{t("objects.jobs.labels.inproduction")}</Text> <Text>{t("objects.jobs.labels.inproduction")}</Text>
)} )}

View File

@@ -31,7 +31,7 @@ export function JobListComponent({ bodyshop }) {
return refetch(); return refetch();
}; };
if (loading) return <ActivityIndicator />; if (loading) return <ActivityIndicator style={{ flex: 1 }} />;
if (error) return <Text>{error.message}</Text>; if (error) return <Text>{error.message}</Text>;
if (data && data.jobs && data.jobs.length === 0) if (data && data.jobs && data.jobs.length === 0)
return ( return (

View File

@@ -12,6 +12,7 @@ export const QUERY_BODYSHOP = gql`
features features
localmediatoken localmediatoken
imexshopid imexshopid
md_ro_statuses
} }
} }
`; `;

14
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"@apollo/client": "^3.14.0", "@apollo/client": "^3.14.0",
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
"@react-native-picker/picker": "2.11.1",
"@react-native-vector-icons/material-design-icons": "^12.3.0", "@react-native-vector-icons/material-design-icons": "^12.3.0",
"@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.5", "@react-navigation/elements": "^2.6.5",
@@ -4546,6 +4547,19 @@
"react-native": "^0.0.0-0 || >=0.65 <1.0" "react-native": "^0.0.0-0 || >=0.65 <1.0"
} }
}, },
"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==",
"license": "MIT",
"workspaces": [
"example"
],
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/@react-native-vector-icons/common": { "node_modules/@react-native-vector-icons/common": {
"version": "12.3.0", "version": "12.3.0",
"resolved": "https://registry.npmjs.org/@react-native-vector-icons/common/-/common-12.3.0.tgz", "resolved": "https://registry.npmjs.org/@react-native-vector-icons/common/-/common-12.3.0.tgz",

View File

@@ -26,6 +26,7 @@
"@apollo/client": "^3.14.0", "@apollo/client": "^3.14.0",
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@react-native-async-storage/async-storage": "2.2.0", "@react-native-async-storage/async-storage": "2.2.0",
"@react-native-picker/picker": "2.11.1",
"@react-native-vector-icons/material-design-icons": "^12.3.0", "@react-native-vector-icons/material-design-icons": "^12.3.0",
"@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.5", "@react-navigation/elements": "^2.6.5",