diff --git a/app/jobs/[jobId]/_layout.tsx b/app/jobs/[jobId]/_layout.tsx
index 614c461..8f50721 100644
--- a/app/jobs/[jobId]/_layout.tsx
+++ b/app/jobs/[jobId]/_layout.tsx
@@ -30,7 +30,7 @@ function JobTabLayout(props) {
options={{
title: t("jobdetail.labels.lines"),
tabBarIcon: ({ color }) => (
-
+
),
}}
/>
@@ -39,7 +39,7 @@ function JobTabLayout(props) {
options={{
title: t("jobdetail.labels.documents"),
tabBarIcon: ({ color }) => (
-
+
),
}}
/>
@@ -48,7 +48,7 @@ function JobTabLayout(props) {
options={{
title: t("jobdetail.labels.notes"),
tabBarIcon: ({ color }) => (
-
+
),
}}
/>
diff --git a/app/jobs/[jobId]/documents.tsx b/app/jobs/[jobId]/documents.tsx
index a00269b..71827d0 100644
--- a/app/jobs/[jobId]/documents.tsx
+++ b/app/jobs/[jobId]/documents.tsx
@@ -1,10 +1,5 @@
-import { Text, View } from "react-native";
-
+import JobDocuments from "../../../components/job-documents/job-documents";
function Documents() {
- return (
-
- Documents
-
- );
+ return ;
}
export default Documents;
diff --git a/app/jobs/[jobId]/notes.tsx b/app/jobs/[jobId]/notes.tsx
index 38b41c0..6625331 100644
--- a/app/jobs/[jobId]/notes.tsx
+++ b/app/jobs/[jobId]/notes.tsx
@@ -1,10 +1,5 @@
-import { Text, View } from "react-native";
-
+import JobNotes from "../../../components/job-notes/job-notes";
function Notes() {
- return (
-
- Notes
-
- );
+ return ;
}
export default Notes;
diff --git a/app/jobs/_layout.tsx b/app/jobs/_layout.tsx
index a34081f..72168bf 100644
--- a/app/jobs/_layout.tsx
+++ b/app/jobs/_layout.tsx
@@ -1,8 +1,9 @@
import { Stack, useRouter } from "expo-router";
+import { useTranslation } from "react-i18next";
function JobsStack() {
const router = useRouter();
-
+ const { t } = useTranslation();
return (
);
-
- // return (
- // setPreviewVisible(false)}
- // onRequestClose={() => setPreviewVisible(false)}
- // visible={previewVisible}
- // transparent={false}
- // >
- //
- // setcurrentIndex(position)}
- // onPageScrollStateChanged={(state) =>
- // state === "idle" ? setDragging(false) : setDragging(true)
- // }
- // />
- // setPreviewVisible(false)}
- // >
- //
- //
- // {!dragging && photos[currentIndex] && photos[currentIndex].videoUrl && (
- // {
- // await videoRef.current.loadAsync(
- // { uri: photos[currentIndex].videoUrl },
- // {},
- // false
- // );
- // videoRef.current.presentFullscreenPlayer();
- // }}
- // >
- //
- //
- // )}
- //
- //
- //
- // );
-}
diff --git a/components/error/error-display.jsx b/components/error/error-display.jsx
index df4b749..105af95 100644
--- a/components/error/error-display.jsx
+++ b/components/error/error-display.jsx
@@ -1,9 +1,20 @@
-import { Text, View } from "react-native";
+import { useTranslation } from "react-i18next";
+import { Text } from "react-native";
+import { Card } from "react-native-paper";
-export default function ErrorDisplay({ errorMessage }) {
+export default function ErrorDisplay({ errorMessage, error }) {
+ const { t } = useTranslation();
return (
-
- {errorMessage}
-
+
+
+
+
+ {errorMessage ||
+ error?.message ||
+ error ||
+ "An unknown error has occured."}
+
+
+
);
}
diff --git a/components/job-documents/job-documents.jsx b/components/job-documents/job-documents.jsx
new file mode 100644
index 0000000..d8566b4
--- /dev/null
+++ b/components/job-documents/job-documents.jsx
@@ -0,0 +1,170 @@
+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,
+ Text,
+ TouchableOpacity,
+ View,
+} from "react-native";
+import ImageView from "react-native-image-viewing";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import env from "../../env";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import { DetermineFileType } from "../../util/document-upload.utility";
+import ErrorDisplay from "../error/error-display";
+
+const mapStateToProps = createStructuredSelector({
+ bodyshop: selectBodyshop,
+});
+const mapDispatchToProps = (dispatch) => ({});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(JobDocumentsComponent);
+
+export function JobDocumentsComponent({ bodyshop }) {
+ const [previewVisible, setPreviewVisible] = useState(false);
+ const [fullphotos, setFullPhotos] = useState([]);
+ const [imgIndex, setImgIndex] = useState(0);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const { jobId } = useGlobalSearchParams();
+
+ const isLms = bodyshop.uselocalmediaserver;
+ const onRefresh = async () => {
+ return getPhotos();
+ };
+
+ const getPhotos = useCallback(async () => {
+ setError(null);
+ setLoading(true);
+ try {
+ if (!isLms) {
+ const result = await axios.post(
+ `${env.API_URL}/media/imgproxy/thumbnails`,
+ {
+ jobid: jobId,
+ }
+ );
+
+ setFullPhotos(
+ result.data.map((doc, idx) => {
+ return {
+ id: idx,
+ videoUrl:
+ DetermineFileType(doc.type) === "video" &&
+ doc.originalUrlViaProxyPath,
+ source:
+ DetermineFileType(doc.type) === "video"
+ ? { uri: doc.thumbnailUrl }
+ : { uri: doc.originalUrl },
+ url:
+ DetermineFileType(doc.type) === "video"
+ ? doc.thumbnailUrl
+ : doc.originalUrl,
+ uri:
+ DetermineFileType(doc.type) === "video"
+ ? doc.originalUrlViaProxyPath
+ : doc.originalUrl,
+ thumbUrl: doc.thumbnailUrl,
+ };
+ })
+ );
+ } else {
+ let localmediaserverhttp = bodyshop.localmediaserverhttp.trim();
+ if (localmediaserverhttp.endsWith("/")) {
+ localmediaserverhttp = localmediaserverhttp.slice(0, -1);
+ }
+
+ const imagesFetch = await cleanAxios.post(
+ `${localmediaserverhttp}/jobs/list`,
+ {
+ jobid: jobId,
+ },
+ { headers: { ims_token: bodyshop.localmediatoken } }
+ );
+
+ const normalizedImages = imagesFetch.data
+ .filter((d) => d.type?.mime?.startsWith("image"))
+ .map((d, idx) => {
+ return {
+ ...d,
+ // src: `${localmediaserverhttp}/${d.src}`,
+
+ uri: `${localmediaserverhttp}${d.src}`,
+ thumbUrl: `${localmediaserverhttp}${d.thumbnail}`,
+ id: idx,
+ };
+ });
+
+ setFullPhotos(normalizedImages);
+ }
+ } catch (error) {
+ console.log(
+ "Error fetching photos:",
+ error.message,
+ JSON.stringify(error, null, 2)
+ );
+ setError(error.message || "Unknown error fetching photos.");
+ }
+ setLoading(false);
+ }, [isLms, jobId, bodyshop]);
+
+ useEffect(() => {
+ getPhotos();
+ }, [getPhotos]);
+
+ if (error) {
+ return ;
+ }
+
+ return (
+
+
+ }
+ data={fullphotos}
+ ListFooterComponent={
+ {`${
+ fullphotos.length
+ } document${fullphotos.length === 1 ? "" : "s"}`}
+ }
+ numColumns={4}
+ style={{ flex: 1 }}
+ keyExtractor={(item) => item.id}
+ renderItem={(object) => (
+ {
+ setImgIndex(object.index);
+ setPreviewVisible(true);
+ }}
+ >
+
+
+ )}
+ />
+
+ setPreviewVisible(false)}
+ visible={previewVisible}
+ images={fullphotos}
+ imageIndex={imgIndex}
+ swipeToCloseEnabled={true}
+ />
+
+ );
+}
diff --git a/components/job-lines/job-lines.jsx b/components/job-lines/job-lines.jsx
index 5a6d7bf..7899086 100644
--- a/components/job-lines/job-lines.jsx
+++ b/components/job-lines/job-lines.jsx
@@ -3,10 +3,11 @@ 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";
+import { ScrollView } from "react-native";
+import { ActivityIndicator, DataTable } from "react-native-paper";
+import ErrorDisplay from "../error/error-display";
-export default function JobLines(props) {
+export default function JobLines() {
const { jobId } = useGlobalSearchParams();
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
@@ -16,22 +17,25 @@ export default function JobLines(props) {
skip: !jobId,
});
+ console.log("*** ~ JobLines ~ error:", error);
const { t } = useTranslation();
const onRefresh = async () => {
return refetch();
};
+
if (loading) {
return ;
}
- if (!data?.jobs_by_pk) {
- return (
-
- Job is not defined.
-
- );
- }
- const job = data.jobs_by_pk;
+ if (error) {
+ return ;
+ }
+
+ if (!data?.jobs_by_pk) {
+ return ;
+ }
+
+ const job = data.jobs_by_pk;
return (
diff --git a/components/job-notes/job-notes.jsx b/components/job-notes/job-notes.jsx
new file mode 100644
index 0000000..eac52e5
--- /dev/null
+++ b/components/job-notes/job-notes.jsx
@@ -0,0 +1,100 @@
+import { GET_JOB_BY_PK } from "@/graphql/jobs.queries";
+import { useQuery } from "@apollo/client";
+import { AntDesign } from "@expo/vector-icons";
+import { useGlobalSearchParams } from "expo-router";
+import { DateTime } from "luxon";
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { FlatList, RefreshControl, Text, View } from "react-native";
+import { ActivityIndicator, Card } from "react-native-paper";
+import ErrorDisplay from "../error/error-display";
+
+export default function JobNotes() {
+ 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 ;
+ }
+ if (error) {
+ return ;
+ }
+
+ if (!data?.jobs_by_pk) {
+ return ;
+ }
+ const job = data.jobs_by_pk;
+
+ if (job.notes.length === 0)
+ return (
+
+
+ {t("jobdetail.labels.nojobnotes")}
+
+
+ );
+
+ return (
+
+ }
+ style={{ flex: 1 }}
+ data={job.notes}
+ renderItem={(object) => }
+ />
+ );
+}
+
+function NoteListItem({ item }) {
+ return (
+
+
+
+ {item.text}
+
+ {item.private && (
+
+ )}
+ {item.critical && (
+
+ )}
+ {item.created_by}
+
+ {DateTime.fromISO(item.created_at).toLocaleString(
+ DateTime.DATETIME_SHORT
+ )}
+
+
+
+
+
+ );
+}
diff --git a/components/job-tombstone/job-tombstone.jsx b/components/job-tombstone/job-tombstone.jsx
index b9b9aa0..78abc0a 100644
--- a/components/job-tombstone/job-tombstone.jsx
+++ b/components/job-tombstone/job-tombstone.jsx
@@ -48,7 +48,10 @@ export default function JobTombstone() {
}
>
-
+
{job.status}
{job.inproduction && (
@@ -63,7 +66,10 @@ export default function JobTombstone() {
-
+
-
+
-
+