Add error handling and notes page.
This commit is contained in:
@@ -30,7 +30,7 @@ function JobTabLayout(props) {
|
|||||||
options={{
|
options={{
|
||||||
title: t("jobdetail.labels.lines"),
|
title: t("jobdetail.labels.lines"),
|
||||||
tabBarIcon: ({ color }) => (
|
tabBarIcon: ({ color }) => (
|
||||||
<FontAwesome size={28} name="cog" color={color} />
|
<FontAwesome size={28} name="list" color={color} />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -39,7 +39,7 @@ function JobTabLayout(props) {
|
|||||||
options={{
|
options={{
|
||||||
title: t("jobdetail.labels.documents"),
|
title: t("jobdetail.labels.documents"),
|
||||||
tabBarIcon: ({ color }) => (
|
tabBarIcon: ({ color }) => (
|
||||||
<FontAwesome size={28} name="cog" color={color} />
|
<FontAwesome size={28} name="photo" color={color} />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -48,7 +48,7 @@ function JobTabLayout(props) {
|
|||||||
options={{
|
options={{
|
||||||
title: t("jobdetail.labels.notes"),
|
title: t("jobdetail.labels.notes"),
|
||||||
tabBarIcon: ({ color }) => (
|
tabBarIcon: ({ color }) => (
|
||||||
<FontAwesome size={28} name="cog" color={color} />
|
<FontAwesome size={28} name="sticky-note" color={color} />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { Text, View } from "react-native";
|
import JobDocuments from "../../../components/job-documents/job-documents";
|
||||||
|
|
||||||
function Documents() {
|
function Documents() {
|
||||||
return (
|
return <JobDocuments />;
|
||||||
<View>
|
|
||||||
<Text>Documents</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
export default Documents;
|
export default Documents;
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { Text, View } from "react-native";
|
import JobNotes from "../../../components/job-notes/job-notes";
|
||||||
|
|
||||||
function Notes() {
|
function Notes() {
|
||||||
return (
|
return <JobNotes />;
|
||||||
<View>
|
|
||||||
<Text>Notes</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
export default Notes;
|
export default Notes;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Stack, useRouter } from "expo-router";
|
import { Stack, useRouter } from "expo-router";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function JobsStack() {
|
function JobsStack() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
@@ -15,6 +16,7 @@ function JobsStack() {
|
|||||||
name="index"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
|
title: t("joblist.titles.jobtab"),
|
||||||
// headerSearchBarOptions: {
|
// headerSearchBarOptions: {
|
||||||
// placement: "automatic",
|
// placement: "automatic",
|
||||||
// placeholder: "Search",
|
// placeholder: "Search",
|
||||||
|
|||||||
@@ -25,58 +25,3 @@ export default function MediaCacheOverlay({
|
|||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
|
||||||
// return (
|
|
||||||
// <Modal
|
|
||||||
// onDismiss={() => setPreviewVisible(false)}
|
|
||||||
// onRequestClose={() => setPreviewVisible(false)}
|
|
||||||
// visible={previewVisible}
|
|
||||||
// transparent={false}
|
|
||||||
// >
|
|
||||||
// <SafeAreaView style={{ flex: 1, backgroundColor: "black" }}>
|
|
||||||
// <Gallery
|
|
||||||
// initialPage={imgIndex}
|
|
||||||
|
|
||||||
// images={photos}
|
|
||||||
// onPageScroll={({ position }) => setcurrentIndex(position)}
|
|
||||||
// onPageScrollStateChanged={(state) =>
|
|
||||||
// state === "idle" ? setDragging(false) : setDragging(true)
|
|
||||||
// }
|
|
||||||
// />
|
|
||||||
// <TouchableOpacity
|
|
||||||
// style={{ position: "absolute" }}
|
|
||||||
// onPress={() => setPreviewVisible(false)}
|
|
||||||
// >
|
|
||||||
// <Ionicons
|
|
||||||
// name="ios-close"
|
|
||||||
// size={64}
|
|
||||||
// color="dodgerblue"
|
|
||||||
// style={{ margin: 20 }}
|
|
||||||
// />
|
|
||||||
// </TouchableOpacity>
|
|
||||||
// {!dragging && photos[currentIndex] && photos[currentIndex].videoUrl && (
|
|
||||||
// <TouchableOpacity
|
|
||||||
// style={{
|
|
||||||
// position: "absolute",
|
|
||||||
// left: Dimensions.get("window").width / 2 - 32,
|
|
||||||
// top: Dimensions.get("window").height / 2 - 32,
|
|
||||||
// justifyContent: "center",
|
|
||||||
// alignItems: "center",
|
|
||||||
// }}
|
|
||||||
// onPress={async () => {
|
|
||||||
// await videoRef.current.loadAsync(
|
|
||||||
// { uri: photos[currentIndex].videoUrl },
|
|
||||||
// {},
|
|
||||||
// false
|
|
||||||
// );
|
|
||||||
// videoRef.current.presentFullscreenPlayer();
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// <Ionicons name="play" size={64} color="white" />
|
|
||||||
// </TouchableOpacity>
|
|
||||||
// )}
|
|
||||||
// </SafeAreaView>
|
|
||||||
// <Video ref={videoRef} useNativeControls />
|
|
||||||
// </Modal>
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 (
|
return (
|
||||||
<View style={{ backgroundColor: "red" }}>
|
<Card style={{ margin: 8, backgroundColor: "#ffdddd" }}>
|
||||||
<Text>{errorMessage}</Text>
|
<Card.Title title={t("general.labels.error")} titleVariant="titleLarge" />
|
||||||
</View>
|
<Card.Content>
|
||||||
|
<Text>
|
||||||
|
{errorMessage ||
|
||||||
|
error?.message ||
|
||||||
|
error ||
|
||||||
|
"An unknown error has occured."}
|
||||||
|
</Text>
|
||||||
|
</Card.Content>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
170
components/job-documents/job-documents.jsx
Normal file
170
components/job-documents/job-documents.jsx
Normal file
@@ -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 <ErrorDisplay message={JSON.stringify(error)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<FlatList
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||||
|
}
|
||||||
|
data={fullphotos}
|
||||||
|
ListFooterComponent={
|
||||||
|
<Text style={{ textAlign: "center", padding: 8 }}>{`${
|
||||||
|
fullphotos.length
|
||||||
|
} document${fullphotos.length === 1 ? "" : "s"}`}</Text>
|
||||||
|
}
|
||||||
|
numColumns={4}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
keyExtractor={(item) => item.id}
|
||||||
|
renderItem={(object) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={{ flex: 1 / 4, aspectRatio: 1, margin: 4 }}
|
||||||
|
onPress={async () => {
|
||||||
|
setImgIndex(object.index);
|
||||||
|
setPreviewVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
resizeMode="cover"
|
||||||
|
source={{
|
||||||
|
uri: object.item.thumbUrl,
|
||||||
|
aspectRatio: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
onRequestClose={() => setPreviewVisible(false)}
|
||||||
|
visible={previewVisible}
|
||||||
|
images={fullphotos}
|
||||||
|
imageIndex={imgIndex}
|
||||||
|
swipeToCloseEnabled={true}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,10 +3,11 @@ import { useQuery } from "@apollo/client";
|
|||||||
import { useGlobalSearchParams } from "expo-router";
|
import { useGlobalSearchParams } from "expo-router";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ScrollView, Text } from "react-native";
|
import { ScrollView } from "react-native";
|
||||||
import { ActivityIndicator, Card, DataTable } from "react-native-paper";
|
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 { jobId } = useGlobalSearchParams();
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
|
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
|
||||||
@@ -16,22 +17,25 @@ export default function JobLines(props) {
|
|||||||
skip: !jobId,
|
skip: !jobId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("*** ~ JobLines ~ error:", error);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const onRefresh = async () => {
|
const onRefresh = async () => {
|
||||||
return refetch();
|
return refetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
if (!data?.jobs_by_pk) {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<Text>Job is not defined.</Text>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const job = data.jobs_by_pk;
|
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <ErrorDisplay message={JSON.stringify(error)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data?.jobs_by_pk) {
|
||||||
|
return <ErrorDisplay message={"Job is not defined."} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const job = data.jobs_by_pk;
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<DataTable>
|
<DataTable>
|
||||||
|
|||||||
100
components/job-notes/job-notes.jsx
Normal file
100
components/job-notes/job-notes.jsx
Normal file
@@ -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 <ActivityIndicator />;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return <ErrorDisplay message={JSON.stringify(error?.message)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data?.jobs_by_pk) {
|
||||||
|
return <ErrorDisplay errorMessage={"Job is not defined."} />;
|
||||||
|
}
|
||||||
|
const job = data.jobs_by_pk;
|
||||||
|
|
||||||
|
if (job.notes.length === 0)
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Card.Content>
|
||||||
|
<Text>{t("jobdetail.labels.nojobnotes")}</Text>
|
||||||
|
</Card.Content>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl refreshing={loading} onRefresh={onRefresh} />
|
||||||
|
}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
data={job.notes}
|
||||||
|
renderItem={(object) => <NoteListItem item={object.item} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoteListItem({ item }) {
|
||||||
|
return (
|
||||||
|
<Card style={{ margin: 8 }}>
|
||||||
|
<Card.Content>
|
||||||
|
<View style={{ display: "flex", flex: 1 }}>
|
||||||
|
<Text>{item.text}</Text>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "column",
|
||||||
|
alignSelf: "flex-end",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.private && (
|
||||||
|
<AntDesign
|
||||||
|
name="eyeo"
|
||||||
|
style={{ margin: 4 }}
|
||||||
|
size={24}
|
||||||
|
color="black"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{item.critical && (
|
||||||
|
<AntDesign
|
||||||
|
name="warning"
|
||||||
|
style={{ margin: 4 }}
|
||||||
|
size={24}
|
||||||
|
color="tomato"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text style={{ fontSize: 12 }}>{item.created_by}</Text>
|
||||||
|
<Text style={{ fontSize: 12 }}>
|
||||||
|
{DateTime.fromISO(item.created_at).toLocaleString(
|
||||||
|
DateTime.DATETIME_SHORT
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Card.Content>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -48,7 +48,10 @@ export default function JobTombstone() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Title title={t("jobdetail.labels.jobinfo")} />
|
<Card.Title
|
||||||
|
title={t("jobdetail.labels.jobinfo")}
|
||||||
|
titleVariant="titleLarge"
|
||||||
|
/>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<Text>{job.status}</Text>
|
<Text>{job.status}</Text>
|
||||||
{job.inproduction && (
|
{job.inproduction && (
|
||||||
@@ -63,7 +66,10 @@ export default function JobTombstone() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Title title={t("jobdetail.labels.claiminformation")} />
|
<Card.Title
|
||||||
|
title={t("jobdetail.labels.claiminformation")}
|
||||||
|
titleVariant="titleLarge"
|
||||||
|
/>
|
||||||
<Card.Content style={localStyles.twoColumnCard}>
|
<Card.Content style={localStyles.twoColumnCard}>
|
||||||
<View style={localStyles.twoColumnCardColumn}>
|
<View style={localStyles.twoColumnCardColumn}>
|
||||||
<DataLabelComponent
|
<DataLabelComponent
|
||||||
@@ -97,7 +103,10 @@ export default function JobTombstone() {
|
|||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Title title={t("jobdetail.labels.employeeassignments")} />
|
<Card.Title
|
||||||
|
title={t("jobdetail.labels.employeeassignments")}
|
||||||
|
titleVariant="titleLarge"
|
||||||
|
/>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<DataLabelComponent
|
<DataLabelComponent
|
||||||
label={t("objects.jobs.fields.employee_body")}
|
label={t("objects.jobs.fields.employee_body")}
|
||||||
@@ -138,7 +147,10 @@ export default function JobTombstone() {
|
|||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Title title={t("jobdetail.labels.dates")} />
|
<Card.Title
|
||||||
|
title={t("jobdetail.labels.dates")}
|
||||||
|
titleVariant="titleLarge"
|
||||||
|
/>
|
||||||
<Card.Content style={localStyles.twoColumnCard}>
|
<Card.Content style={localStyles.twoColumnCard}>
|
||||||
<View style={localStyles.twoColumnCardColumn}>
|
<View style={localStyles.twoColumnCardColumn}>
|
||||||
<DataLabelComponent
|
<DataLabelComponent
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
"signout": "Sign Out"
|
"signout": "Sign Out"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"na": "N/A"
|
"na": "N/A",
|
||||||
|
"error": "Error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jobdetail": {
|
"jobdetail": {
|
||||||
|
|||||||
Reference in New Issue
Block a user