Caegory seçimi
This commit is contained in:
parent
8d389fc955
commit
9ca2b81a50
6 changed files with 253 additions and 130 deletions
|
|
@ -9,6 +9,7 @@ using Volo.Abp.Application.Dtos;
|
|||
using Volo.Abp.Domain.Repositories;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
|
||||
namespace Kurs.Platform.Reports;
|
||||
|
||||
|
|
@ -18,15 +19,26 @@ public class ReportAppService : PlatformAppService, IReportAppService
|
|||
private readonly IRepository<ReportTemplate, Guid> _reportTemplateRepository;
|
||||
private readonly IRepository<ReportGenerated, Guid> _generatedReportRepository;
|
||||
private readonly IRepository<ReportParameter, Guid> _reportParameterRepository;
|
||||
private readonly IRepository<ReportCategory, Guid> _reportCategotyRepository;
|
||||
|
||||
public ReportAppService(
|
||||
IRepository<ReportTemplate, Guid> reportTemplateRepository,
|
||||
IRepository<ReportGenerated, Guid> generatedReportRepository,
|
||||
IRepository<ReportParameter, Guid> reportParameterRepository)
|
||||
IRepository<ReportParameter, Guid> reportParameterRepository,
|
||||
IRepository<ReportCategory, Guid> reportCategotyRepository
|
||||
)
|
||||
{
|
||||
_reportTemplateRepository = reportTemplateRepository;
|
||||
_generatedReportRepository = generatedReportRepository;
|
||||
_reportParameterRepository = reportParameterRepository;
|
||||
_reportCategotyRepository = reportCategotyRepository;
|
||||
}
|
||||
|
||||
public async Task<List<ReportCategoryDto>> GetCategoriesAsync()
|
||||
{
|
||||
var entity = await _reportCategotyRepository.GetListAsync();
|
||||
|
||||
return ObjectMapper.Map<List<ReportCategory>, List<ReportCategoryDto>>(entity);
|
||||
}
|
||||
|
||||
public async Task<PagedResultDto<ReportTemplateDto>> GetTemplatesAsync(GetReportTemplatesInput input)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
|||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "index.html",
|
||||
"revision": "0.e9emm6ltkho"
|
||||
"revision": "0.71ce98091og"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
|
|
|
|||
|
|
@ -1,28 +1,68 @@
|
|||
import React, { useState, useMemo } from 'react'
|
||||
import React, { useState, useMemo, useEffect } from 'react'
|
||||
import { TemplateEditor } from '../reports/TemplateEditor'
|
||||
import { ReportGenerator } from '../reports/ReportGenerator'
|
||||
import { TemplateCard } from './TemplateCard'
|
||||
import { Button } from '../ui/Button'
|
||||
import { Input } from '../ui/Input'
|
||||
import { Plus, Search, Filter, FileText, BarChart3 } from 'lucide-react'
|
||||
import { ReportTemplateDto } from '@/proxy/reports/models'
|
||||
import { ReportCategoryDto, ReportTemplateDto } from '@/proxy/reports/models'
|
||||
import { useReports } from '@/utils/hooks/useReports'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
export const Dashboard: React.FC = () => {
|
||||
const { templates, isLoading, createTemplate, updateTemplate, deleteTemplate, generateReport } =
|
||||
useReports()
|
||||
const {
|
||||
templates,
|
||||
categories,
|
||||
isLoading,
|
||||
createTemplate,
|
||||
updateTemplate,
|
||||
deleteTemplate,
|
||||
generateReport,
|
||||
loadTemplatesByCategory,
|
||||
} = useReports()
|
||||
|
||||
const tumuCategory = useMemo(
|
||||
() => ({
|
||||
id: 'tumu-category',
|
||||
name: 'Tümü',
|
||||
description: '',
|
||||
icon: '📋',
|
||||
}),
|
||||
[],
|
||||
)
|
||||
|
||||
const [showEditor, setShowEditor] = useState(false)
|
||||
const [showGenerator, setShowGenerator] = useState(false)
|
||||
const [editingTemplate, setEditingTemplate] = useState<ReportTemplateDto | null>(null)
|
||||
const [generatingTemplate, setGeneratingTemplate] = useState<ReportTemplateDto | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [selectedCategory, setSelectedCategory] = useState('Tümü')
|
||||
const [selectedCategory, setSelectedCategory] = useState<ReportCategoryDto | null>(null)
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const cats = ['Tümü', ...new Set(templates.map((t) => t.categoryName))]
|
||||
return cats
|
||||
}, [templates])
|
||||
// Create category options with "Tümü" at the top
|
||||
const categoryOptions = useMemo(() => {
|
||||
const categoryNames = [tumuCategory, ...categories.map((cat) => cat)]
|
||||
return categoryNames
|
||||
}, [categories, tumuCategory])
|
||||
|
||||
// Set default category when component mounts
|
||||
useEffect(() => {
|
||||
if (!selectedCategory) {
|
||||
setSelectedCategory(tumuCategory)
|
||||
}
|
||||
}, [tumuCategory, selectedCategory])
|
||||
|
||||
// Handle category change
|
||||
useEffect(() => {
|
||||
if (selectedCategory) {
|
||||
loadTemplatesByCategory(selectedCategory.name)
|
||||
}
|
||||
}, [selectedCategory, loadTemplatesByCategory])
|
||||
|
||||
// Load templates for default category on mount
|
||||
useEffect(() => {
|
||||
loadTemplatesByCategory('Tümü')
|
||||
}, [])
|
||||
|
||||
const filteredTemplates = useMemo(() => {
|
||||
return templates.filter((template) => {
|
||||
|
|
@ -31,12 +71,9 @@ export const Dashboard: React.FC = () => {
|
|||
template.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
template.tags.some((tag: any) => tag.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
|
||||
const matchesCategory =
|
||||
selectedCategory === 'Tümü' || template.categoryName === selectedCategory
|
||||
|
||||
return matchesSearch && matchesCategory
|
||||
return matchesSearch
|
||||
})
|
||||
}, [templates, searchQuery, selectedCategory])
|
||||
}, [templates, searchQuery])
|
||||
|
||||
const handleCreateTemplate = () => {
|
||||
setEditingTemplate(null)
|
||||
|
|
@ -93,7 +130,7 @@ export const Dashboard: React.FC = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<div className="mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center min-h-96">
|
||||
<div className="text-center">
|
||||
|
|
@ -102,7 +139,44 @@ export const Dashboard: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="flex flex-col lg:flex-row gap-8">
|
||||
{/* Left Sidebar - Categories */}
|
||||
<div className="lg:w-64 flex-shrink-0 p-4 bg-gray-50">
|
||||
<nav className="space-y-2">
|
||||
{categoryOptions.map((category) => {
|
||||
return (
|
||||
<button
|
||||
key={category.id}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
className={`w-full flex items-center space-x-3 px-4 py-3 rounded-lg text-left transition-colors ${
|
||||
selectedCategory?.id === category.id
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
{category.icon && <span className="text-lg">{category.icon}</span>}
|
||||
<span className="font-medium">{category.name}</span>
|
||||
</div>
|
||||
{category.name !== 'Tümü' && (
|
||||
<span className="text-sm text-gray-500 ml-2">
|
||||
({templates.filter((t) => t.categoryName === category.name).length})
|
||||
</span>
|
||||
)}
|
||||
{category.name === 'Tümü' && (
|
||||
<span className="text-sm text-gray-500 ml-2">({templates.length})</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
{translate('::App.Forum.Dashboard.Statistics')}
|
||||
</h2>
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-200">
|
||||
|
|
@ -121,7 +195,7 @@ export const Dashboard: React.FC = () => {
|
|||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600">Aktif Kategoriler</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{categories.length - 1}</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{categories.length}</p>
|
||||
</div>
|
||||
<div className="bg-emerald-100 p-3 rounded-full">
|
||||
<Filter className="h-6 w-6 text-emerald-600" />
|
||||
|
|
@ -143,7 +217,6 @@ export const Dashboard: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="bg-white rounded-xl shadow-md p-6 mb-8 border border-gray-200">
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
|
|
@ -158,23 +231,12 @@ export const Dashboard: React.FC = () => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:w-48">
|
||||
<select
|
||||
value={selectedCategory}
|
||||
onChange={(e) => setSelectedCategory(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
{categories.map((category) => (
|
||||
<option key={category} value={category}>
|
||||
{category}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Button onClick={handleCreateTemplate}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Yeni Şablon
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Templates Grid */}
|
||||
{filteredTemplates.length === 0 ? (
|
||||
<div className="bg-white rounded-xl shadow-md p-12 border border-gray-200">
|
||||
|
|
@ -207,7 +269,8 @@ export const Dashboard: React.FC = () => {
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modals */}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
export type ReportParameterType = 'text' | 'number' | 'date' | 'select' | 'checkbox'
|
||||
|
||||
export interface ReportCategoryDto {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
export interface ReportParameterDto {
|
||||
id: string
|
||||
reportTemplateId: string
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import {
|
|||
ReportGeneratedDto,
|
||||
CreateReportTemplateDto,
|
||||
UpdateReportTemplateDto,
|
||||
ReportGenerateDto, // backend'deki GenerateReportDto (templateId + parameters)
|
||||
ReportGenerateDto,
|
||||
ReportCategoryDto,
|
||||
GetReportTemplatesInput, // backend'deki GenerateReportDto (templateId + parameters)
|
||||
} from '@/proxy/reports/models'
|
||||
import apiService from './api.service'
|
||||
import { PagedAndSortedResultRequestDto, PagedResultDto } from '@/proxy'
|
||||
|
|
@ -16,8 +18,17 @@ export interface ReportsData {
|
|||
export class ReportsService {
|
||||
apiName = 'Default'
|
||||
|
||||
getCategories = () =>
|
||||
apiService.fetchData<ReportCategoryDto[]>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/report/categories',
|
||||
},
|
||||
{ apiName: this.apiName },
|
||||
)
|
||||
|
||||
// TEMPLATES
|
||||
getTemplates = (input: PagedAndSortedResultRequestDto) =>
|
||||
getTemplates = (input: GetReportTemplatesInput) =>
|
||||
apiService.fetchData<PagedResultDto<ReportTemplateDto>, PagedAndSortedResultRequestDto>(
|
||||
{
|
||||
method: 'GET',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ReportGeneratedDto, ReportTemplateDto } from '@/proxy/reports/models'
|
||||
import { ReportGeneratedDto, ReportTemplateDto, ReportCategoryDto } from '@/proxy/reports/models'
|
||||
import ReportsService from '@/services/reports.service'
|
||||
import { useState, useCallback, useEffect } from 'react'
|
||||
|
||||
|
|
@ -7,12 +7,14 @@ const reportsService = new ReportsService()
|
|||
interface ReportData {
|
||||
templates: ReportTemplateDto[]
|
||||
generatedReports: ReportGeneratedDto[]
|
||||
categories: ReportCategoryDto[]
|
||||
}
|
||||
|
||||
export const useReports = () => {
|
||||
const [data, setData] = useState<ReportData>({
|
||||
templates: [],
|
||||
generatedReports: [],
|
||||
categories: [],
|
||||
})
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
|
|
@ -22,7 +24,8 @@ export const useReports = () => {
|
|||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
|
||||
const [templatesResponse, generatedReportsResponse] = await Promise.all([
|
||||
const [templatesResponse, generatedReportsResponse, categoriesResponse] = await Promise.all(
|
||||
[
|
||||
reportsService.getTemplates({
|
||||
sorting: '',
|
||||
skipCount: 0,
|
||||
|
|
@ -33,11 +36,14 @@ export const useReports = () => {
|
|||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
}),
|
||||
])
|
||||
reportsService.getCategories(),
|
||||
],
|
||||
)
|
||||
|
||||
setData({
|
||||
templates: templatesResponse.data.items || [],
|
||||
generatedReports: generatedReportsResponse.data.items || [],
|
||||
categories: categoriesResponse.data || [],
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error)
|
||||
|
|
@ -45,6 +51,7 @@ export const useReports = () => {
|
|||
setData({
|
||||
templates: [],
|
||||
generatedReports: [],
|
||||
categories: [],
|
||||
})
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
|
|
@ -93,7 +100,7 @@ export const useReports = () => {
|
|||
...prevData,
|
||||
templates: prevData.templates.map((template) =>
|
||||
template.id === id
|
||||
? { ...template, ...updates, lastModificationTime: new Date() }
|
||||
? { ...template, ...updates, lastModificationTime: new Date().toISOString() }
|
||||
: template,
|
||||
),
|
||||
}))
|
||||
|
|
@ -127,10 +134,10 @@ export const useReports = () => {
|
|||
async (templateId: string, parameterValues: Record<string, string>) => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const reportData: ReportGeneratedDto = {
|
||||
const reportData = {
|
||||
templateId,
|
||||
parameters: parameterValues,
|
||||
} as ReportGeneratedDto
|
||||
}
|
||||
|
||||
const response = await reportsService.generateReport(reportData)
|
||||
const report = response.data as ReportGeneratedDto
|
||||
|
|
@ -182,9 +189,31 @@ export const useReports = () => {
|
|||
[data.templates],
|
||||
)
|
||||
|
||||
const loadTemplatesByCategory = useCallback(async (categoryName?: string) => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const templatesResponse = await reportsService.getTemplates({
|
||||
sorting: '',
|
||||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
category: categoryName && categoryName !== 'Tümü' ? categoryName : undefined,
|
||||
})
|
||||
|
||||
setData((prevData) => ({
|
||||
...prevData,
|
||||
templates: templatesResponse.data.items || [],
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('Error loading templates by category:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
templates: data.templates,
|
||||
generatedReports: data.generatedReports,
|
||||
categories: data.categories,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
createTemplate,
|
||||
|
|
@ -193,5 +222,6 @@ export const useReports = () => {
|
|||
generateReport,
|
||||
getReportById,
|
||||
getTemplateById,
|
||||
loadTemplatesByCategory,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue