manage document

This commit is contained in:
hamid zarghami
2025-09-30 15:41:42 +03:30
parent c0777d7755
commit 5f5efbd063
6 changed files with 337 additions and 1 deletions
+130
View File
@@ -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
+21 -1
View File
@@ -71,4 +71,24 @@ export const useUpdateSiteSetting = () => {
return useMutation({
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;
};
+25
View File
@@ -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;
}
+2
View File
@@ -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>