Add job status changer.
This commit is contained in:
166
components/job-status-selector/JobStatusSelector.tsx
Normal file
166
components/job-status-selector/JobStatusSelector.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import { UPDATE_JOB_STATUS } from "@/graphql/jobs.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import * as Haptics from "expo-haptics";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { FlatList, StyleSheet, View } from "react-native";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
List,
|
||||
Modal,
|
||||
Portal,
|
||||
Text,
|
||||
useTheme,
|
||||
} from "react-native-paper";
|
||||
|
||||
/**
|
||||
* JobStatusSelector component contract
|
||||
* Props:
|
||||
* - statuses: string[] (list of available statuses)
|
||||
* - currentStatus: string (currently applied status)
|
||||
* - onSelect: (status: string) => void (fires when user selects a status)
|
||||
* - label?: string (optional label for trigger button)
|
||||
*/
|
||||
export interface JobStatusSelectorProps {
|
||||
statuses: string[];
|
||||
currentStatus: string | undefined;
|
||||
onSelect: (status: string) => void;
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const keyExtractor = (item: string) => item;
|
||||
|
||||
export const JobStatusSelector: React.FC<JobStatusSelectorProps> = ({
|
||||
statuses,
|
||||
currentStatus,
|
||||
onSelect,
|
||||
label = "Change Status",
|
||||
disabled = false,
|
||||
}) => {
|
||||
const { jobId } = useLocalSearchParams();
|
||||
|
||||
const theme = useTheme();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const show = () => setVisible(true);
|
||||
const hide = () => setVisible(false);
|
||||
|
||||
const [updateJobStatus] = useMutation(UPDATE_JOB_STATUS);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
async (status: string) => {
|
||||
Haptics.selectionAsync().catch(() => {});
|
||||
hide();
|
||||
await updateJobStatus({
|
||||
variables: {
|
||||
jobId,
|
||||
status,
|
||||
},
|
||||
});
|
||||
if (onSelect && typeof onSelect === "function") onSelect(status);
|
||||
},
|
||||
[onSelect, jobId, updateJobStatus]
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.root}>
|
||||
<Button
|
||||
mode="outlined"
|
||||
icon="playlist-edit"
|
||||
onPress={show}
|
||||
disabled={disabled || statuses.length === 0}
|
||||
style={styles.trigger}
|
||||
>
|
||||
{currentStatus || label}
|
||||
</Button>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onDismiss={hide}
|
||||
contentContainerStyle={[
|
||||
{ backgroundColor: theme.colors.surface },
|
||||
styles.modalContainer,
|
||||
]}
|
||||
>
|
||||
<Text variant="titleMedium" style={styles.modalTitle}>
|
||||
{label}
|
||||
</Text>
|
||||
<Divider />
|
||||
<FlatList
|
||||
data={statuses}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={({ item }) => {
|
||||
const selected = item === currentStatus;
|
||||
return (
|
||||
<List.Item
|
||||
title={item}
|
||||
onPress={() => handleSelect(item)}
|
||||
style={selected ? styles.selectedItem : undefined}
|
||||
titleStyle={selected ? { fontWeight: "600" } : undefined}
|
||||
left={(props) =>
|
||||
selected ? <List.Icon {...props} icon="check" /> : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
ItemSeparatorComponent={() => <Divider />}
|
||||
style={styles.list}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
/>
|
||||
<Button
|
||||
onPress={hide}
|
||||
style={styles.closeBtn}
|
||||
mode="text"
|
||||
icon="close"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</Modal>
|
||||
</Portal>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
alignSelf: "flex-start",
|
||||
},
|
||||
trigger: {
|
||||
minWidth: 140,
|
||||
},
|
||||
modalContainer: {
|
||||
marginHorizontal: 24,
|
||||
borderRadius: 16,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 8,
|
||||
height: "60%",
|
||||
display: "flex",
|
||||
},
|
||||
modalTitle: {
|
||||
paddingHorizontal: 12,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
list: {
|
||||
marginTop: 4,
|
||||
flex: 1,
|
||||
},
|
||||
selectedItem: {
|
||||
backgroundColor: "rgba(0,0,0,0.05)",
|
||||
},
|
||||
closeBtn: {
|
||||
marginTop: 8,
|
||||
alignSelf: "flex-end",
|
||||
},
|
||||
});
|
||||
|
||||
export default JobStatusSelector;
|
||||
|
||||
/**
|
||||
* Usage example:
|
||||
* <JobStatusSelector
|
||||
* statuses={availableStatuses}
|
||||
* currentStatus={job.status}
|
||||
* onSelect={(newStatus) => console.log('Status changed to', newStatus)}
|
||||
* />
|
||||
*/
|
||||
Reference in New Issue
Block a user