manage document
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import { type FC, useState } from 'react'
|
||||
import { useGetDocuments } from './hooks/useSettingData'
|
||||
import { type DocumentTypeItem } from './types/Types'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import Error from '../../components/Error'
|
||||
import Td from '../../components/Td'
|
||||
import { Calendar, DocumentText, TickCircle } from 'iconsax-react'
|
||||
import Button from '@/components/Button'
|
||||
import AddDocumentModal from './components/AddDocumentModal'
|
||||
|
||||
const ManageDocument: FC = () => {
|
||||
|
||||
const { data: documents, isLoading, error } = useGetDocuments()
|
||||
|
||||
// Modal states
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
|
||||
// Handlers
|
||||
const handleOpenModal = () => {
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setIsModalOpen(false)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<PageLoading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<Error errorText="خطا در بارگذاری اطلاعات اسناد" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const documentItems = documents?.results?.documentTypes || [];
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
if (!dateString) return '-';
|
||||
return new Date(dateString).toLocaleDateString('fa-IR');
|
||||
};
|
||||
|
||||
const truncateText = (text: string, maxLength: number = 50) => {
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength) + '...';
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex justify-end mt-5'>
|
||||
<Button
|
||||
label='افزودن اسناد'
|
||||
onClick={handleOpenModal}
|
||||
className='w-fit'
|
||||
/>
|
||||
</div>
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'>
|
||||
<table className='w-full text-sm'>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={'عنوان'} />
|
||||
<Td text={'توضیحات'} />
|
||||
<Td text={'ضروری'} />
|
||||
<Td text={'تاریخ ایجاد'} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{documentItems.length === 0 ? (
|
||||
<tr className='tr'>
|
||||
<td colSpan={5} className="text-center py-8 text-gray-500">
|
||||
هیچ سندی یافت نشد
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
documentItems.map((item: DocumentTypeItem) => (
|
||||
<tr key={item._id} className='tr'>
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
<DocumentText size={16} color="#8C90A3" />
|
||||
<span className="font-medium">{item.title}</span>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text="">
|
||||
<div className="max-w-xs">
|
||||
<span className="text-gray-600 text-sm">
|
||||
{truncateText(item.description)}
|
||||
</span>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
<TickCircle
|
||||
size={16}
|
||||
color={item.required ? "#10B981" : "#EF4444"}
|
||||
/>
|
||||
<span className={item.required ? "text-green-600" : "text-red-600"}>
|
||||
{item.required ? "ضروری" : "اختیاری"}
|
||||
</span>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar size={16} color="#8C90A3" />
|
||||
<span>{formatDate(item.createdAt)}</span>
|
||||
</div>
|
||||
</Td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Add Document Modal */}
|
||||
<AddDocumentModal
|
||||
open={isModalOpen}
|
||||
onClose={handleCloseModal}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ManageDocument
|
||||
@@ -0,0 +1,142 @@
|
||||
import { type FC, useState } from 'react'
|
||||
import { useCreateDocument } from '../hooks/useSettingData'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import Button from '@/components/Button'
|
||||
import Input from '@/components/Input'
|
||||
import Textarea from '@/components/Textarea'
|
||||
import Switch from '@/components/Switch'
|
||||
import DefaulModal from '@/components/DefaulModal'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const AddDocumentModal: FC<Props> = ({ open, onClose }) => {
|
||||
const queryClient = useQueryClient()
|
||||
const createDocumentMutation = useCreateDocument()
|
||||
|
||||
// Form states
|
||||
const [formData, setFormData] = useState({
|
||||
title: '',
|
||||
description: '',
|
||||
required: false
|
||||
})
|
||||
|
||||
// Form errors
|
||||
const [formErrors, setFormErrors] = useState({
|
||||
title: '',
|
||||
description: ''
|
||||
})
|
||||
|
||||
// Handlers
|
||||
const handleClose = () => {
|
||||
onClose()
|
||||
// Reset form when closing
|
||||
setFormData({
|
||||
title: '',
|
||||
description: '',
|
||||
required: false
|
||||
})
|
||||
setFormErrors({
|
||||
title: '',
|
||||
description: ''
|
||||
})
|
||||
}
|
||||
|
||||
const handleInputChange = (field: string, value: string | boolean) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
}))
|
||||
// Clear error when user starts typing
|
||||
if (formErrors[field as keyof typeof formErrors]) {
|
||||
setFormErrors(prev => ({
|
||||
...prev,
|
||||
[field]: ''
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const validateForm = () => {
|
||||
const errors = {
|
||||
title: '',
|
||||
description: ''
|
||||
}
|
||||
|
||||
if (!formData.title.trim()) {
|
||||
errors.title = 'عنوان سند الزامی است'
|
||||
}
|
||||
|
||||
if (!formData.description.trim()) {
|
||||
errors.description = 'توضیحات سند الزامی است'
|
||||
}
|
||||
|
||||
setFormErrors(errors)
|
||||
return !errors.title && !errors.description
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!validateForm()) return
|
||||
|
||||
try {
|
||||
await createDocumentMutation.mutateAsync(formData)
|
||||
handleClose()
|
||||
// Refetch documents
|
||||
queryClient.invalidateQueries({ queryKey: ['documents'] })
|
||||
} catch (error) {
|
||||
console.error('Error creating document:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DefaulModal
|
||||
open={open}
|
||||
close={handleClose}
|
||||
isHeader={true}
|
||||
title_header="افزودن سند جدید"
|
||||
width={500}
|
||||
>
|
||||
<div className="space-y-6 w-[400px] mt-3">
|
||||
<Input
|
||||
label="عنوان سند"
|
||||
value={formData.title}
|
||||
onChange={(e) => handleInputChange('title', e.target.value)}
|
||||
placeholder="عنوان سند را وارد کنید"
|
||||
error_text={formErrors.title}
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="توضیحات سند"
|
||||
value={formData.description}
|
||||
onChange={(e) => handleInputChange('description', e.target.value)}
|
||||
placeholder="توضیحات سند را وارد کنید"
|
||||
error_text={formErrors.description}
|
||||
rows={4}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
active={formData.required}
|
||||
onChange={(value) => handleInputChange('required', value)}
|
||||
label="سند ضروری است"
|
||||
/>
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<Button
|
||||
label="لغو"
|
||||
onClick={handleClose}
|
||||
className="flex-1 bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||
/>
|
||||
<Button
|
||||
label={createDocumentMutation.isPending ? "در حال ذخیره..." : "ذخیره"}
|
||||
onClick={handleSubmit}
|
||||
disabled={createDocumentMutation.isPending}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddDocumentModal
|
||||
@@ -72,3 +72,23 @@ export const useUpdateSiteSetting = () => {
|
||||
mutationFn: api.updateSiteSetting,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateDocument = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.createDocument,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetDocuments = () => {
|
||||
return useQuery({
|
||||
queryKey: ["documents"],
|
||||
queryFn: api.getDocuments,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteDocument = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.deleteDocument,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ import axios from "../../../config/axios";
|
||||
import type {
|
||||
AboutUsResponse,
|
||||
CreateAboutUs,
|
||||
CreateDocumentType,
|
||||
CreateFaqType,
|
||||
DocumentTypesResponse,
|
||||
FaqDetailResponse,
|
||||
FaqResponse,
|
||||
UpdateSiteSetting,
|
||||
@@ -64,3 +66,18 @@ export const updateSiteSetting = async (params: UpdateSiteSetting) => {
|
||||
const { data } = await axios.post(`/admin/site-setting/update`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createDocument = async (params: CreateDocumentType) => {
|
||||
const { data } = await axios.post(`/admin/settings/document-types`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getDocuments = async (): Promise<DocumentTypesResponse> => {
|
||||
const { data } = await axios.get(`/seller/document-types`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteDocument = async (id: string) => {
|
||||
const { data } = await axios.delete(`/admin/settings/document-types/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -112,3 +112,28 @@ export interface UpdateSiteSetting {
|
||||
url: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface CreateDocumentType {
|
||||
title: string;
|
||||
description: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export interface DocumentTypeItem {
|
||||
_id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
required: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface DocumentTypesResults {
|
||||
documentTypes: DocumentTypeItem[];
|
||||
}
|
||||
|
||||
export interface DocumentTypesResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: DocumentTypesResults;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import ShipmentPage from '@/pages/setting/ShipmentPage'
|
||||
import PrivacyPage from '@/pages/setting/PrivacyPage'
|
||||
import ReturnPolicyPage from '@/pages/setting/ReturnPolicyPage'
|
||||
import JobsPage from '@/pages/setting/JobsPage'
|
||||
import ManageDocument from '@/pages/setting/ManageDocument'
|
||||
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
@@ -131,6 +132,7 @@ const MainRouter: FC = () => {
|
||||
<Route path={Pages.pages.privacy} element={<PrivacyPage />} />
|
||||
<Route path={Pages.pages.returnPolicy} element={<ReturnPolicyPage />} />
|
||||
<Route path={Pages.pages.jobs} element={<JobsPage />} />
|
||||
<Route path={Pages.pages.documents} element={<ManageDocument />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user