Improve search layouts.

This commit is contained in:
Patrick Fic
2025-10-22 15:07:57 -07:00
parent 49d40aa038
commit c62d2ab05f
9 changed files with 546 additions and 328 deletions

View File

@@ -47,12 +47,10 @@ function AuthenticatedLayout() {
<Label>{t("settings.titles.settings")}</Label> <Label>{t("settings.titles.settings")}</Label>
</NativeTabs.Trigger> </NativeTabs.Trigger>
<NativeTabs.Trigger name="search" role="search"> <NativeTabs.Trigger name="search" role="search">
{Platform.select({ {Platform.select({
//ios: <Icon sf="checklist" drawable="custom_android_drawable" />, //ios: <Icon sf="checklist" drawable="custom_android_drawable" />,
android: ( android: (
<Icon <Icon src={<VectorIcon family={MaterialIcons} name="search" />} />
src={<VectorIcon family={MaterialIcons} name="search" />}
/>
), ),
})} })}
<Label>Search</Label> <Label>Search</Label>

View File

@@ -11,6 +11,8 @@ export default function SearchLayout() {
headerSearchBarOptions: { headerSearchBarOptions: {
placement: "automatic", placement: "automatic",
placeholder: "Search", placeholder: "Search",
autoFocus: true,
shouldShowHintSearchIcon: true,
onChangeText: (event) => { onChangeText: (event) => {
router.setParams({ router.setParams({
globalSearch: event?.nativeEvent?.text, globalSearch: event?.nativeEvent?.text,

View File

@@ -1,12 +1,4 @@
import { useLocalSearchParams } from "expo-router"; import GlobalSearch from "../../components/global-search/global-search";
import { ScrollView } from "react-native";
import { Text } from "react-native-paper";
export default function SearchIndex() { export default function SearchIndex() {
const { globalSearch } = useLocalSearchParams(); return <GlobalSearch />;
return (
<ScrollView>
<Text>Some search results here for: {globalSearch}</Text>
</ScrollView>
);
} }

View File

@@ -131,6 +131,27 @@
<folder_node> <folder_node>
<name>labels</name> <name>labels</name>
<children> <children>
<concept_node>
<name>error</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>na</name> <name>na</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -152,6 +173,58 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>uploadprogress</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>globalsearch</name>
<children>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>entersearch</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
</children> </children>
@@ -4932,6 +5005,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>wronginfo</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>wrongpassword</name> <name>wrongpassword</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -0,0 +1,109 @@
import axios from "axios";
import { useLocalSearchParams } from "expo-router";
import debounce from "lodash/debounce";
import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { FlatList, View } from "react-native";
import { ActivityIndicator, Text } from "react-native-paper";
import env from "../../env";
import ErrorDisplay from "../error/error-display";
import JobListItem from "../jobs-list/job-list-item";
// Debounce delay (ms) adjust as needed
const GLOBAL_SEARCH_DEBOUNCE_MS = 400;
/**
* Hook returning a debounced search trigger.
* It recreates the debounced function only when the underlying callback changes.
* Placeholder: Replace the body of `performSearch` with real API / GraphQL logic.
*/
function useDebouncedGlobalSearch(onSearch, delay = GLOBAL_SEARCH_DEBOUNCE_MS) {
const debouncedRef = useRef(() => {});
useEffect(() => {
// Create debounced wrapper
const debounced = debounce((query) => {
onSearch(query);
}, delay);
debouncedRef.current = debounced;
return () => debounced.cancel();
}, [onSearch, delay]);
return useCallback((query) => {
debouncedRef.current && debouncedRef.current(query);
}, []);
}
export default function GlobalSearch() {
const { globalSearch } = useLocalSearchParams();
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [results, setResults] = useState([]);
const { t } = useTranslation();
// Placeholder: Replace with actual API call (e.g., Apollo client query, REST fetch, Redux saga dispatch)
const performSearch = useCallback(async (query) => {
// Defensive trimr
const q = (query || "").trim();
if (!q) return;
setLoading(true);
setError(null);
// TODO: Integrate real search endpoint
console.log(`[GlobalSearch] (debounced placeholder) searching for: "${q}"`);
try {
const searchData = await axios.post(`${env.API_URL}/search`, {
search: q,
});
if (searchData.data) {
const jobResults = searchData.data?.hits?.hits
?.filter((hit) => hit._index === "jobs")
.map((hit) => hit._source);
setResults(jobResults);
} else {
setError("No results available. Try again.");
}
} catch (error) {
console.error("Search error:", error);
setError(error.message);
}
setLoading(false);
}, []);
const debouncedSearch = useDebouncedGlobalSearch(performSearch);
// Trigger debounced search when the route param changes
useEffect(() => {
if (typeof globalSearch === "string" && globalSearch.length > 0) {
debouncedSearch(globalSearch);
}
}, [globalSearch, debouncedSearch]);
if (globalSearch === undefined || globalSearch.trim() === "") {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text variant="bodyMedium" style={{ margin: 12, textAlign: "center" }}>
{t("globalsearch.labels.entersearch")}
</Text>
</View>
);
}
return (
<View style={{ flex: 1 }}>
{loading && <ActivityIndicator size="large" />}
{error && <ErrorDisplay errorMessage={error} />}
<Text variant="titleSmall" style={{ margin: 12, alignSelf: "center" }}>
{results.length} results found
</Text>
<FlatList
style={{ flex: 1 }}
data={results}
keyExtractor={(item) => item.id?.toString()}
renderItem={(object) => <JobListItem item={object.item} />}
/>
</View>
);
}

View File

@@ -3,7 +3,7 @@ import { useRouter } from "expo-router";
import React, { memo, useCallback } from "react"; import React, { memo, useCallback } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Pressable, StyleSheet, View } from "react-native"; import { Pressable, StyleSheet, View } from "react-native";
import { IconButton, Text, useTheme } from "react-native-paper"; import { Chip, IconButton, Text, useTheme } from "react-native-paper";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.analytics"; import { logImEXEvent } from "../../firebase/firebase.analytics";
@@ -86,6 +86,7 @@ function JobListItemComponent({ openImagePicker, item }) {
{vehicle} {vehicle}
</Text> </Text>
)} )}
<Chip style>{item.status}</Chip>
</View> </View>
</View> </View>
<IconButton <IconButton
@@ -158,6 +159,7 @@ const styles = StyleSheet.create({
}, },
body: { body: {
marginTop: 2, marginTop: 2,
flexDirection: "row", gap: 8, alignItems: "center",
}, },
ownerText: { ownerText: {
fontWeight: "600", fontWeight: "600",

View File

@@ -1,313 +1,318 @@
{ {
"translation": { "translation": {
"app": { "app": {
"nomobileaccess": "Your shop does not currently have access to ImEX Mobile. ", "nomobileaccess": "Your shop does not currently have access to ImEX Mobile. ",
"title": "ImEX Mobile" "title": "ImEX Mobile"
}, },
"camera": { "camera": {
"titles": { "titles": {
"cameratab": "Camera" "cameratab": "Camera"
} }
}, },
"general": { "general": {
"actions": { "actions": {
"signout": "Sign Out" "signout": "Sign Out"
}, },
"labels": { "labels": {
"na": "N/A", "error": "Error",
"error": "Error", "na": "N/A",
"uploadprogress": "Upload Progress" "uploadprogress": "Upload Progress"
} }
}, },
"jobdetail": { "globalsearch": {
"labels": { "labels": {
"claiminformation": "Claim Information", "entersearch": "Recent items"
"dates": "Dates", }
"documents": "Docs", },
"employeeassignments": "Employee Assignments", "jobdetail": {
"job": "Job", "labels": {
"jobinfo": "Job Information", "claiminformation": "Claim Information",
"lines": "Lines", "dates": "Dates",
"lines_desc": "Desc.", "documents": "Docs",
"lines_lb_hrs": "Hrs", "employeeassignments": "Employee Assignments",
"lines_lbr_ty": "Lbr. Ty.", "job": "Job",
"lines_part_type": "Part Ty.", "jobinfo": "Job Information",
"lines_price": "$", "lines": "Lines",
"lines_qty": "Qty.", "lines_desc": "Desc.",
"nojobnotes": "There are no notes.", "lines_lb_hrs": "Hrs",
"notes": "Notes" "lines_lbr_ty": "Lbr. Ty.",
}, "lines_part_type": "Part Ty.",
"lbr_types": { "lines_price": "$",
"LA1": "LA1", "lines_qty": "Qty.",
"LA2": "LA2", "nojobnotes": "There are no notes.",
"LA3": "LA3", "notes": "Notes"
"LA4": "LA4", },
"LAA": "Aluminum", "lbr_types": {
"LAB": "Body", "LA1": "LA1",
"LAD": "Diagnostic", "LA2": "LA2",
"LAE": "Electrical", "LA3": "LA3",
"LAF": "Frame", "LA4": "LA4",
"LAG": "Glass", "LAA": "Aluminum",
"LAM": "Mechanical", "LAB": "Body",
"LAR": "Refinish", "LAD": "Diagnostic",
"LAS": "Structural", "LAE": "Electrical",
"LAU": "User Defined" "LAF": "Frame",
}, "LAG": "Glass",
"part_types": { "LAM": "Mechanical",
"CCC": "CC Cleaning", "LAR": "Refinish",
"CCD": "CC Damage Waiver", "LAS": "Structural",
"CCDR": "CC Daily Rate", "LAU": "User Defined"
"CCF": "CC Refuel", },
"CCM": "CC Mileage", "part_types": {
"PAA": "Aftermarket", "CCC": "CC Cleaning",
"PAC": "Rechromed", "CCD": "CC Damage Waiver",
"PAE": "Existing", "CCDR": "CC Daily Rate",
"PAL": "LKQ", "CCF": "CC Refuel",
"PAM": "Remanufactured", "CCM": "CC Mileage",
"PAN": "New/OEM", "PAA": "Aftermarket",
"PAO": "Other", "PAC": "Rechromed",
"PAP": "OEM Partial", "PAE": "Existing",
"PAR": "Recored", "PAL": "LKQ",
"PAS": "Sublet", "PAM": "Remanufactured",
"PASL": "Sublet" "PAN": "New/OEM",
} "PAO": "Other",
}, "PAP": "OEM Partial",
"joblist": { "PAR": "Recored",
"actions": { "PAS": "Sublet",
"refresh": "Refresh", "PASL": "Sublet"
"swipecamera": "Add Pictures/Video" }
}, },
"labels": { "joblist": {
"activejobs": "Jobs", "actions": {
"detail": "Job Detail", "refresh": "Refresh",
"nojobs": "There are no active jobs.", "swipecamera": "Add Pictures/Video"
"search": "Search..." },
}, "labels": {
"titles": { "activejobs": "Jobs",
"jobtab": "Jobs" "detail": "Job Detail",
} "nojobs": "There are no active jobs.",
}, "search": "Search..."
"mediabrowser": { },
"actions": { "titles": {
"refresh": "Refresh", "jobtab": "Jobs"
"upload": "Upload" }
}, },
"labels": { "mediabrowser": {
"converting": "Converting", "actions": {
"deleteafterupload": "Delete After Upload", "refresh": "Refresh",
"localserver": "Local Server URL: {{url}}", "upload": "Upload"
"nomedia": "Look's like there's no media on your device. Take some photos or videos and they will appear here.", },
"selectjob": "--- Select a job ---", "labels": {
"selectjobassetselector": "Please select a job to upload media. ", "converting": "Converting",
"storageexceeded": "Unable to uploaded selected files because there is not sufficient space available on this job.", "deleteafterupload": "Delete After Upload",
"storageexceeded_title": "Unable to upload file(s)", "localserver": "Local Server URL: {{url}}",
"storageused": "Storage Used: {{used}} / {{total}} ({{percent}}%)", "nomedia": "Look's like there's no media on your device. Take some photos or videos and they will appear here.",
"temporarystorage": "* Temporary Storage *", "selectjob": "--- Select a job ---",
"uploading": "Uploading" "selectjobassetselector": "Please select a job to upload media. ",
}, "storageexceeded": "Unable to uploaded selected files because there is not sufficient space available on this job.",
"titles": { "storageexceeded_title": "Unable to upload file(s)",
"mediabrowsertab": "Media Browser" "storageused": "Storage Used: {{used}} / {{total}} ({{percent}}%)",
} "temporarystorage": "* Temporary Storage *",
}, "uploading": "Uploading"
"mediacache": { },
"actions": { "titles": {
"deleteall": "Delete All", "mediabrowsertab": "Media Browser"
"uploadall": "Upload All" }
}, },
"titles": { "mediacache": {
"mediacachetab": "Media" "actions": {
} "deleteall": "Delete All",
}, "uploadall": "Upload All"
"messaging": { },
"titles": { "titles": {
"messagingtab": "Messaging" "mediacachetab": "Media"
} }
}, },
"more": { "messaging": {
"titles": { "titles": {
"moretab": "More" "messagingtab": "Messaging"
} }
}, },
"objects": { "more": {
"jobs": { "titles": {
"fields": { "moretab": "More"
"actual_completion": "Actual Completion", }
"actual_delivery": "Actual Delivery", },
"actual_in": "Actual In", "objects": {
"adjustment_bottom_line": "Adjustments", "jobs": {
"ca_gst_registrant": "GST Registrant", "fields": {
"category": "Category", "actual_completion": "Actual Completion",
"ccc": "CC Cleaning", "actual_delivery": "Actual Delivery",
"ccd": "CC Damage Waiver", "actual_in": "Actual In",
"ccdr": "CC Daily Rate", "adjustment_bottom_line": "Adjustments",
"ccf": "CC Refuel", "ca_gst_registrant": "GST Registrant",
"ccm": "CC Mileage", "category": "Category",
"cieca_id": "CIECA ID", "ccc": "CC Cleaning",
"claim_total": "Claim Total", "ccd": "CC Damage Waiver",
"class": "Class", "ccdr": "CC Daily Rate",
"clm_no": "Claim #", "ccf": "CC Refuel",
"clm_total": "Claim Total", "ccm": "CC Mileage",
"csr": "Customer Service Rep.", "cieca_id": "CIECA ID",
"customerowing": "Customer Owing", "claim_total": "Claim Total",
"date_closed": "Closed", "class": "Class",
"date_estimated": "Date Estimated", "clm_no": "Claim #",
"date_exported": "Exported", "clm_total": "Claim Total",
"date_invoiced": "Invoiced", "csr": "Customer Service Rep.",
"date_open": "Open", "customerowing": "Customer Owing",
"date_scheduled": "Scheduled", "date_closed": "Closed",
"ded_amt": "Deductible", "date_estimated": "Date Estimated",
"ded_status": "Deductible Status", "date_exported": "Exported",
"depreciation_taxes": "Depreciation/Taxes", "date_invoiced": "Invoiced",
"employee_body": "Body", "date_open": "Open",
"employee_csr": "CSR", "date_scheduled": "Scheduled",
"employee_prep": "Prep", "ded_amt": "Deductible",
"employee_refinish": "Refinish", "ded_status": "Deductible Status",
"est_addr1": "Appraiser Address", "depreciation_taxes": "Depreciation/Taxes",
"est_co_nm": "Appraiser", "employee_body": "Body",
"est_ct_fn": "Appraiser First Name", "employee_csr": "CSR",
"est_ct_ln": "Appraiser Last Name", "employee_prep": "Prep",
"est_ea": "Appraiser Email", "employee_refinish": "Refinish",
"est_number": "Estimate #", "est_addr1": "Appraiser Address",
"est_ph1": "Appraiser Phone #", "est_co_nm": "Appraiser",
"federal_tax_payable": "Federal Tax Payable", "est_ct_fn": "Appraiser First Name",
"federal_tax_rate": "Federal Tax Rate", "est_ct_ln": "Appraiser Last Name",
"ins_addr1": "Insurance Co. Address", "est_ea": "Appraiser Email",
"ins_city": "Insurance City", "est_number": "Estimate #",
"ins_co_id": "Insurance Co. ID", "est_ph1": "Appraiser Phone #",
"ins_co_nm": "Insurance Company Name", "federal_tax_payable": "Federal Tax Payable",
"ins_ct_fn": "File Handler First Name", "federal_tax_rate": "Federal Tax Rate",
"ins_ct_ln": "File Handler Last Name", "ins_addr1": "Insurance Co. Address",
"ins_ea": "File Handler Email", "ins_city": "Insurance City",
"ins_ph1": "File Handler Phone #", "ins_co_id": "Insurance Co. ID",
"intake": { "ins_co_nm": "Insurance Company Name",
"label": "Label", "ins_ct_fn": "File Handler First Name",
"name": "Name", "ins_ct_ln": "File Handler Last Name",
"required": "Required?", "ins_ea": "File Handler Email",
"type": "Type" "ins_ph1": "File Handler Phone #",
}, "intake": {
"kmin": "Mileage In", "label": "Label",
"kmout": "Mileage Out", "name": "Name",
"la1": "LA1", "required": "Required?",
"la2": "LA2", "type": "Type"
"la3": "LA3", },
"la4": "LA4", "kmin": "Mileage In",
"laa": "Aluminum ", "kmout": "Mileage Out",
"lab": "Body", "la1": "LA1",
"labor_rate_desc": "Labor Rate Name", "la2": "LA2",
"lad": "Diagnostic", "la3": "LA3",
"lae": "Electrical", "la4": "LA4",
"laf": "Frame", "laa": "Aluminum ",
"lag": "Glass", "lab": "Body",
"lam": "Mechanical", "labor_rate_desc": "Labor Rate Name",
"lar": "Refinish", "lad": "Diagnostic",
"las": "Structural", "lae": "Electrical",
"lau": "LAU", "laf": "Frame",
"local_tax_rate": "Local Tax Rate", "lag": "Glass",
"loss_date": "Loss Date", "lam": "Mechanical",
"loss_desc": "Loss Description", "lar": "Refinish",
"ma2s": "2 Stage Paint", "las": "Structural",
"ma3s": "3 Stage Pain", "lau": "LAU",
"mabl": "MABL?", "local_tax_rate": "Local Tax Rate",
"macs": "MACS?", "loss_date": "Loss Date",
"mahw": "Hazardous Waste", "loss_desc": "Loss Description",
"mapa": "Paint Materials", "ma2s": "2 Stage Paint",
"mash": "Shop Materials", "ma3s": "3 Stage Pain",
"matd": "Tire Disposal", "mabl": "MABL?",
"other_amount_payable": "Other Amount Payable", "macs": "MACS?",
"owner": "Owner", "mahw": "Hazardous Waste",
"owner_owing": "Cust. Owes", "mapa": "Paint Materials",
"ownr_ea": "Email", "mash": "Shop Materials",
"ownr_ph1": "Phone 1", "matd": "Tire Disposal",
"paa": "Aftermarket", "other_amount_payable": "Other Amount Payable",
"pae": "Existing", "owner": "Owner",
"pal": "LKQ", "owner_owing": "Cust. Owes",
"pam": "Remanufactured", "ownr_ea": "Email",
"pan": "OEM/New", "ownr_ph1": "Phone 1",
"pao": "Other", "paa": "Aftermarket",
"pap": "EOM Partial", "pae": "Existing",
"par": "Re-cored", "pal": "LKQ",
"pas": "Sublet", "pam": "Remanufactured",
"pay_date": "Pay Date", "pan": "OEM/New",
"phoneshort": "PH", "pao": "Other",
"policy_no": "Policy #", "pap": "EOM Partial",
"ponumber": "PO Number", "par": "Re-cored",
"rate_la1": "LA1", "pas": "Sublet",
"rate_la2": "LA2", "pay_date": "Pay Date",
"rate_la3": "LA3", "phoneshort": "PH",
"rate_la4": "LA4", "policy_no": "Policy #",
"rate_laa": "Aluminum", "ponumber": "PO Number",
"rate_lab": "Body", "rate_la1": "LA1",
"rate_lad": "Diagnostic", "rate_la2": "LA2",
"rate_lae": "Electrical", "rate_la3": "LA3",
"rate_laf": "Frame", "rate_la4": "LA4",
"rate_lag": "Glass", "rate_laa": "Aluminum",
"rate_lam": "Mechanical", "rate_lab": "Body",
"rate_lar": "Refinish", "rate_lad": "Diagnostic",
"rate_las": "Sublet", "rate_lae": "Electrical",
"rate_lau": "Aluminum", "rate_laf": "Frame",
"rate_ma2s": "2 Stage Paint", "rate_lag": "Glass",
"rate_ma3s": "3 Stage Paint", "rate_lam": "Mechanical",
"rate_mabl": "MABL??", "rate_lar": "Refinish",
"rate_macs": "MACS??", "rate_las": "Sublet",
"rate_mahw": "Hazardous Waste", "rate_lau": "Aluminum",
"rate_mapa": "Paint Materials", "rate_ma2s": "2 Stage Paint",
"rate_mash": "Shop Material", "rate_ma3s": "3 Stage Paint",
"rate_matd": "Tire Disposal", "rate_mabl": "MABL??",
"referralsource": "Referral Source", "rate_macs": "MACS??",
"regie_number": "Registration #", "rate_mahw": "Hazardous Waste",
"repairtotal": "Repair Total", "rate_mapa": "Paint Materials",
"ro_number": "RO #", "rate_mash": "Shop Material",
"scheduled_completion": "Scheduled Completion", "rate_matd": "Tire Disposal",
"scheduled_delivery": "Scheduled Delivery", "referralsource": "Referral Source",
"scheduled_in": "Scheduled In", "regie_number": "Registration #",
"selling_dealer": "Selling Dealer", "repairtotal": "Repair Total",
"selling_dealer_contact": "Selling Dealer Contact", "ro_number": "RO #",
"servicecar": "Service Car", "scheduled_completion": "Scheduled Completion",
"servicing_dealer": "Servicing Dealer", "scheduled_delivery": "Scheduled Delivery",
"servicing_dealer_contact": "Servicing Dealer Contact", "scheduled_in": "Scheduled In",
"specialcoveragepolicy": "Special Coverage Policy", "selling_dealer": "Selling Dealer",
"state_tax_rate": "State Tax Rate", "selling_dealer_contact": "Selling Dealer Contact",
"status": "Job Status", "servicecar": "Service Car",
"storage_payable": "Storage/PVRT", "servicing_dealer": "Servicing Dealer",
"tax_registration_number": "Tax Registration Number", "servicing_dealer_contact": "Servicing Dealer Contact",
"towing_payable": "Towing Payable", "specialcoveragepolicy": "Special Coverage Policy",
"unitnumber": "Unit #", "state_tax_rate": "State Tax Rate",
"updated_at": "Updated At", "status": "Job Status",
"uploaded_by": "Uploaded By", "storage_payable": "Storage/PVRT",
"vehicle": "Vehicle" "tax_registration_number": "Tax Registration Number",
}, "towing_payable": "Towing Payable",
"labels": { "unitnumber": "Unit #",
"inproduction": "In Production" "updated_at": "Updated At",
} "uploaded_by": "Uploaded By",
} "vehicle": "Vehicle"
}, },
"production": { "labels": {
"titles": { "inproduction": "In Production"
"production": "Production" }
} }
}, },
"settings": { "production": {
"labels": { "titles": {
"version": "Version {{number}}" "production": "Production"
}, }
"titles": { },
"settings": "Settings" "settings": {
} "labels": {
}, "version": "Version {{number}}"
"signin": { },
"actions": { "titles": {
"signin": "Sign In" "settings": "Settings"
}, }
"errors": { },
"emailformat": "The email you have entered is not formatted correctly. ", "signin": {
"usernotfound": "No user found.", "actions": {
"wronginfo": "The email or password you entered is not correct.", "signin": "Sign In"
"wrongpassword": "The password you entered is not correct." },
}, "errors": {
"fields": { "emailformat": "The email you have entered is not formatted correctly. ",
"email": "Email", "usernotfound": "No user found.",
"password": "Password" "wronginfo": "The email or password you entered is not correct.",
} "wrongpassword": "The password you entered is not correct."
} },
} "fields": {
"email": "Email",
"password": "Password"
}
}
}
} }

View File

@@ -14,7 +14,14 @@
"signout": "" "signout": ""
}, },
"labels": { "labels": {
"na": "" "error": "",
"na": "",
"uploadprogress": ""
}
},
"globalsearch": {
"labels": {
"entersearch": ""
} }
}, },
"jobdetail": { "jobdetail": {
@@ -299,6 +306,7 @@
"errors": { "errors": {
"emailformat": "", "emailformat": "",
"usernotfound": "", "usernotfound": "",
"wronginfo": "",
"wrongpassword": "" "wrongpassword": ""
}, },
"fields": { "fields": {

View File

@@ -14,7 +14,14 @@
"signout": "" "signout": ""
}, },
"labels": { "labels": {
"na": "" "error": "",
"na": "",
"uploadprogress": ""
}
},
"globalsearch": {
"labels": {
"entersearch": ""
} }
}, },
"jobdetail": { "jobdetail": {
@@ -299,6 +306,7 @@
"errors": { "errors": {
"emailformat": "", "emailformat": "",
"usernotfound": "", "usernotfound": "",
"wronginfo": "",
"wrongpassword": "" "wrongpassword": ""
}, },
"fields": { "fields": {