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

View File

@@ -1,13 +1,29 @@
import { GET_JOB_BY_PK } from "@/graphql/jobs.queries";
import { selectBodyshop } from "@/redux/user/user.selectors";
import { useQuery } from "@apollo/client";
import { useLocalSearchParams } from "expo-router";
import React from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
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";
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 { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
variables: {
@@ -15,6 +31,10 @@ export default function JobTombstone() {
},
skip: !jobId,
});
const [visible, setVisible] = useState(false);
const openMenu = () => setVisible(true);
const closeMenu = () => setVisible(false);
console.log("JobTombstone render", visible);
const theme = useTheme();
@@ -22,6 +42,42 @@ export default function JobTombstone() {
const onRefresh = async () => {
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) {
return <ActivityIndicator size="large" style={{ flex: 1 }} />;
}
@@ -50,6 +106,15 @@ export default function JobTombstone() {
/>
<Card.Content>
<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 && (
<Text>{t("objects.jobs.labels.inproduction")}</Text>
)}

View File

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