521 lines
19 KiB
TypeScript
521 lines
19 KiB
TypeScript
'use client'
|
||
import Link from 'next/link'
|
||
import { Input } from "@/components/ui/input"
|
||
import { Textarea } from "@/components/ui/textarea"
|
||
import { Label } from "@/components/ui/label"
|
||
import { useEffect, useState } from 'react'
|
||
import { Button } from '@/components/ui/button'
|
||
import LSearchCustomers from '../components/l-search-customers'
|
||
import axiosInstance from '@/axios/axiosInstance'
|
||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||
import * as z from "zod"
|
||
import { useRouter } from 'next/navigation'
|
||
import { IoMdArrowRoundBack } from "react-icons/io";
|
||
import { AlertCircle } from "lucide-react"
|
||
import {
|
||
Alert,
|
||
AlertDescription,
|
||
AlertTitle,
|
||
} from "@/components/ui/alert"
|
||
import { ILead } from './interfaces'
|
||
import { toast } from "sonner"
|
||
import { FaFaceGrinStars, FaFaceMeh, FaFaceFrown, FaFaceSmile } from "react-icons/fa6";
|
||
import { TbTargetArrow } from "react-icons/tb";
|
||
import { HiBellAlert } from "react-icons/hi2";
|
||
import { useForm, Controller, SubmitHandler } from "react-hook-form"
|
||
import LeadComment from '../components/lead-comment'
|
||
import LeadCommentAdd from '../components/lead-comment-add'
|
||
import RemainderModal from '../../components/ui/reminder-modal'
|
||
|
||
|
||
|
||
|
||
interface IFormValues {
|
||
lead_types: [
|
||
{
|
||
id: Number,
|
||
title: String
|
||
},
|
||
],
|
||
product_types: [
|
||
{
|
||
id: Number,
|
||
name: String
|
||
},
|
||
],
|
||
lead_source: [
|
||
{
|
||
id: Number,
|
||
title: String
|
||
},
|
||
],
|
||
lead_event: [
|
||
{
|
||
id: Number,
|
||
name: string,
|
||
message: string
|
||
send_sms: boolean
|
||
},
|
||
]
|
||
lead_steps: [
|
||
{
|
||
id: Number,
|
||
name: String
|
||
},
|
||
],
|
||
shahr: [
|
||
{
|
||
id: Number,
|
||
name: String
|
||
is_ostan: boolean,
|
||
"ostan": any
|
||
},
|
||
]
|
||
}
|
||
|
||
interface IShahr {
|
||
id: Number,
|
||
name: String
|
||
is_ostan: boolean,
|
||
"ostan": any
|
||
}
|
||
|
||
interface IFormNewLead {
|
||
title: string,
|
||
customer_id: number,
|
||
mobile1: number,
|
||
mobile2: number,
|
||
phone: number,
|
||
lead_event_id: number,
|
||
desc: string,
|
||
product_type_id: number ,
|
||
lead_type_id: number,
|
||
lead_source_id: number,
|
||
ostan: number,
|
||
city_id: number,
|
||
lead_step_id: number,
|
||
}
|
||
|
||
const formSchema = z.object({
|
||
title: z.string().min(5, {
|
||
message: "Username must be at least 2 characters.",
|
||
}),
|
||
})
|
||
|
||
const LeadIdPage = ({params}:{params: {id: number} })=>{
|
||
|
||
const [remainderModalHidden, setRemainderModalHidden] = useState<boolean>(true)
|
||
const [cSearchOpen, setCSearchOpen] = useState<boolean>(false)
|
||
const [statusButtonState, setStatusButtonState] = useState<{su:boolean, fa:boolean, un: boolean}>({su:false, fa:false, un: false})
|
||
const [customerName, setCustomerName] = useState<any>()
|
||
const [formValues, setFormValues] = useState<IFormValues | null>(null)
|
||
const [shahr, setShahr] = useState<IShahr[] | []>([])
|
||
const [leadForm, setLeadForm] = useState<IFormNewLead | null>(null)
|
||
const [submitButtonEnable, setSubmitButtonEnable] = useState<boolean>(true)
|
||
const [lead, setLead] = useState<ILead | null>(null)
|
||
const router = useRouter()
|
||
|
||
useEffect(()=>{
|
||
|
||
setValue('title', lead?.title)
|
||
setValue('customer_id', lead?.customer.id)
|
||
setCustomerName(lead?.customer.name_family)
|
||
setValue('desc', lead?.desc)
|
||
setValue('product_type_id', lead?.product_type.id)
|
||
setValue('lead_type_id', lead?.type.id)
|
||
setValue('lead_source_id', lead?.source.id)
|
||
setValue('ostan', lead?.city.ostan.id)
|
||
setValue('lead_step_id', lead?.step.id)
|
||
setValue('mobile1', lead?.mobile1)
|
||
setValue('mobile2', lead?.mobile2)
|
||
setValue('phone', lead?.phone)
|
||
setValue('lead_event_id', lead?.lead_event)
|
||
|
||
changeOstanById(lead?.city.ostan.id, lead?.city.id)
|
||
|
||
|
||
}, [lead, ])
|
||
|
||
|
||
const {
|
||
register,
|
||
control,
|
||
handleSubmit,
|
||
watch,
|
||
setValue,
|
||
formState: { errors },
|
||
} = useForm<IFormNewLead>({
|
||
shouldFocusError: false,
|
||
})
|
||
|
||
const onSubmit: SubmitHandler<IFormNewLead> = async (data) => {
|
||
|
||
await setSubmitButtonEnable(false)
|
||
|
||
try {
|
||
const result = await axiosInstance.patch(`lead/${lead?.id}/edit/`, data)
|
||
|
||
if(result.status == 201){
|
||
setSubmitButtonEnable(true)
|
||
queryClient.invalidateQueries({ queryKey: ['form-items-and-lead'] })
|
||
|
||
toast("ویرایش شد", {
|
||
description: "سرنخ با موفقیت ویرایش شد",
|
||
action: {
|
||
label: "بستن",
|
||
onClick: () => console.log("Undo"),
|
||
},
|
||
})
|
||
|
||
} else {
|
||
alert("خطا در ثبت")
|
||
}
|
||
|
||
} catch (error) {
|
||
console.log(error)
|
||
}
|
||
|
||
}
|
||
|
||
const onChangeStatus = async (status:string)=>{
|
||
const editStatusRs = await axiosInstance.patch(`lead/${lead?.id}/edit-status/`, {
|
||
status: status
|
||
})
|
||
|
||
if(editStatusRs.status=201){
|
||
queryClient.invalidateQueries({ queryKey: ['form-items-and-lead'] });
|
||
setTimeout(()=>{
|
||
alert("تغییر وضعیت سرنخ انجام شد");
|
||
}, 400)
|
||
}
|
||
|
||
}
|
||
|
||
const changeOstanById = async (ostan_id:any, city_id:any)=>{
|
||
|
||
const shahrha = await formValues?.shahr
|
||
|
||
if(shahrha){
|
||
|
||
const shahrF = await shahrha.filter((item, key)=>{
|
||
return item.ostan == ostan_id
|
||
})
|
||
|
||
setShahr(shahrF)
|
||
|
||
setTimeout(()=>{ setValue('city_id', city_id) }, 500)
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
}
|
||
|
||
const changeOstan = async (e:any)=>{
|
||
const ostan_id = await e.target.value
|
||
const shahrha = await formValues?.shahr
|
||
const shahr = await shahrha.filter((item, key)=>{
|
||
return item.ostan == ostan_id
|
||
})
|
||
setShahr(shahr)
|
||
|
||
setLeadForm((prevLeadForm)=>({
|
||
...prevLeadForm!,
|
||
ostan: ostan_id
|
||
}));
|
||
|
||
console.log(leadForm)
|
||
}
|
||
|
||
const queryClient = useQueryClient()
|
||
|
||
const result = useQuery({ queryKey:['form-items-and-lead'], queryFn: async ()=>{
|
||
|
||
const leadData = await axiosInstance.get(`lead/${params.id}/`)
|
||
const formData = await axiosInstance.get('lead/form-items/')
|
||
|
||
return {
|
||
leadData: leadData,
|
||
formData: formData
|
||
}
|
||
|
||
},onSuccess: async (result)=>{
|
||
await setFormValues(result.formData.data)
|
||
await setLead(result.leadData.data)
|
||
},})
|
||
|
||
if (result.isLoading){
|
||
return (<p className='text-center mt-5'>
|
||
درحال دریافت اطلاعات...
|
||
</p>)
|
||
}
|
||
|
||
|
||
return (
|
||
<div className="w-full md:w-[60%] lg:w-[90%] m-auto " dir="rtl">
|
||
|
||
<div className="bg-white rounded-md p-4 flex flex-row mt-7 mb-3 justify-between items-center">
|
||
|
||
<div className="text-md flex flex-row gap-x-2">
|
||
<span>
|
||
وضعیت سرنخ:
|
||
</span>
|
||
|
||
{lead?.status == 'SU' ? <FaFaceGrinStars size={26} color={'green'} /> : null}
|
||
{lead?.status == 'FA' ? <FaFaceFrown size={26} color={'red'} /> : null}
|
||
{lead?.status == 'UN' || null ? <FaFaceSmile size={26} color={'#757575'} /> : null}
|
||
|
||
</div>
|
||
|
||
<div className="flex flex-row gap-x-2">
|
||
<div className='flex flex-row gap-x-4 border p-2 rounded-md'>
|
||
|
||
<div onClick={()=>{onChangeStatus('SU')}} className='' onMouseLeave={()=>{ setStatusButtonState( (prev) => ({...prev, su:false}) ) }} onMouseOver={()=>{ setStatusButtonState( (prev) => ({...prev, su:true}) ) }}>
|
||
<FaFaceGrinStars size={26} color={statusButtonState.su ? 'green': '#757575'} />
|
||
</div>
|
||
|
||
<div onClick={()=>{onChangeStatus("FA")}} className='' onMouseLeave={()=>{ setStatusButtonState( (prev) => ({...prev, fa:false}) ) }} onMouseOver={()=>{ setStatusButtonState( (prev) => ({...prev, fa:true}) ) }}>
|
||
<FaFaceFrown size={26} color={statusButtonState.fa ? 'red': '#757575'} />
|
||
</div>
|
||
|
||
<div onClick={()=>{onChangeStatus("UN")}} className='' onMouseLeave={()=>{ setStatusButtonState( (prev) => ({...prev, un:false}) ) }} onMouseOver={()=>{ setStatusButtonState( (prev) => ({...prev, un:true}) ) }}>
|
||
<FaFaceSmile size={26} color={statusButtonState.un ? 'black': '#757575'} />
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
<div className='flex items-center flex-row gap-x-2'>
|
||
|
||
<div className='relative'>
|
||
|
||
<div onClick={()=>{setRemainderModalHidden(false)}} className='bg-gray-100 border pr-3 pl-3 pt-2 pb-2 rounded-md text-white'>
|
||
<HiBellAlert size={25} color={lead?.lead_reminder?.id ? 'red': 'black'} />
|
||
</div>
|
||
|
||
|
||
<RemainderModal
|
||
objID={lead?.id}
|
||
reminder_data={lead?.lead_reminder}
|
||
onOpenClose={setRemainderModalHidden} hidden={remainderModalHidden} />
|
||
|
||
</div>
|
||
|
||
|
||
<div onClick={()=>{router.back()}} className="bg-gray-100 border pr-3 pl-3 pt-2 pb-2 rounded-md text-white">
|
||
<IoMdArrowRoundBack color={"black"} size={"25"} />
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
<div className="mt-0 bg-white p-4 rounded-md mb-2">
|
||
|
||
<form onSubmit={handleSubmit(onSubmit)} className="formNewLead flex flex-col gap-y-4">
|
||
|
||
<div className='title'>
|
||
<Label htmlFor="title">عنوان سرنخ</Label>
|
||
<Input {...register("title", { required: 'عنوان ضروری است', minLength: {value: 5, message: 'عنوان مناسب تری بنویسید'} } )} id="title" />
|
||
</div>
|
||
|
||
<div className='customer relative w-full'>
|
||
|
||
<Label htmlFor="title">مشتری</Label>
|
||
|
||
<Controller
|
||
name="customer_id"
|
||
control={control}
|
||
rules={{ required: 'انتخاب مشتری ضروری است' }}
|
||
render={ ({ field: { onChange, onBlur, value, ref } }) => (
|
||
<>
|
||
<span
|
||
className="w-[100%] justify-between block border rounded-md p-2 cursor-pointer"
|
||
onClick={()=>setCSearchOpen(!cSearchOpen)}
|
||
ref={ref}
|
||
>
|
||
{customerName ? customerName : 'برای انتخاب مشتری کلیک کنید'}
|
||
</span>
|
||
|
||
<div className={`mt-1 customer-select ${cSearchOpen ? null : 'hidden'} h-auto absolute bg-white border-red-800 border w-full rounded-md p-4`}>
|
||
<LSearchCustomers onSelect={onChange} searchBoxState={setCSearchOpen} changeCustomerName={setCustomerName} />
|
||
</div>
|
||
</>
|
||
|
||
)}
|
||
/>
|
||
|
||
</div>
|
||
|
||
<div className='grid grid-cols-4 gap-x-4 gap-y-4'>
|
||
|
||
|
||
<div className='phoneasli-1'>
|
||
<Label>تلفن همراه 1</Label>
|
||
<Input {...register("mobile1", { required: 'تلفن همراه 1 ضروری است', pattern: { value: /^([+\u002B\u06F9\u0669]?[\u06F0-\u06F9\u0660-\u0669]?[\u06F0-\u06F9\u0660-\u0669]?|0|۰|٠)?[9\u06F9\u0669][0-9\u06F0-\u06F9\u0660-\u0669]{9}$/, message: 'فرمت تلفن همراه 1 صحیح نیست'} } )} />
|
||
</div>
|
||
|
||
|
||
<div className='phoneasli-2'>
|
||
<Label>تلفن همراه 2</Label>
|
||
<Input {...register("mobile2", { required: false, pattern: { value: /^([+\u002B\u06F9\u0669]?[\u06F0-\u06F9\u0660-\u0669]?[\u06F0-\u06F9\u0660-\u0669]?|0|۰|٠)?[9\u06F9\u0669][0-9\u06F0-\u06F9\u0660-\u0669]{9}$/, message: 'فرمت تلفن همراه 2 صحیح نیست'} } )} />
|
||
</div>
|
||
|
||
<div className='phoneasli-2'>
|
||
<Label>تلفن ثابت</Label>
|
||
<Input {...register("phone", { required: false })} />
|
||
</div>
|
||
|
||
<div className='event w-full'>
|
||
<Label>رویداد</Label>
|
||
|
||
|
||
<select {...register("lead_event_id", { required: 'رویداد را انتخاب کنید'} )} className='border rounded-sm w-full h-10'>
|
||
<option value={''}>انتخاب کنید</option>
|
||
<option value={'0'}>رویداد ندارد</option>
|
||
|
||
|
||
{formValues?.lead_event.map((item, key)=>{
|
||
return (<option value={item.id} key={key}>{item.name}</option>)
|
||
})}
|
||
|
||
</select>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div className='desc w-full'>
|
||
<Label htmlFor="desc">توضیحات</Label>
|
||
<Textarea {...register("desc", { required: 'توضیحات سرنخ ضروری است', minLength: {value: 30, message: 'توضیحات کامل تری بنویسید'} } )} id="desc" />
|
||
</div>
|
||
|
||
|
||
<div className='types grid grid-cols-3 gap-x-4 gap-y-4'>
|
||
|
||
|
||
<div className='prod-type'>
|
||
<Label htmlFor="desc">نوع محصول</Label>
|
||
|
||
<select {...register("product_type_id", { required: 'نوع محصول را انتخاب کنید'} )} className='border rounded-sm w-full h-10'>
|
||
<option value={""}>انتخاب کنید</option>
|
||
|
||
{formValues?.product_types.map((item, key)=>{
|
||
return (<option value={item.id} key={key}>{item.name}</option>)
|
||
})}
|
||
|
||
|
||
</select>
|
||
</div>
|
||
|
||
<div className='lead-type'>
|
||
<Label htmlFor="desc">نوع سرنخ</Label>
|
||
|
||
<select {...register("lead_type_id", { required: 'نوع سرنخ را انتخاب کنید'} )} className='border rounded-sm w-full h-10'>
|
||
<option value={''}>انتخاب کنید</option>
|
||
|
||
{formValues?.lead_types.map((item, key)=>{
|
||
return (<option value={item.id} key={key}>{item.title}</option>)
|
||
})}
|
||
|
||
</select>
|
||
</div>
|
||
|
||
|
||
<div className='lead-source'>
|
||
<Label htmlFor="desc">منبع سرنخ</Label>
|
||
|
||
<select {...register("lead_source_id", { required: 'منبع سرنخ را انتخاب کنید'} )} className='border rounded-sm w-full h-10'>
|
||
<option value={''} >انتخاب کنید</option>
|
||
{formValues?.lead_source.map((item, key)=>{
|
||
return (<option value={item.id} key={key}>{item.title}</option>)
|
||
})}
|
||
</select>
|
||
</div>
|
||
|
||
|
||
|
||
<div className='ostan'>
|
||
<Label htmlFor="desc">استان</Label>
|
||
|
||
<select {...register("ostan", { required: false} )} onChange={(e:any)=>{changeOstan(e)}} className='border rounded-sm w-full h-10'>
|
||
<option>انتخاب کنید</option>
|
||
{formValues?.shahr.map((item, key)=>{
|
||
if(item.is_ostan==true){
|
||
return (<option value={item.id} key={key}>{item.name}</option>)
|
||
}
|
||
})}
|
||
</select>
|
||
</div>
|
||
|
||
|
||
<div className='shahr'>
|
||
<Label htmlFor="desc">شهر</Label>
|
||
|
||
<select {...register("city_id", { required: 'شهر را انتخاب کنید'} )} className='border rounded-sm w-full h-10'>
|
||
<option value={''}>انتخاب کنید</option>
|
||
{shahr.map((item, key)=>{
|
||
return (<option value={item.id} key={key}>{item.name}</option>)
|
||
})}
|
||
</select>
|
||
</div>
|
||
|
||
<div className='lead-step'>
|
||
<Label htmlFor="desc">مرحله</Label>
|
||
|
||
<select {...register("lead_step_id", { required: 'مرحله سرنخ را مشخص کنید'} )} className='border rounded-sm w-full h-10'>
|
||
<option value={''}>انتخاب کنید</option>
|
||
{formValues?.lead_steps.map((item, key)=>{
|
||
return (<option value={item.id} key={key}>{item.name}</option>)
|
||
})}
|
||
</select>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{ Object.keys(errors).length ? (
|
||
|
||
<Alert variant="destructive">
|
||
<AlertCircle className="h-4 w-4" />
|
||
<AlertTitle>خطا</AlertTitle>
|
||
<AlertDescription>
|
||
{Object.keys(errors).map((item, key)=>{
|
||
return <p key={key}>{errors[item]?.message}</p>
|
||
})}
|
||
</AlertDescription>
|
||
</Alert>
|
||
|
||
) : null}
|
||
|
||
|
||
<div className='flex flex-row justify-center mt-4'>
|
||
{!submitButtonEnable ? (<p className='text-center text-red-600'>در حال ثبت اطلاعات....</p>) : (
|
||
<input disabled={submitButtonEnable ? false : true } onClick={()=>{ console.log(Object.keys(errors)) } } className={'bg-gray-400 text-white pr-4 pl-4 pt-2 pb-2 rounded-md'} type='submit' value={'ویرایش'} />
|
||
)}
|
||
</div>
|
||
</form>
|
||
|
||
</div>
|
||
|
||
<div className='flex flex-col w-full border-b-[1px] border-gray-400 pb-2 mb-4'>
|
||
|
||
<LeadCommentAdd lead_id={lead?.id} />
|
||
|
||
</div>
|
||
|
||
<div className='flex flex-col gap-y-2'>
|
||
|
||
{lead?.lead_comment.map((item, key)=>{
|
||
return <LeadComment data={item} key={key} />
|
||
})}
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default LeadIdPage |