Progress update cleanup and UI improvements.

This commit is contained in:
Patrick Fic
2025-10-16 14:29:10 -07:00
parent 93a539bd6d
commit 31d151c3b4
17 changed files with 373 additions and 109 deletions

View File

@@ -1,8 +1,8 @@
import { useTranslation } from "react-i18next";
import { Text } from "react-native";
import { Card } from "react-native-paper";
import { Button, Card } from "react-native-paper";
export default function ErrorDisplay({ errorMessage, error }) {
export default function ErrorDisplay({ errorMessage, error, onDismiss }) {
const { t } = useTranslation();
return (
<Card style={{ margin: 8, backgroundColor: "#ffdddd" }}>
@@ -14,6 +14,11 @@ export default function ErrorDisplay({ errorMessage, error }) {
error ||
"An unknown error has occured."}
</Text>
{onDismiss ? (
<Card.Actions>
<Button onPress={onDismiss}>{t("general.labels.dismiss")}</Button>
</Card.Actions>
) : null}
</Card.Content>
</Card>
);

View File

@@ -11,6 +11,7 @@ import {
View,
} from "react-native";
import ImageView from "react-native-image-viewing";
import { ActivityIndicator } from "react-native-paper";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import env from "../../env";
@@ -119,6 +120,7 @@ export function JobDocumentsComponent({ bodyshop }) {
getPhotos();
}, [getPhotos]);
if (loading) return <ActivityIndicator style={{ flex: 1 }} size="large" />;
if (error) {
return <ErrorDisplay message={JSON.stringify(error)} />;
}

View File

@@ -17,14 +17,13 @@ export default function JobLines() {
skip: !jobId,
});
console.log("*** ~ JobLines ~ error:", error);
const { t } = useTranslation();
const onRefresh = async () => {
return refetch();
};
if (loading) {
return <ActivityIndicator />;
return <ActivityIndicator size="large" style={{ flex: 1 }} />;
}
if (error) {

View File

@@ -25,7 +25,7 @@ export default function JobNotes() {
};
if (loading) {
return <ActivityIndicator />;
return <ActivityIndicator size="large" style={{ flex: 1 }} />;
}
if (error) {
return <ErrorDisplay message={JSON.stringify(error?.message)} />;

View File

@@ -23,14 +23,13 @@ export default function JobTombstone() {
});
const theme = useTheme();
console.log("*** ~ JobTombstone ~ theme:", theme.colors);
const { t } = useTranslation();
const onRefresh = async () => {
return refetch();
};
if (loading) {
return <ActivityIndicator />;
return <ActivityIndicator size="large" style={{ flex: 1 }} />;
}
if (!data.jobs_by_pk) {
return (

View File

@@ -133,7 +133,12 @@ const styles = StyleSheet.create({
borderWidth: StyleSheet.hairlineWidth,
backdropFilter: "blur(20px)", // web only
},
cardContents: { flex: 1, flexDirection: "row", display: "flex" },
cardContents: {
flex: 1,
flexDirection: "row",
display: "flex",
alignItems: "center",
},
headerRow: {
flexDirection: "row",
alignItems: "flex-start",

View File

@@ -0,0 +1,56 @@
import SignOutButton from "@/components-old/sign-out-button/sign-out-button.component";
import { toggleDeleteAfterUpload } from "@/redux/app/app.actions";
import { selectDeleteAfterUpload } from "@/redux/app/app.selectors";
import { selectBodyshop } from "@/redux/user/user.selectors";
import { formatBytes } from "@/util/uploadUtils";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { StyleSheet, View } from "react-native";
import { Button, Divider, Text } from "react-native-paper";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import UploadDeleteSwitch from "./upload-delete-switch";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
deleteAfterUpload: selectDeleteAfterUpload,
});
const mapDispatchToProps = (dispatch) => ({
toggleDeleteAfterUpload: () => dispatch(toggleDeleteAfterUpload()),
});
export default connect(mapStateToProps, mapDispatchToProps)(Tab);
function Tab({ bodyshop, deleteAfterUpload, toggleDeleteAfterUpload }) {
return (
<View style={styles.container}>
<Text variant="titleLarge">Settings</Text>
<Text>
Media Storage:{" "}
{bodyshop?.uselocalmediaserver
? bodyshop.localmediaserverhttp
: "Cloud"}
</Text>
{!bodyshop?.uselocalmediaserver && (
<Text>Job Size Limit: {formatBytes(bodyshop?.jobsizelimit)}</Text>
)}
<UploadDeleteSwitch />
<Button
onPress={() => {
AsyncStorage.removeItem("persist:root");
}}
>
Clear Storage
</Button>
<Divider />
<SignOutButton />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});

View File

@@ -0,0 +1,53 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { StyleSheet, Text, View } from "react-native";
import { Switch } from "react-native-paper";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { toggleDeleteAfterUpload } from "../../redux/app/app.actions";
import { selectDeleteAfterUpload } from "../../redux/app/app.selectors";
const mapStateToProps = createStructuredSelector({
deleteAfterUpload: selectDeleteAfterUpload,
});
const mapDispatchToProps = (dispatch) => ({
toggleDeleteAfterUpload: () => dispatch(toggleDeleteAfterUpload()),
});
export function UploadDeleteSwitch({
deleteAfterUpload,
toggleDeleteAfterUpload,
}) {
const { t } = useTranslation();
return (
<View style={styles.container}>
<Text style={styles.text}>
{t("mediabrowser.labels.deleteafterupload")}
</Text>
<Switch
// trackColor={{ false: '#767577', true: '#81b0ff' }}
// thumbColor={deleteAfterUpload ? 'tomato' : '#f4f3f4'}
//ios_backgroundColor="#3e3e3e"
onValueChange={() => {
toggleDeleteAfterUpload();
}}
value={deleteAfterUpload}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
display: "flex",
flexDirection: "row",
alignItems: "center",
margin: 10,
},
text: {
flex: 1,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(UploadDeleteSwitch);

View File

@@ -1,28 +1,53 @@
import { clearUploadError } from "@/redux/photos/photos.actions";
import theme from "@/util/theme";
import { formatBytes } from "@/util/uploadUtils";
import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
import { ProgressBar } from "react-native-paper";
import { useTranslation } from "react-i18next";
import { StyleSheet, View } from "react-native";
import { ProgressBar, Text } from "react-native-paper";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectPhotos,
selectUploadProgress,
selectPhotos,
selectUploadError,
selectUploadProgress,
} from "../../redux/photos/photos.selectors";
import ErrorDisplay from "../error/error-display";
const mapStateToProps = createStructuredSelector({
photos: selectPhotos,
photoUploadProgress: selectUploadProgress,
uploadError: selectUploadError,
});
const mapDispatchToProps = (dispatch) => ({
clearError: () => dispatch(clearUploadError()),
});
export default connect(mapStateToProps, null)(UploadProgress);
export default connect(mapStateToProps, mapDispatchToProps)(UploadProgress);
export function UploadProgress({ photos, photoUploadProgress }) {
export function UploadProgress({
photos,
photoUploadProgress,
uploadError,
clearError,
}) {
const { t } = useTranslation();
if (photos?.length === 0) return null;
if (uploadError)
return <ErrorDisplay error={uploadError} onDismiss={clearError} />;
return (
<View style={styles.modalContainer}>
<View style={styles.modal}>
<Text variant="titleLarge" style={styles.title}>
{t("general.labels.uploadprogress")}
</Text>
{Object.keys(photoUploadProgress).map((key) => (
<View key={key} style={styles.progressItem}>
<Text style={styles.progressText}>
<Text
style={styles.progressText}
numberOfLines={1}
ellipsizeMode="tail"
>
{photoUploadProgress[key].fileName}
</Text>
<View style={styles.progressBarContainer}>
@@ -42,38 +67,14 @@ export function UploadProgress({ photos, photoUploadProgress }) {
>
<Text>{`${formatBytes(
photoUploadProgress[key].loaded /
(((photoUploadProgress[key].uploadEnd || new Date()) -
photoUploadProgress[key].uploadStart) /
(((photoUploadProgress[key].endTime || new Date()) -
photoUploadProgress[key].startTime) /
1000)
)}/sec`}</Text>
{photoUploadProgress[key].percent === 1 && (
<>
<ActivityIndicator style={{ marginLeft: 12 }} />
<Text style={{ marginLeft: 4 }}>Processing...</Text>
</>
)}
</View>
</View>
</View>
))}
<View style={styles.centeredView}>
{
// progress.statusText ? (
// <>
// <ActivityIndicator style={{ marginLeft: 12 }} />
// <Text style={{ marginLeft: 4 }}>{progress.statusText}</Text>
// <Divider />
// </>
// ) : (
// <>
// <Text>{`${progress.totalFilesCompleted} of ${progress.totalFiles} uploaded.`}</Text>
// <Text>{`${formatBytes(progress.totalUploaded)} of ${formatBytes(
// progress.totalToUpload
// )} uploaded.`}</Text>
// </>
// )
}
</View>
</View>
</View>
);
@@ -81,17 +82,19 @@ export function UploadProgress({ photos, photoUploadProgress }) {
const styles = StyleSheet.create({
modalContainer: {
display: "flex",
flex: 1,
// flex: 1,
marginTop: 14,
marginBottom: 14,
justifyContent: "center",
},
modal: {
//flex: 1,
display: "flex",
marginLeft: 20,
marginRight: 20,
backgroundColor: "white",
marginLeft: 12,
marginRight: 12,
backgroundColor: theme.colors.elevation.level3,
borderRadius: 20,
padding: 18,
paddingTop: 12,
shadowColor: "#000",
shadowOffset: {
width: 0,
@@ -101,6 +104,13 @@ const styles = StyleSheet.create({
shadowRadius: 4,
elevation: 5,
},
title: {
alignSelf: "center",
alignItems: "center",
marginBottom: 12,
paddingLeft: 12,
paddingRight: 12,
},
centeredView: {
justifyContent: "center",
alignItems: "center",
@@ -116,6 +126,12 @@ const styles = StyleSheet.create({
},
progressText: {
flex: 1,
flexShrink: 1, // allow shrinking so ellipsis can appear
minWidth: 0, // ensures proper shrinking inside a flex row (especially on web)
// (Optional) If you find web still not clipping, you can uncomment the next lines:
// overflow: 'hidden',
// textOverflow: 'ellipsis',
// whiteSpace: 'nowrap',
},
progressBarContainer: {
flex: 3,