add suppliers
This commit is contained in:
@@ -54,6 +54,8 @@ function ContactsContent() {
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selectedType, setSelectedType] = useState('all')
|
||||
const [selectedSpecialization, setSelectedSpecialization] = useState('all')
|
||||
const [createDefaultType, setCreateDefaultType] = useState('INDIVIDUAL')
|
||||
const [selectedStatus, setSelectedStatus] = useState('all')
|
||||
const [selectedSource, setSelectedSource] = useState('all')
|
||||
const [selectedRating, setSelectedRating] = useState('all')
|
||||
@@ -82,6 +84,7 @@ function ContactsContent() {
|
||||
|
||||
if (searchTerm) filters.search = searchTerm
|
||||
if (selectedType !== 'all') filters.type = selectedType
|
||||
if (selectedSpecialization !== 'all') filters.specialization = selectedSpecialization
|
||||
if (selectedStatus !== 'all') filters.status = selectedStatus
|
||||
if (selectedSource !== 'all') filters.source = selectedSource
|
||||
if (selectedRating !== 'all') filters.rating = parseInt(selectedRating)
|
||||
@@ -97,7 +100,7 @@ function ContactsContent() {
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [currentPage, searchTerm, selectedType, selectedStatus, selectedSource, selectedRating, selectedCategory])
|
||||
}, [currentPage, searchTerm, selectedType, selectedStatus, selectedSource, selectedRating, selectedCategory, selectedSpecialization])
|
||||
|
||||
useEffect(() => {
|
||||
const debounce = setTimeout(() => {
|
||||
@@ -109,7 +112,7 @@ function ContactsContent() {
|
||||
|
||||
useEffect(() => {
|
||||
fetchContacts()
|
||||
}, [currentPage, selectedType, selectedStatus, selectedSource, selectedRating, selectedCategory])
|
||||
}, [currentPage, selectedType, selectedStatus, selectedSource, selectedRating, selectedCategory, selectedSpecialization])
|
||||
|
||||
const handleCreate = async (data: CreateContactData) => {
|
||||
setSubmitting(true)
|
||||
@@ -247,6 +250,21 @@ function ContactsContent() {
|
||||
return (contact as any).nameAr || ''
|
||||
}
|
||||
|
||||
const supplierSpecializations = [
|
||||
'كاميرات',
|
||||
'شبكات',
|
||||
'أجهزة كومبيوتر',
|
||||
'projectors',
|
||||
'مقاسم هاتفية',
|
||||
' Mobile - Tablet',
|
||||
'firewall',
|
||||
'طاقة بديلة',
|
||||
'حديد',
|
||||
' باركود - POS',
|
||||
'أجهزة منزلية',
|
||||
'تكييف وتبريد',
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<header className="bg-white shadow-sm border-b">
|
||||
@@ -307,8 +325,20 @@ function ContactsContent() {
|
||||
<button
|
||||
onClick={() => {
|
||||
resetForm()
|
||||
setCreateDefaultType('SUPPLIER')
|
||||
setShowCreateModal(true)
|
||||
}}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 transition-colors"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
إضافة موردين
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
resetForm()
|
||||
setCreateDefaultType('INDIVIDUAL')
|
||||
setShowCreateModal(true)
|
||||
}}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
@@ -406,8 +436,10 @@ function ContactsContent() {
|
||||
<option value="UN">UN</option>
|
||||
<option value="NGO">NGO</option>
|
||||
<option value="INSTITUTION">Institution</option>
|
||||
<option value="SUPPLIER">Suppliers - موردين</option>
|
||||
</select>
|
||||
|
||||
|
||||
<select
|
||||
value={selectedStatus}
|
||||
onChange={(e) => setSelectedStatus(e.target.value)}
|
||||
@@ -452,7 +484,23 @@ function ContactsContent() {
|
||||
<option value="OTHER">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
اختصاص المورد
|
||||
</label>
|
||||
<select
|
||||
value={selectedSpecialization}
|
||||
onChange={(e) => setSelectedSpecialization(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
>
|
||||
<option value="all">كل الاختصاصات</option>
|
||||
{supplierSpecializations.map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Rating</label>
|
||||
<select
|
||||
@@ -493,6 +541,7 @@ function ContactsContent() {
|
||||
setSelectedRating('all')
|
||||
setSelectedCategory('all')
|
||||
setCurrentPage(1)
|
||||
setSelectedSpecialization('all')
|
||||
}}
|
||||
className="w-full px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
@@ -528,8 +577,8 @@ function ContactsContent() {
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
||||
>
|
||||
Create First Contact
|
||||
</button>
|
||||
{createDefaultType === 'SUPPLIER' ? 'إضافة مورد' : 'Create New Contact'}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -732,7 +781,8 @@ function ContactsContent() {
|
||||
size="xl"
|
||||
>
|
||||
<ContactForm
|
||||
key="create-contact"
|
||||
key={`create-${createDefaultType}`}
|
||||
defaultType={createDefaultType}
|
||||
onSubmit={async (data) => {
|
||||
await handleCreate(data as CreateContactData)
|
||||
}}
|
||||
|
||||
@@ -337,15 +337,14 @@ export default function ManagedExpenseClaimsPage() {
|
||||
|
||||
<div className="space-y-1">
|
||||
{claim.attachments.map((attachment) => (
|
||||
<a
|
||||
<button
|
||||
key={attachment.id}
|
||||
href={`${process.env.NEXT_PUBLIC_API_URL}/hr/portal/expense-claims/attachments/${attachment.id}/view`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="block rounded-lg border bg-white px-3 py-2 text-sm text-blue-600 hover:underline"
|
||||
type="button"
|
||||
onClick={() => openAttachment(attachment)}
|
||||
className="block w-full text-right rounded-lg border bg-white px-3 py-2 text-sm text-blue-600 hover:underline"
|
||||
>
|
||||
{attachment.originalName}
|
||||
</a>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ interface ContactFormProps {
|
||||
onSubmit: (data: CreateContactData | UpdateContactData) => Promise<void>
|
||||
onCancel: () => void
|
||||
submitting?: boolean
|
||||
defaultType?: string
|
||||
}
|
||||
|
||||
const buildInitialFormData = (contact?: Contact): CreateContactData => ({
|
||||
@@ -39,10 +40,12 @@ const buildInitialFormData = (contact?: Contact): CreateContactData => ({
|
||||
customFields: contact?.customFields
|
||||
})
|
||||
|
||||
export default function ContactForm({ contact, onSubmit, onCancel, submitting = false }: ContactFormProps) {
|
||||
const isEdit = !!contact
|
||||
export default function ContactForm({ contact, onSubmit, onCancel, submitting = false, defaultType = 'INDIVIDUAL' }: ContactFormProps) { const isEdit = !!contact
|
||||
|
||||
const [formData, setFormData] = useState<CreateContactData>(buildInitialFormData(contact))
|
||||
const [formData, setFormData] = useState<CreateContactData>({
|
||||
...buildInitialFormData(contact),
|
||||
type: contact?.type || defaultType,
|
||||
})
|
||||
const [rating, setRating] = useState<number>(contact?.rating || 0)
|
||||
const [newTag, setNewTag] = useState('')
|
||||
const [formErrors, setFormErrors] = useState<Record<string, string>>({})
|
||||
@@ -50,11 +53,13 @@ export default function ContactForm({ contact, onSubmit, onCancel, submitting =
|
||||
const [employees, setEmployees] = useState<Employee[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
setFormData(buildInitialFormData(contact))
|
||||
setFormData({
|
||||
...buildInitialFormData(contact),
|
||||
type: contact?.type || defaultType,
|
||||
})
|
||||
setRating(contact?.rating || 0)
|
||||
setNewTag('')
|
||||
setFormErrors({})
|
||||
}, [contact])
|
||||
}, [contact, defaultType])
|
||||
|
||||
useEffect(() => {
|
||||
categoriesAPI.getTree().then(setCategories).catch(() => {})
|
||||
@@ -92,9 +97,23 @@ export default function ContactForm({ contact, onSubmit, onCancel, submitting =
|
||||
'NGO',
|
||||
'INSTITUTION',
|
||||
])
|
||||
|
||||
const isSupplier = formData.type === 'SUPPLIER'
|
||||
const isOrganizationType = organizationTypes.has(formData.type)
|
||||
const showCompanyFields = isOrganizationType
|
||||
const showCompanyFields = isOrganizationType && !isSupplier
|
||||
const supplierSpecializations = [
|
||||
'كاميرات',
|
||||
'شبكات',
|
||||
'أجهزة كومبيوتر',
|
||||
'projectors',
|
||||
'مقاسم هاتفية',
|
||||
' Mobile - Tablet',
|
||||
'firewall',
|
||||
'طاقة بديلة',
|
||||
'حديد',
|
||||
' باركود - POS',
|
||||
'أجهزة منزلية',
|
||||
'تكييف وتبريد',
|
||||
]
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const errors: Record<string, string> = {}
|
||||
@@ -103,6 +122,10 @@ export default function ContactForm({ contact, onSubmit, onCancel, submitting =
|
||||
errors.name = 'Name must be at least 2 characters'
|
||||
}
|
||||
|
||||
if (isSupplier && (!formData.tags || formData.tags.length === 0)) {
|
||||
errors.tags = 'الاختصاص مطلوب'
|
||||
}
|
||||
|
||||
if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
||||
errors.email = 'Invalid email format'
|
||||
}
|
||||
@@ -158,8 +181,8 @@ const cleanData = Object.entries({ ...formData, rating }).reduce((acc, [key, val
|
||||
}
|
||||
|
||||
if (!cleanData.parentId) {
|
||||
delete cleanData.parentId
|
||||
}
|
||||
delete cleanData.parentId
|
||||
}
|
||||
|
||||
if (cleanData.categories && cleanData.categories.length === 0) {
|
||||
delete cleanData.categories
|
||||
@@ -223,6 +246,7 @@ const cleanData = Object.entries({ ...formData, rating }).reduce((acc, [key, val
|
||||
<option value="UN">UN - الأمم المتحدة</option>
|
||||
<option value="NGO">NGO - منظمة غير حكومية</option>
|
||||
<option value="INSTITUTION">Institution - مؤسسة</option>
|
||||
<option value="SUPPLIER">Supplier - مورد</option>
|
||||
</select>
|
||||
{formErrors.type && <p className="text-red-500 text-xs mt-1">{formErrors.type}</p>}
|
||||
</div>
|
||||
@@ -251,50 +275,106 @@ const cleanData = Object.entries({ ...formData, rating }).reduce((acc, [key, val
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{isOrganizationType ? 'Contact Person Name' : 'Name'} <span className="text-red-500">*</span>
|
||||
{isSupplier ? 'اسم المورد' : isOrganizationType ? 'Contact Person Name' : 'Name'} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder={isOrganizationType ? 'Enter contact person name' : 'Enter contact name'}
|
||||
placeholder={isSupplier ? 'أدخل اسم المورد' : isOrganizationType ? 'Enter contact person name' : 'Enter contact name'}
|
||||
/>
|
||||
{formErrors.name && <p className="text-red-500 text-xs mt-1">{formErrors.name}</p>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Rating
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<button
|
||||
key={star}
|
||||
type="button"
|
||||
onClick={() => setRating(star)}
|
||||
className="focus:outline-none transition-colors"
|
||||
>
|
||||
<Star
|
||||
className={`h-8 w-8 ${
|
||||
star <= rating
|
||||
? 'fill-yellow-400 text-yellow-400'
|
||||
: 'text-gray-300 hover:text-yellow-200'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
{rating > 0 && (
|
||||
{isSupplier && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
الاختصاص <span className="text-red-500">*</span>
|
||||
</label>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{supplierSpecializations.map((item) => {
|
||||
const checked = formData.tags?.includes(item) || false
|
||||
return (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setFormData({
|
||||
...formData,
|
||||
tags: checked
|
||||
? (formData.tags || []).filter((tag) => tag !== item)
|
||||
: [...(formData.tags || []), item],
|
||||
})
|
||||
}}
|
||||
className={`px-3 py-1 rounded-full border text-sm transition-colors ${
|
||||
checked
|
||||
? 'bg-blue-600 text-white border-blue-600'
|
||||
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={newTag}
|
||||
onChange={(e) => setNewTag(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && (e.preventDefault(), addTag())}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="أضف اختصاص آخر"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRating(0)}
|
||||
className="ml-2 text-sm text-gray-500 hover:text-gray-700"
|
||||
onClick={addTag}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
||||
>
|
||||
Clear
|
||||
<Plus className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{formErrors.tags && <p className="text-red-500 text-xs mt-1">{formErrors.tags}</p>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isSupplier && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Rating
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<button
|
||||
key={star}
|
||||
type="button"
|
||||
onClick={() => setRating(star)}
|
||||
className="focus:outline-none transition-colors"
|
||||
>
|
||||
<Star
|
||||
className={`h-8 w-8 ${
|
||||
star <= rating
|
||||
? 'fill-yellow-400 text-yellow-400'
|
||||
: 'text-gray-300 hover:text-yellow-200'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
{rating > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRating(0)}
|
||||
className="ml-2 text-sm text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -413,64 +493,76 @@ const cleanData = Object.entries({ ...formData, rating }).reduce((acc, [key, val
|
||||
)}
|
||||
|
||||
<div className="pt-6 border-t">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Address Information</h3>
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
{isSupplier ? 'العنوان' : 'Address Information'}
|
||||
</h3>
|
||||
|
||||
{isSupplier ? (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Street Address
|
||||
العنوان
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.address || ''}
|
||||
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="Street address"
|
||||
placeholder="العنوان"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
City
|
||||
Street Address
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.city || ''}
|
||||
onChange={(e) => setFormData({ ...formData, city: e.target.value })}
|
||||
value={formData.address || ''}
|
||||
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="City"
|
||||
placeholder="Street address"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Country
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.country || ''}
|
||||
onChange={(e) => setFormData({ ...formData, country: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="Country"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">City</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.city || ''}
|
||||
onChange={(e) => setFormData({ ...formData, city: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="City"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Postal Code
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.postalCode || ''}
|
||||
onChange={(e) => setFormData({ ...formData, postalCode: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="Postal code"
|
||||
/>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Country</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.country || ''}
|
||||
onChange={(e) => setFormData({ ...formData, country: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="Country"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Postal Code</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.postalCode || ''}
|
||||
onChange={(e) => setFormData({ ...formData, postalCode: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="Postal code"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isSupplier && (
|
||||
<div className="pt-6 border-t">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Categories</h3>
|
||||
<CategorySelector
|
||||
@@ -479,6 +571,7 @@ const cleanData = Object.entries({ ...formData, rating }).reduce((acc, [key, val
|
||||
multiSelect={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isCompanyEmployeeSelected && (
|
||||
<div className="pt-6 border-t">
|
||||
@@ -501,48 +594,50 @@ const cleanData = Object.entries({ ...formData, rating }).reduce((acc, [key, val
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="pt-6 border-t">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Tags</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={newTag}
|
||||
onChange={(e) => setNewTag(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && (e.preventDefault(), addTag())}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="Add a tag (press Enter)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={addTag}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
{!isSupplier && (
|
||||
<div className="pt-6 border-t">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Tags</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={newTag}
|
||||
onChange={(e) => setNewTag(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && (e.preventDefault(), addTag())}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900"
|
||||
placeholder="Add a tag (press Enter)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={addTag}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{formData.tags && formData.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{formData.tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="inline-flex items-center gap-2 px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm"
|
||||
>
|
||||
#{tag}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeTag(tag)}
|
||||
className="hover:text-red-600 transition-colors"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{formData.tags && formData.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{formData.tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="inline-flex items-center gap-2 px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm"
|
||||
>
|
||||
#{tag}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeTag(tag)}
|
||||
className="hover:text-red-600 transition-colors"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DuplicateAlert
|
||||
email={formData.email}
|
||||
|
||||
@@ -65,6 +65,7 @@ export interface UpdateContactData extends Partial<CreateContactData> {
|
||||
export interface ContactFilters {
|
||||
search?: string
|
||||
type?: string
|
||||
specialization?: string
|
||||
status?: string
|
||||
category?: string
|
||||
source?: string
|
||||
@@ -87,6 +88,7 @@ export const contactsAPI = {
|
||||
const params = new URLSearchParams()
|
||||
if (filters.search) params.append('search', filters.search)
|
||||
if (filters.type) params.append('type', filters.type)
|
||||
if (filters.specialization) params.append('specialization', filters.specialization)
|
||||
if (filters.status) params.append('status', filters.status)
|
||||
if (filters.category) params.append('category', filters.category)
|
||||
if (filters.source) params.append('source', filters.source)
|
||||
|
||||
Reference in New Issue
Block a user