added full settings options
This commit is contained in:
@@ -23,6 +23,8 @@ import { downloadROPDF, printROPDF } from '../lib/roPdf';
|
||||
import { useSettings } from '../store/settings';
|
||||
import { logError } from '../lib/userMessages';
|
||||
import { parseRepairOrderServices, syncAssignmentsForROServices } from '../lib/technicianData';
|
||||
import { formatServiceTextFields } from '../lib/dataFormatting';
|
||||
import { spellCheckProps } from '../lib/spellcheck';
|
||||
|
||||
type TabId = 'details' | 'services' | 'financial' | 'history' | 'timeline';
|
||||
|
||||
@@ -166,12 +168,14 @@ function PricingBadge({ mode }: { mode: 'full' | 'split' }) {
|
||||
return <span className="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-[10px] font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-300">Split</span>;
|
||||
}
|
||||
|
||||
function EditableServiceRow({ service, index, onChange, onRemove, defaultLaborRate = 145 }: {
|
||||
function EditableServiceRow({ service, index, onChange, onRemove, defaultLaborRate = 145, spellCheck, lang }: {
|
||||
service: Partial<ROService>;
|
||||
index: number;
|
||||
onChange: (index: number, field: string, value: string | number | boolean) => void;
|
||||
onRemove: (index: number) => void;
|
||||
defaultLaborRate?: number;
|
||||
spellCheck?: boolean;
|
||||
lang?: string;
|
||||
}) {
|
||||
const pricingMode = service.pricingMode || 'full';
|
||||
const isSplit = pricingMode === 'split';
|
||||
@@ -246,7 +250,7 @@ function EditableServiceRow({ service, index, onChange, onRemove, defaultLaborRa
|
||||
</div>
|
||||
<div className="sm:col-span-2 lg:col-span-1">
|
||||
<label className="block text-xs text-gray-500 dark:text-gray-400">Description</label>
|
||||
<textarea value={service.description || ''} onChange={(e) => onChange(index, 'description', e.target.value)} placeholder="Description..." rows={1} className="mt-0.5 w-full rounded border border-gray-300 bg-white px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100" />
|
||||
<textarea value={service.description || ''} onChange={(e) => onChange(index, 'description', e.target.value)} placeholder="Description..." rows={1} spellCheck={spellCheck} lang={lang} className="mt-0.5 w-full rounded border border-gray-300 bg-white px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100" />
|
||||
</div>
|
||||
{pricingMode === 'full' ? (
|
||||
<>
|
||||
@@ -305,9 +309,11 @@ function Input({ value, onChange, placeholder, type = 'text', icon }: { value: s
|
||||
);
|
||||
}
|
||||
|
||||
function Textarea({ value, onChange, placeholder, rows = 3 }: { value: string; onChange: (value: string) => void; placeholder?: string; rows?: number }) {
|
||||
function Textarea({ value, onChange, placeholder, rows = 3, spellCheck, lang }: { value: string; onChange: (value: string) => void; placeholder?: string; rows?: number; spellCheck?: boolean; lang?: string }) {
|
||||
return (
|
||||
<textarea value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} rows={rows}
|
||||
spellCheck={spellCheck}
|
||||
lang={lang}
|
||||
className="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:border-green-500 focus:outline-none focus:ring-2 focus:ring-green-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
|
||||
/>
|
||||
);
|
||||
@@ -321,6 +327,8 @@ export default function RODetailModal() {
|
||||
const navigate = useNavigate();
|
||||
const { showToast } = useToast();
|
||||
const settings = useSettings();
|
||||
const serviceTextSpellCheck = spellCheckProps(settings, settings.spellCheckServiceText);
|
||||
const internalNotesSpellCheck = spellCheckProps(settings, settings.spellCheckInternalNotes);
|
||||
const statusConfig = useMemo(() => getStatusConfig(settings), [settings]);
|
||||
|
||||
const modalBackground = (location.state as { backgroundLocation?: unknown })?.backgroundLocation;
|
||||
@@ -864,7 +872,8 @@ export default function RODetailModal() {
|
||||
if (!ro) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
const cleaned = localServices.map((s) => ({
|
||||
const cleaned = localServices.map((s) => {
|
||||
const service = {
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
description: s.description || '',
|
||||
@@ -876,7 +885,9 @@ export default function RODetailModal() {
|
||||
technician: s.technician || '',
|
||||
pricingMode: s.pricingMode || 'full',
|
||||
applyShopCharge: s.applyShopCharge !== false,
|
||||
}));
|
||||
};
|
||||
return settings.applyServiceFormattingToROs ? formatServiceTextFields(service, settings) : service;
|
||||
});
|
||||
const payload = { services: JSON.stringify(cleaned) };
|
||||
await pb.collection('repairOrders').update(ro.id, payload);
|
||||
|
||||
@@ -916,7 +927,7 @@ export default function RODetailModal() {
|
||||
const partsPrice = Number(newServiceForm.partsPrice) || 0;
|
||||
const isSplit = newServiceForm.pricingMode === 'split';
|
||||
const description = newServiceForm.description.trim();
|
||||
const data = {
|
||||
const data = formatServiceTextFields({
|
||||
name: newServiceForm.name.trim(),
|
||||
category: newServiceForm.category.trim(),
|
||||
description,
|
||||
@@ -927,7 +938,7 @@ export default function RODetailModal() {
|
||||
price: isSplit ? laborPrice + partsPrice : fullPrice,
|
||||
active: true,
|
||||
userId,
|
||||
};
|
||||
}, settings.applyServiceFormattingToCatalog ? settings : { ...settings, serviceNameFormat: 'preserve', serviceDescriptionFormat: 'preserve', serviceRecommendationFormat: 'preserve', serviceCategoryFormat: 'preserve', trimServiceText: true, addPeriodToServiceDescriptions: false });
|
||||
try {
|
||||
const created = await pb.collection('services').create(data) as Record<string, unknown>;
|
||||
const newSvc: ServiceItem = {
|
||||
@@ -1054,6 +1065,7 @@ export default function RODetailModal() {
|
||||
onChange={(e) => setReminderNotes(e.target.value)}
|
||||
placeholder="e.g. Next oil change reminder"
|
||||
rows={2}
|
||||
{...internalNotesSpellCheck}
|
||||
className="mt-1 w-full rounded border border-blue-300 bg-white px-2 py-1.5 text-sm dark:border-blue-700 dark:bg-gray-800 dark:text-gray-100"
|
||||
/>
|
||||
</div>
|
||||
@@ -1315,7 +1327,7 @@ export default function RODetailModal() {
|
||||
<div className="mt-3">
|
||||
<FormField label="Description">
|
||||
<div className="space-y-2">
|
||||
<Textarea value={newServiceForm.description} onChange={(v) => setNewServiceForm((p) => ({ ...p, description: v }))} placeholder="Brief description..." rows={2} />
|
||||
<Textarea value={newServiceForm.description} onChange={(v) => setNewServiceForm((p) => ({ ...p, description: v }))} placeholder="Brief description..." rows={2} {...serviceTextSpellCheck} />
|
||||
</div>
|
||||
</FormField>
|
||||
</div>
|
||||
@@ -1373,7 +1385,7 @@ export default function RODetailModal() {
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{localServices.map((svc, i) => (
|
||||
<EditableServiceRow key={i} service={svc} index={i} onChange={handleServiceChange} onRemove={handleRemoveService} defaultLaborRate={settings.defaultLaborRate || 145} />
|
||||
<EditableServiceRow key={i} service={svc} index={i} onChange={handleServiceChange} onRemove={handleRemoveService} defaultLaborRate={settings.defaultLaborRate || 145} {...serviceTextSpellCheck} />
|
||||
))}
|
||||
{localServices.length === 0 && (
|
||||
<p className="text-center text-sm text-gray-400">No services on this RO yet.</p>
|
||||
|
||||
Reference in New Issue
Block a user