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
|
||||||
@@ -71,4 +71,24 @@ export const useUpdateSiteSetting = () => {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: api.updateSiteSetting,
|
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 {
|
import type {
|
||||||
AboutUsResponse,
|
AboutUsResponse,
|
||||||
CreateAboutUs,
|
CreateAboutUs,
|
||||||
|
CreateDocumentType,
|
||||||
CreateFaqType,
|
CreateFaqType,
|
||||||
|
DocumentTypesResponse,
|
||||||
FaqDetailResponse,
|
FaqDetailResponse,
|
||||||
FaqResponse,
|
FaqResponse,
|
||||||
UpdateSiteSetting,
|
UpdateSiteSetting,
|
||||||
@@ -64,3 +66,18 @@ export const updateSiteSetting = async (params: UpdateSiteSetting) => {
|
|||||||
const { data } = await axios.post(`/admin/site-setting/update`, params);
|
const { data } = await axios.post(`/admin/site-setting/update`, params);
|
||||||
return data;
|
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;
|
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 PrivacyPage from '@/pages/setting/PrivacyPage'
|
||||||
import ReturnPolicyPage from '@/pages/setting/ReturnPolicyPage'
|
import ReturnPolicyPage from '@/pages/setting/ReturnPolicyPage'
|
||||||
import JobsPage from '@/pages/setting/JobsPage'
|
import JobsPage from '@/pages/setting/JobsPage'
|
||||||
|
import ManageDocument from '@/pages/setting/ManageDocument'
|
||||||
|
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
@@ -131,6 +132,7 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Pages.pages.privacy} element={<PrivacyPage />} />
|
<Route path={Pages.pages.privacy} element={<PrivacyPage />} />
|
||||||
<Route path={Pages.pages.returnPolicy} element={<ReturnPolicyPage />} />
|
<Route path={Pages.pages.returnPolicy} element={<ReturnPolicyPage />} />
|
||||||
<Route path={Pages.pages.jobs} element={<JobsPage />} />
|
<Route path={Pages.pages.jobs} element={<JobsPage />} />
|
||||||
|
<Route path={Pages.pages.documents} element={<ManageDocument />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user