mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 18:13:14 +00:00
Role-based bottom-tab navigation (dispatcher dashboard, admin workspace), shared components (StatusChip, PriorityChip, ProposalCard, LineItemRow, FilePickerButton, EmptyState), dispatcher screens (dashboard, new proposal with camera/document picker and offline drafts, proposal detail), admin screens (dashboard with stats, proposal queue, workspace with approve/ regenerate/PDF, line item editor), login, and settings.
130 lines
3.2 KiB
TypeScript
130 lines
3.2 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { View, StyleSheet, Alert } from 'react-native';
|
|
import { Button, Text, ProgressBar } from 'react-native-paper';
|
|
import { launchCamera } from 'react-native-image-picker';
|
|
import DocumentPicker from 'react-native-document-picker';
|
|
import axios from 'axios';
|
|
|
|
interface Props {
|
|
onFileSelected: (fileName: string, s3Key: string) => void;
|
|
getUploadUrl: (
|
|
fileName: string,
|
|
) => Promise<{ uploadUrl: string; s3Key: string }>;
|
|
}
|
|
|
|
export function FilePickerButton({ onFileSelected, getUploadUrl }: Props) {
|
|
const [uploading, setUploading] = useState(false);
|
|
const [progress, setProgress] = useState(0);
|
|
const [fileName, setFileName] = useState<string | null>(null);
|
|
|
|
const uploadFile = async (uri: string, name: string, type: string) => {
|
|
setUploading(true);
|
|
setProgress(0);
|
|
setFileName(name);
|
|
|
|
try {
|
|
const { uploadUrl, s3Key } = await getUploadUrl(name);
|
|
|
|
const file = await fetch(uri);
|
|
const blob = await file.blob();
|
|
|
|
await axios.put(uploadUrl, blob, {
|
|
headers: { 'Content-Type': type },
|
|
onUploadProgress: (e) => {
|
|
if (e.total) setProgress(e.loaded / e.total);
|
|
},
|
|
});
|
|
|
|
onFileSelected(name, s3Key);
|
|
} catch {
|
|
Alert.alert(
|
|
'Upload Failed',
|
|
'Could not upload the file. Please try again.',
|
|
);
|
|
setFileName(null);
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
const pickDocument = async () => {
|
|
try {
|
|
const result = await DocumentPicker.pickSingle({
|
|
type: [DocumentPicker.types.pdf],
|
|
});
|
|
if (result.uri && result.name) {
|
|
await uploadFile(
|
|
result.uri,
|
|
result.name,
|
|
result.type || 'application/pdf',
|
|
);
|
|
}
|
|
} catch (err) {
|
|
if (!DocumentPicker.isCancel(err)) {
|
|
Alert.alert('Error', 'Could not pick document.');
|
|
}
|
|
}
|
|
};
|
|
|
|
const takePhoto = async () => {
|
|
const result = await launchCamera({ mediaType: 'photo', quality: 0.8 });
|
|
if (result.assets?.[0]?.uri) {
|
|
const asset = result.assets[0];
|
|
await uploadFile(
|
|
asset.uri!,
|
|
asset.fileName || 'vendor-proposal.jpg',
|
|
asset.type || 'image/jpeg',
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={styles.buttons}>
|
|
<Button
|
|
mode="outlined"
|
|
icon="file-pdf-box"
|
|
onPress={pickDocument}
|
|
disabled={uploading}
|
|
style={styles.button}
|
|
>
|
|
Choose PDF
|
|
</Button>
|
|
<Button
|
|
mode="outlined"
|
|
icon="camera"
|
|
onPress={takePhoto}
|
|
disabled={uploading}
|
|
style={styles.button}
|
|
>
|
|
Camera
|
|
</Button>
|
|
</View>
|
|
{uploading && <ProgressBar progress={progress} style={styles.progress} />}
|
|
{fileName && !uploading && (
|
|
<Text variant="bodySmall" style={styles.fileName}>
|
|
{fileName}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
gap: 8,
|
|
},
|
|
buttons: {
|
|
flexDirection: 'row',
|
|
gap: 12,
|
|
},
|
|
button: {
|
|
flex: 1,
|
|
},
|
|
progress: {
|
|
marginTop: 4,
|
|
},
|
|
fileName: {
|
|
color: '#2E7D32',
|
|
},
|
|
});
|