Files
crm-app-ui/src/app/(index)/leads/[id]/page.tsx
T

521 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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