Files
danak-admin/src/pages/accessLogs/AccessLogsList.tsx
T
2025-09-02 17:00:37 +03:30

220 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
import React, { useState } from "react";
import {
DocumentText,
DocumentDownload,
Refresh,
Activity,
InfoCircle
} from "iconsax-react";
import Button from "../../components/Button";
import Pagination from "../../components/Pagination";
import TitleLine from "../../components/TitleLine";
import PageLoading from "../../components/PageLoading";
// import Error from "../../components/Error"; // Using custom error display
import FilterPanel from "./components/FilterPanel";
import AccessLogsTable from "./components/AccessLogsTable";
import LogDetailsModal from "./components/LogDetailsModal";
import { useAccessLogs, useAccessLogDetail } from "./hooks/useAccessLogs";
import { AccessLog } from "./types/AccessLogTypes";
const AccessLogsList: React.FC = () => {
const {
logs,
loading,
error,
total,
filters,
updateFilters,
resetFilters,
exportLogs,
refetch,
} = useAccessLogs();
const { log: selectedLog, fetchLog, clearLog } = useAccessLogDetail();
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false);
const handleViewDetails = async (log: AccessLog) => {
await fetchLog(log.id);
setIsDetailsModalOpen(true);
};
const handleCloseDetailsModal = () => {
setIsDetailsModalOpen(false);
clearLog();
};
const handleSort = (field: string, order: "ASC" | "DESC") => {
updateFilters({ sortBy: field, sortOrder: order });
};
const handlePageChange = (page: number) => {
updateFilters({ page });
};
const handleExport = async () => {
try {
await exportLogs();
} catch (error) {
console.error("خطا در خروجی گرفتن:", error);
}
};
if (error) {
return (
<div className="p-6">
<TitleLine title="لاگ‌های دسترسی" />
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex items-center justify-between">
<p className="text-red-800">{error}</p>
<Button
onClick={refetch}
className="bg-red-600 text-white hover:bg-red-700"
>
تلاش مجدد
</Button>
</div>
</div>
</div>
);
}
return (
<div className="p-6 space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<TitleLine title="لاگ‌های دسترسی" />
<p className="text-gray-600 mt-2">
مشاهده و مدیریت تمام فعالیتهای سیستم
</p>
</div>
<div className="flex items-center gap-3">
<Button
onClick={refetch}
disabled={loading}
className="flex items-center gap-2 border border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
>
<Refresh size={16} className={loading ? "animate-spin" : ""} />
بروزرسانی
</Button>
<Button
onClick={handleExport}
disabled={loading || !logs || logs.length === 0}
className="flex items-center gap-2 border border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
>
<DocumentDownload size={16} />
خروجی CSV
</Button>
</div>
</div>
{/* Stats Summary */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<div className="flex items-center gap-3">
<div className="flex-shrink-0 p-2 bg-blue-100 rounded-lg">
<DocumentText size={20} className="text-blue-600" />
</div>
<div>
<p className="text-sm font-medium text-gray-600">کل لاگها</p>
<p className="text-xl font-bold text-gray-900">
{(total || 0).toLocaleString('fa-IR')}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<div className="flex items-center gap-3">
<div className="flex-shrink-0 p-2 bg-green-100 rounded-lg">
<Activity size={20} className="text-green-600" />
</div>
<div>
<p className="text-sm font-medium text-gray-600">صفحه فعلی</p>
<p className="text-xl font-bold text-gray-900">
{filters.page || 1}
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
<div className="flex items-center gap-3">
<div className="flex-shrink-0 p-2 bg-purple-100 rounded-lg">
<InfoCircle size={20} className="text-purple-600" />
</div>
<div>
<p className="text-sm font-medium text-gray-600">نمایش در صفحه</p>
<p className="text-xl font-bold text-gray-900">
{filters.limit || 20}
</p>
</div>
</div>
</div>
</div>
{/* Filter Panel */}
<FilterPanel
filters={filters}
onFiltersChange={updateFilters}
onReset={resetFilters}
loading={loading}
/>
{/* Loading State */}
{loading && <PageLoading />}
{/* Table */}
{!loading && (
<>
<AccessLogsTable
logs={logs}
loading={loading}
onViewDetails={handleViewDetails}
onSort={handleSort}
sortBy={filters.sortBy}
sortOrder={filters.sortOrder}
/>
{/* Pagination */}
{total > (filters.limit || 20) && (
<div className="flex justify-center">
<Pagination
currentPage={filters.page || 1}
totalPages={Math.ceil(total / (filters.limit || 20))}
onPageChange={handlePageChange}
/>
</div>
)}
</>
)}
{/* Details Modal */}
<LogDetailsModal
log={selectedLog}
isOpen={isDetailsModalOpen}
onClose={handleCloseDetailsModal}
/>
{/* Empty State Info */}
{!loading && (!logs || logs.length === 0) && !error && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex items-center gap-3">
<InfoCircle size={20} className="text-blue-600" />
<div>
<h3 className="text-sm font-medium text-blue-900">راهنما</h3>
<p className="text-sm text-blue-700 mt-1">
از فیلترهای بالا برای جستجوی دقیقتر استفاده کنید. میتوانید بر اساس نوع عملیات، کاربر، تاریخ و سایر معیارها جستجو کنید.
</p>
</div>
</div>
</div>
)}
</div>
);
};
export default AccessLogsList;