commit
This commit is contained in:
@@ -6,7 +6,7 @@ import { toast } from 'sonner';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { CrmTextarea } from '@/features/crm/components/crm-form-controls';
|
||||
import {
|
||||
approvalByIdOptions,
|
||||
approvalsQueryOptions
|
||||
@@ -80,11 +80,11 @@ export function QuotationApprovalTab({
|
||||
{canSubmitNow ? (
|
||||
<div className='space-y-3 rounded-lg border p-4'>
|
||||
<div className='text-sm font-medium'>Submit quotation into approval flow</div>
|
||||
<Textarea
|
||||
<CrmTextarea
|
||||
value={remark}
|
||||
onChange={(event) => setRemark(event.target.value)}
|
||||
placeholder='Optional remark for approvers'
|
||||
rows={3}
|
||||
size='md'
|
||||
/>
|
||||
<Button
|
||||
isLoading={submitApproval.isPending}
|
||||
|
||||
@@ -33,7 +33,13 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
CrmCurrencyInput,
|
||||
CrmDateInput,
|
||||
CrmNumberInput,
|
||||
CrmPercentageInput,
|
||||
CrmTextarea,
|
||||
} from "@/features/crm/components/crm-form-controls";
|
||||
import {
|
||||
createQuotationAttachmentMutation,
|
||||
createQuotationCustomerMutation,
|
||||
@@ -168,12 +174,11 @@ function ItemDialog({
|
||||
placeholder="Description"
|
||||
/>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Input
|
||||
<CrmNumberInput
|
||||
value={values.quantity}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, quantity: e.target.value }))
|
||||
onChange={(value) =>
|
||||
setValues((s) => ({ ...s, quantity: value }))
|
||||
}
|
||||
type="number"
|
||||
placeholder="Quantity"
|
||||
/>
|
||||
<Select
|
||||
@@ -199,21 +204,21 @@ function ItemDialog({
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Input
|
||||
<CrmCurrencyInput
|
||||
value={values.unitPrice}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, unitPrice: e.target.value }))
|
||||
onChange={(value) =>
|
||||
setValues((s) => ({ ...s, unitPrice: value }))
|
||||
}
|
||||
type="number"
|
||||
placeholder="Unit price"
|
||||
currencyLabel="THB"
|
||||
/>
|
||||
<Input
|
||||
<CrmCurrencyInput
|
||||
value={values.discount}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, discount: e.target.value }))
|
||||
onChange={(value) =>
|
||||
setValues((s) => ({ ...s, discount: value }))
|
||||
}
|
||||
type="number"
|
||||
placeholder="Discount"
|
||||
currencyLabel="THB"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
@@ -238,21 +243,21 @@ function ItemDialog({
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
<CrmPercentageInput
|
||||
value={values.taxRate}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, taxRate: e.target.value }))
|
||||
onChange={(value) =>
|
||||
setValues((s) => ({ ...s, taxRate: value }))
|
||||
}
|
||||
type="number"
|
||||
placeholder="Tax rate %"
|
||||
/>
|
||||
</div>
|
||||
<Textarea
|
||||
<CrmTextarea
|
||||
value={values.notes}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, notes: e.target.value }))
|
||||
}
|
||||
placeholder="Notes"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
@@ -418,11 +423,11 @@ function TopicDialog({
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Topic title"
|
||||
/>
|
||||
<Textarea
|
||||
<CrmTextarea
|
||||
value={itemsText}
|
||||
onChange={(e) => setItemsText(e.target.value)}
|
||||
placeholder="One line per topic item"
|
||||
rows={6}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
@@ -500,12 +505,12 @@ function FollowupDialog({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4">
|
||||
<Input
|
||||
type="date"
|
||||
<CrmDateInput
|
||||
value={values.followupDate}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, followupDate: e.target.value }))
|
||||
setValues((s) => ({ ...s, followupDate: e }))
|
||||
}
|
||||
className="w-full"
|
||||
/>
|
||||
<Select
|
||||
value={values.followupType}
|
||||
@@ -552,12 +557,12 @@ function FollowupDialog({
|
||||
}
|
||||
placeholder="Outcome"
|
||||
/>
|
||||
<Input
|
||||
type="date"
|
||||
<CrmDateInput
|
||||
value={values.nextFollowupDate}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, nextFollowupDate: e.target.value }))
|
||||
setValues((s) => ({ ...s, nextFollowupDate: e }))
|
||||
}
|
||||
className="w-full"
|
||||
/>
|
||||
<Input
|
||||
value={values.nextAction}
|
||||
@@ -566,12 +571,13 @@ function FollowupDialog({
|
||||
}
|
||||
placeholder="Next action"
|
||||
/>
|
||||
<Textarea
|
||||
<CrmTextarea
|
||||
value={values.notes}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, notes: e.target.value }))
|
||||
}
|
||||
placeholder="Notes"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
@@ -668,21 +674,21 @@ function AttachmentDialog({
|
||||
}
|
||||
placeholder="application/pdf"
|
||||
/>
|
||||
<Input
|
||||
<CrmNumberInput
|
||||
value={values.fileSize}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, fileSize: e.target.value }))
|
||||
onChange={(value) =>
|
||||
setValues((s) => ({ ...s, fileSize: value }))
|
||||
}
|
||||
type="number"
|
||||
placeholder="File size bytes"
|
||||
/>
|
||||
</div>
|
||||
<Textarea
|
||||
<CrmTextarea
|
||||
value={values.description}
|
||||
onChange={(e) =>
|
||||
setValues((s) => ({ ...s, description: e.target.value }))
|
||||
}
|
||||
placeholder="Description"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
|
||||
@@ -10,6 +10,13 @@ import {
|
||||
ProjectPartiesEditor,
|
||||
type ProjectPartyEditorItem
|
||||
} from '@/features/crm/components/project-parties-editor';
|
||||
import {
|
||||
CrmCurrencyInput,
|
||||
CrmDateInput,
|
||||
CrmNumberInput,
|
||||
CrmPercentageInput,
|
||||
CrmTextarea
|
||||
} from '@/features/crm/components/crm-form-controls';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
@@ -27,7 +34,6 @@ import {
|
||||
SheetTitle
|
||||
} from '@/components/ui/sheet';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { quotationCustomersOptions } from '../api/queries';
|
||||
import { createQuotationMutation, updateQuotationMutation } from '../api/mutations';
|
||||
import type { QuotationMutationPayload, QuotationRecord, QuotationReferenceData } from '../api/types';
|
||||
@@ -99,16 +105,21 @@ function toFormState(
|
||||
|
||||
function Field({
|
||||
label,
|
||||
required = false,
|
||||
children,
|
||||
className
|
||||
}: {
|
||||
label: string;
|
||||
required?: boolean;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='mb-2 text-sm font-medium'>{label}</div>
|
||||
<div className='mb-2 text-sm font-medium'>
|
||||
{label}
|
||||
{required ? ' *' : ''}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -308,7 +319,7 @@ export function QuotationFormSheet({
|
||||
</Select>
|
||||
</Field>
|
||||
|
||||
<Field label='Quotation Type'>
|
||||
<Field label='Quotation Type' required>
|
||||
<Select value={state.quotationType} onValueChange={(value) => setField('quotationType', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder='Select type' />
|
||||
@@ -323,12 +334,12 @@ export function QuotationFormSheet({
|
||||
</Select>
|
||||
</Field>
|
||||
|
||||
<Field label='Quotation Date'>
|
||||
<Input type='date' value={state.quotationDate} onChange={(e) => setField('quotationDate', e.target.value)} />
|
||||
<Field label='Quotation Date' required>
|
||||
<CrmDateInput value={state.quotationDate} onChange={(value) => setField('quotationDate', value)} />
|
||||
</Field>
|
||||
|
||||
<Field label='Valid Until'>
|
||||
<Input type='date' value={state.validUntil} onChange={(e) => setField('validUntil', e.target.value)} />
|
||||
<CrmDateInput value={state.validUntil} onChange={(value) => setField('validUntil', value)} />
|
||||
</Field>
|
||||
|
||||
<Field label='Branch'>
|
||||
@@ -378,7 +389,7 @@ export function QuotationFormSheet({
|
||||
</Field>
|
||||
|
||||
<Field label='Exchange Rate'>
|
||||
<Input value={state.exchangeRate} onChange={(e) => setField('exchangeRate', e.target.value)} type='number' step='0.0001' />
|
||||
<CrmNumberInput value={state.exchangeRate} onChange={(value) => setField('exchangeRate', value)} step='0.0001' />
|
||||
</Field>
|
||||
|
||||
<Field label='Project Name'>
|
||||
@@ -414,11 +425,11 @@ export function QuotationFormSheet({
|
||||
</Field>
|
||||
|
||||
<Field label='Chance %'>
|
||||
<Input value={state.chancePercent} onChange={(e) => setField('chancePercent', e.target.value)} type='number' min='0' max='100' />
|
||||
<CrmPercentageInput value={state.chancePercent} onChange={(value) => setField('chancePercent', value)} />
|
||||
</Field>
|
||||
|
||||
<Field label='Discount'>
|
||||
<Input value={state.discount} onChange={(e) => setField('discount', e.target.value)} type='number' step='0.01' />
|
||||
<CrmCurrencyInput value={state.discount} onChange={(value) => setField('discount', value)} currencyLabel='THB' />
|
||||
</Field>
|
||||
|
||||
<Field label='Discount Type'>
|
||||
@@ -438,7 +449,7 @@ export function QuotationFormSheet({
|
||||
</Field>
|
||||
|
||||
<Field label='Tax Rate %'>
|
||||
<Input value={state.taxRate} onChange={(e) => setField('taxRate', e.target.value)} type='number' step='0.01' />
|
||||
<CrmPercentageInput value={state.taxRate} onChange={(value) => setField('taxRate', value)} />
|
||||
</Field>
|
||||
|
||||
<Field label='Sent Via'>
|
||||
@@ -472,13 +483,13 @@ export function QuotationFormSheet({
|
||||
|
||||
<div className='md:col-span-2'>
|
||||
<Field label='Notes'>
|
||||
<Textarea value={state.notes} onChange={(e) => setField('notes', e.target.value)} placeholder='Internal notes' />
|
||||
<CrmTextarea value={state.notes} onChange={(e) => setField('notes', e.target.value)} placeholder='Internal notes' size='md' />
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className='md:col-span-2'>
|
||||
<Field label='Revision Remark'>
|
||||
<Textarea value={state.revisionRemark} onChange={(e) => setField('revisionRemark', e.target.value)} placeholder='Revision note or approval context' />
|
||||
<CrmTextarea value={state.revisionRemark} onChange={(e) => setField('revisionRemark', e.target.value)} placeholder='Revision note or approval context' size='md' />
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user