diff --git a/babel-translations.babel b/babel-translations.babel index 8375a85..61dc093 100644 --- a/babel-translations.babel +++ b/babel-translations.babel @@ -2685,6 +2685,27 @@ + + inproduction + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + ins_addr1 false diff --git a/components/job-status-selector/JobStatusSelector.tsx b/components/job-status-selector/JobStatusSelector.tsx new file mode 100644 index 0000000..f411880 --- /dev/null +++ b/components/job-status-selector/JobStatusSelector.tsx @@ -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 = ({ + 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 ( + + + + + + {label} + + + { + const selected = item === currentStatus; + return ( + handleSelect(item)} + style={selected ? styles.selectedItem : undefined} + titleStyle={selected ? { fontWeight: "600" } : undefined} + left={(props) => + selected ? : null + } + /> + ); + }} + ItemSeparatorComponent={() => } + style={styles.list} + keyboardShouldPersistTaps="handled" + /> + + + + + ); +}; + +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: + * console.log('Status changed to', newStatus)} + * /> + */ diff --git a/components/job-tombstone/job-tombstone.jsx b/components/job-tombstone/job-tombstone.jsx index 00010cf..4856bb9 100644 --- a/components/job-tombstone/job-tombstone.jsx +++ b/components/job-tombstone/job-tombstone.jsx @@ -7,15 +7,15 @@ import { useTranslation } from "react-i18next"; import { RefreshControl, ScrollView, StyleSheet, View } from "react-native"; import { ActivityIndicator, - Button, Card, - Menu, + Chip, Text, useTheme, } from "react-native-paper"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import DataLabelComponent from "../data-label/data-label"; +import { JobStatusSelector } from "../job-status-selector/JobStatusSelector"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -31,10 +31,7 @@ function JobTombstone({ bodyshop }) { }, skip: !jobId, }); - const [visible, setVisible] = useState(false); - const openMenu = () => setVisible(true); - const closeMenu = () => setVisible(false); - console.log("JobTombstone render", visible); + console.log("JobTombstone render"); const theme = useTheme(); @@ -44,6 +41,8 @@ function JobTombstone({ bodyshop }) { }; const [availableStatuses, setAvailableStatuses] = useState([]); + const job = data?.jobs_by_pk; + useEffect(() => { if (!job || !bodyshop) return; @@ -88,7 +87,7 @@ function JobTombstone({ bodyshop }) { ); } - const job = data.jobs_by_pk; + return ( - {job.status} - {job.status}} - > - {availableStatuses.map((status) => ( - {}} title={status} /> - ))} - + + } + /> {job.inproduction && ( - {t("objects.jobs.labels.inproduction")} + + {t("objects.jobs.labels.inproduction")} + + } + /> )} {job.inproduction && job.production_vars && !!job.production_vars.note && ( - {job.production_vars.note} + {job.production_vars.note}} + /> )} diff --git a/graphql/jobs.queries.js b/graphql/jobs.queries.js index bd4b017..82e59a9 100644 --- a/graphql/jobs.queries.js +++ b/graphql/jobs.queries.js @@ -430,6 +430,7 @@ export const UPDATE_JOB_STATUS = gql` update_jobs(where: { id: { _eq: $jobId } }, _set: { status: $status }) { returning { id + status } } } @@ -886,9 +887,8 @@ export const generate_UPDATE_JOB_KANBAN = ( ) => { const oldChildQuery = ` updateOldChild: update_jobs(where: { id: { _eq: "${oldChildId}" } }, - _set: {kanbanparent: ${ - oldChildNewParent ? `"${oldChildNewParent}"` : null - }}) { + _set: {kanbanparent: ${oldChildNewParent ? `"${oldChildNewParent}"` : null + }}) { returning { id kanbanparent @@ -897,9 +897,8 @@ export const generate_UPDATE_JOB_KANBAN = ( const movedQuery = ` updateMovedChild: update_jobs(where: { id: { _eq: "${movedId}" } }, - _set: {kanbanparent: ${ - movedNewParent ? `"${movedNewParent}"` : null - } , status: "${movedNewStatus}"}) { + _set: {kanbanparent: ${movedNewParent ? `"${movedNewParent}"` : null + } , status: "${movedNewStatus}"}) { returning { id status diff --git a/translations/en-US/common.json b/translations/en-US/common.json index b8a608d..4fba6dc 100644 --- a/translations/en-US/common.json +++ b/translations/en-US/common.json @@ -177,6 +177,7 @@ "est_ph1": "Appraiser Phone #", "federal_tax_payable": "Federal Tax Payable", "federal_tax_rate": "Federal Tax Rate", + "inproduction": "In Production", "ins_addr1": "Insurance Co. Address", "ins_city": "Insurance City", "ins_co_id": "Insurance Co. ID", diff --git a/translations/es-MX/common.json b/translations/es-MX/common.json index f395feb..8bc4793 100644 --- a/translations/es-MX/common.json +++ b/translations/es-MX/common.json @@ -177,6 +177,7 @@ "est_ph1": "Número de teléfono del tasador", "federal_tax_payable": "Impuesto federal por pagar", "federal_tax_rate": "", + "inproduction": "", "ins_addr1": "Dirección de Insurance Co.", "ins_city": "Ciudad de seguros", "ins_co_id": "ID de la compañía de seguros", diff --git a/translations/fr-CA/common.json b/translations/fr-CA/common.json index dccfdc9..d5b903f 100644 --- a/translations/fr-CA/common.json +++ b/translations/fr-CA/common.json @@ -177,6 +177,7 @@ "est_ph1": "Numéro de téléphone de l'évaluateur", "federal_tax_payable": "Impôt fédéral à payer", "federal_tax_rate": "", + "inproduction": "", "ins_addr1": "Adresse Insurance Co.", "ins_city": "Insurance City", "ins_co_id": "ID de la compagnie d'assurance",