Html Report Definition

This commit is contained in:
Sedat ÖZTÜRK 2026-01-07 14:57:23 +03:00
parent 7d9705951d
commit 7738a34a55
38 changed files with 418 additions and 3452 deletions

View file

@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Erp.Platform.Reports
{
public class CreateReportTemplateDto
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Required]
public string HtmlContent { get; set; }
public Guid CategoryId { get; set; }
public List<string> Tags { get; set; }
public List<CreateReportParameterDto> Parameters { get; set; }
public CreateReportTemplateDto()
{
Tags = [];
Parameters = [];
}
}
public class CreateReportParameterDto
{
[Required]
public string Name { get; set; }
public string Placeholder { get; set; }
public string Type { get; set; }
public string DefaultValue { get; set; }
public bool Required { get; set; }
public string Description { get; set; }
}
}

View file

@ -1,18 +0,0 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Erp.Platform.Reports
{
public class GetReportTemplatesInput : PagedAndSortedResultRequestDto
{
public string Filter { get; set; }
public Guid CategoryId { get; set; }
}
public class GetGeneratedReportsInput : PagedAndSortedResultRequestDto
{
public string Filter { get; set; }
public Guid? TemplateId { get; set; }
}
}

View file

@ -1,33 +0,0 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Erp.Platform.Reports
{
public interface IReportAppService : IApplicationService
{
// Template operations
Task<PagedResultDto<ReportTemplateDto>> GetTemplatesAsync(GetReportTemplatesInput input);
Task<ReportTemplateDto> GetTemplateAsync(Guid id);
Task<ReportTemplateDto> CreateTemplateAsync(CreateReportTemplateDto input);
Task<ReportTemplateDto> UpdateTemplateAsync(Guid id, UpdateReportTemplateDto input);
Task DeleteTemplateAsync(Guid id);
// Generated Report operations
Task<PagedResultDto<ReportGeneratedDto>> GetGeneratedReportsAsync(GetGeneratedReportsInput input);
Task<ReportGeneratedDto> GetGeneratedReportAsync(Guid id);
Task<ReportGeneratedDto> GenerateReportAsync(ReportGenerateDto input);
Task DeleteGeneratedReportAsync(Guid id);
// Bulk operations
Task<ReportsDataDto> GetAllDataAsync();
}
public class ReportsDataDto
{
public PagedResultDto<ReportTemplateDto> Templates { get; set; }
public PagedResultDto<ReportGeneratedDto> GeneratedReports { get; set; }
}
}

View file

@ -1,15 +0,0 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Erp.Platform.Reports
{
public class ReportCategoryDto : FullAuditedEntityDto<Guid>
{
public string Name { get; set; }
public string Description { get; set; }
public string Icon { get; set; }
public int TemplateCount { get; init; }
}
}

View file

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Erp.Platform.Reports
{
public class ReportGenerateDto
{
[Required]
public Guid TemplateId { get; set; }
public Dictionary<string, string> Parameters { get; set; }
public ReportGenerateDto()
{
Parameters = [];
}
}
}

View file

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;
namespace Erp.Platform.Reports
{
public class ReportGeneratedDto : FullAuditedEntityDto<Guid>
{
public Guid? TemplateId { get; set; }
[Required]
public string TemplateName { get; set; }
[Required]
public string GeneratedContent { get; set; }
public Dictionary<string, string> Parameters { get; set; }
public ReportTemplateDto ReportTemplate { get; set; }
public ReportGeneratedDto()
{
Parameters = [];
}
}
}

View file

@ -1,19 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Erp.Platform.Reports
{
public class ReportParameterDto
{
public Guid Id { get; set; }
public Guid? TemplateId { get; set; }
[Required]
public string Name { get; set; }
public string Placeholder { get; set; }
public string Type { get; set; }
public string DefaultValue { get; set; }
public bool Required { get; set; }
public string Description { get; set; }
}
}

View file

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;
namespace Erp.Platform.Reports
{
public class ReportTemplateDto : FullAuditedEntityDto<Guid>
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Required]
public string HtmlContent { get; set; }
public Guid CategoryId { get; set; }
public List<string> Tags { get; set; }
public List<ReportParameterDto> Parameters { get; set; }
public ReportTemplateDto()
{
Tags = [];
Parameters = [];
}
}
}

View file

@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Erp.Platform.Reports;
public class UpdateReportTemplateDto
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
[Required]
public string HtmlContent { get; set; }
public Guid CategoryId { get; set; }
public List<string> Tags { get; set; }
public List<UpdateReportParameterDto> Parameters { get; set; }
public UpdateReportTemplateDto()
{
Tags = [];
Parameters = [];
}
}
public class UpdateReportParameterDto
{
public Guid? Id { get; set; }
[Required]
public string Name { get; set; }
public string Placeholder { get; set; }
public string Type { get; set; }
public string DefaultValue { get; set; }
public bool Required { get; set; }
public string Description { get; set; }
}

View file

@ -1,408 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Erp.Platform.Entities;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
using System.Linq.Dynamic.Core;
using Microsoft.EntityFrameworkCore;
namespace Erp.Platform.Reports;
[Authorize()]
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<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)
{
// IQueryable başlat - Parameters'ı include et
var query = await _reportTemplateRepository.GetQueryableAsync();
query = query.Include(x => x.Parameters);
// Filtreleme
if (!string.IsNullOrWhiteSpace(input.Filter))
{
query = query.Where(x =>
x.Name.Contains(input.Filter) ||
x.Description.Contains(input.Filter)
);
}
if (input.CategoryId != Guid.Empty)
{
query = query.Where(x => x.CategoryId == input.CategoryId);
}
// Toplam kayıt sayısı
var totalCount = await AsyncExecuter.CountAsync(query);
// Sıralama (ABP default olarak sorting null ise Id'ye göre sıralar)
query = query.OrderBy(input.Sorting ?? nameof(ReportTemplate.Name));
// Sayfalama
var templates = await AsyncExecuter.ToListAsync(
query
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
);
// DTO dönüşümü
var templateDtos = templates.Select(MapToReportTemplateDto).ToList();
return new PagedResultDto<ReportTemplateDto>(
totalCount,
templateDtos
);
}
public async Task<ReportTemplateDto> GetTemplateAsync(Guid id)
{
var query = await _reportTemplateRepository.GetQueryableAsync();
var template = await query
.Include(x => x.Parameters)
.FirstOrDefaultAsync(x => x.Id == id);
if (template == null)
{
throw new ArgumentException($"Template with id {id} not found");
}
return MapToReportTemplateDto(template);
}
public async Task<ReportTemplateDto> CreateTemplateAsync(CreateReportTemplateDto input)
{
var template = new ReportTemplate(
GuidGenerator.Create(),
input.Name,
input.Description,
input.HtmlContent,
input.CategoryId)
{
Tags = JsonSerializer.Serialize(input.Tags)
};
template = await _reportTemplateRepository.InsertAsync(template, true);
// Parameters ekle
foreach (var paramDto in input.Parameters)
{
var parameter = new ReportParameter(
GuidGenerator.Create(),
template.Id,
paramDto.Name,
paramDto.Placeholder,
paramDto.Type,
paramDto.Required)
{
DefaultValue = paramDto.DefaultValue,
Description = paramDto.Description
};
await _reportParameterRepository.InsertAsync(parameter);
}
return await GetTemplateAsync(template.Id);
}
public async Task<ReportTemplateDto> UpdateTemplateAsync(Guid id, UpdateReportTemplateDto input)
{
// 1) Şablonu getir ve alanlarını güncelle
var template = await _reportTemplateRepository.GetAsync(id);
template.Name = input.Name;
template.Description = input.Description;
template.HtmlContent = input.HtmlContent;
template.CategoryId = input.CategoryId;
template.Tags = JsonSerializer.Serialize(input.Tags ?? []);
// Şablonu hemen persist et (audit alanları için de iyi olur)
await _reportTemplateRepository.UpdateAsync(template, autoSave: true);
// 2) Parametrelerde upsert + artıklarını sil
var existingParams = await _reportParameterRepository.GetListAsync(p => p.TemplateId == id);
var existingById = existingParams.ToDictionary(p => p.Id, p => p);
var inputParams = input.Parameters ?? new List<UpdateReportParameterDto>();
// Id'si olan/olmayan diye ayır
var withId = inputParams.Where(x => x.Id.HasValue).ToList();
var withoutId = inputParams.Where(x => !x.Id.HasValue).ToList();
// 2.a) Id'si olanları güncelle (varsa) ya da ekle (yoksa)
foreach (var dto in withId)
{
var pid = dto.Id!.Value;
if (existingById.TryGetValue(pid, out var entity))
{
// Güncelle
entity.Name = dto.Name;
entity.Placeholder = dto.Placeholder;
entity.Type = dto.Type;
entity.Required = dto.Required;
entity.DefaultValue = dto.DefaultValue;
entity.Description = dto.Description;
await _reportParameterRepository.UpdateAsync(entity);
existingById.Remove(pid); // kalanlar silinecek listesinde kalmasın
}
else
{
// DB'de yoksa yeni ekle (istemci Id göndermiş olabilir)
var newParam = new ReportParameter(
pid,
template.Id,
dto.Name,
dto.Placeholder,
dto.Type,
dto.Required)
{
DefaultValue = dto.DefaultValue,
Description = dto.Description
};
await _reportParameterRepository.InsertAsync(newParam);
}
}
// 2.b) Id'siz gelenleri yeni olarak ekle
foreach (var dto in withoutId)
{
var newParam = new ReportParameter(
GuidGenerator.Create(),
template.Id,
dto.Name,
dto.Placeholder,
dto.Type,
dto.Required)
{
DefaultValue = dto.DefaultValue,
Description = dto.Description
};
await _reportParameterRepository.InsertAsync(newParam);
}
// 2.c) Input'ta olmayan eski parametreleri sil
foreach (var leftover in existingById.Values)
{
await _reportParameterRepository.DeleteAsync(leftover);
}
// 3) Değişiklikleri tek seferde kaydet
await CurrentUnitOfWork.SaveChangesAsync();
// 4) Güncel DTO'yu dön
return await GetTemplateAsync(template.Id);
}
public async Task DeleteTemplateAsync(Guid id)
{
await _reportTemplateRepository.DeleteAsync(id);
}
public async Task<PagedResultDto<ReportGeneratedDto>> GetGeneratedReportsAsync(GetGeneratedReportsInput input)
{
var query = await _generatedReportRepository.GetQueryableAsync();
// Okuma senaryosu: tracking gerekmiyor + Template'ı eager load edelim
query = query.AsNoTracking()
.Include(x => x.ReportTemplate);
// Filtre
if (!string.IsNullOrWhiteSpace(input.Filter))
{
query = query.Where(x =>
x.TemplateName.Contains(input.Filter) ||
x.GeneratedContent.Contains(input.Filter)
);
}
if (input.TemplateId.HasValue)
{
query = query.Where(x => x.TemplateId == input.TemplateId.Value);
}
// Toplam kayıt
var totalCount = await AsyncExecuter.CountAsync(query);
// Sıralama
if (!string.IsNullOrWhiteSpace(input.Sorting))
query = query.OrderBy(input.Sorting); // ör. "generatedAt DESC" veya "templateName"
else
query = query.OrderByDescending(x => x.CreationTime);
// Sayfalama
var reports = await AsyncExecuter.ToListAsync(
query.Skip(input.SkipCount).Take(input.MaxResultCount)
);
// DTO map
var reportDtos = reports.Select(MapToGeneratedReportDto).ToList();
return new PagedResultDto<ReportGeneratedDto>(totalCount, reportDtos);
}
public async Task<ReportGeneratedDto> GetGeneratedReportAsync(Guid id)
{
var report = await _generatedReportRepository.GetAsync(id);
return MapToGeneratedReportDto(report);
}
public async Task<ReportGeneratedDto> GenerateReportAsync(ReportGenerateDto input)
{
var template = await _reportTemplateRepository.GetAsync(input.TemplateId);
if (template == null)
{
throw new ArgumentException("Template not found");
}
// HTML içeriğindeki parametreleri değiştir
var generatedContent = template.HtmlContent;
foreach (var param in input.Parameters)
{
var pattern = $"@@{param.Key}";
generatedContent = generatedContent.Replace(pattern, param.Value ?? "");
}
var generatedReport = new ReportGenerated(
GuidGenerator.Create(),
template.Id,
template.Name,
generatedContent,
JsonSerializer.Serialize(input.Parameters));
generatedReport = await _generatedReportRepository.InsertAsync(generatedReport, true);
return await GetGeneratedReportAsync(generatedReport.Id);
}
public async Task DeleteGeneratedReportAsync(Guid id)
{
await _generatedReportRepository.DeleteAsync(id);
}
public async Task<ReportsDataDto> GetAllDataAsync()
{
var templatesInput = new GetReportTemplatesInput { MaxResultCount = 1000 };
var reportsInput = new GetGeneratedReportsInput { MaxResultCount = 1000 };
var templates = await GetTemplatesAsync(templatesInput);
var reports = await GetGeneratedReportsAsync(reportsInput);
return new ReportsDataDto
{
Templates = templates,
GeneratedReports = reports
};
}
private ReportTemplateDto MapToReportTemplateDto(ReportTemplate template)
{
var dto = new ReportTemplateDto
{
Id = template.Id,
Name = template.Name,
Description = template.Description,
HtmlContent = template.HtmlContent,
CategoryId = template.CategoryId,
CreationTime = template.CreationTime,
LastModificationTime = template.LastModificationTime,
CreatorId = template.CreatorId,
LastModifierId = template.LastModifierId
};
// Tags deserialize
try
{
dto.Tags = string.IsNullOrEmpty(template.Tags)
? []
: JsonSerializer.Deserialize<List<string>>(template.Tags);
}
catch
{
dto.Tags = new List<string>();
}
// Parameters map
dto.Parameters = template.Parameters?.Select(p => new ReportParameterDto
{
Id = p.Id,
TemplateId = p.TemplateId,
Name = p.Name,
Placeholder = p.Placeholder,
Type = p.Type,
DefaultValue = p.DefaultValue,
Required = p.Required,
Description = p.Description
}).ToList() ?? [];
return dto;
}
private ReportGeneratedDto MapToGeneratedReportDto(ReportGenerated report)
{
var dto = new ReportGeneratedDto
{
Id = report.Id,
TemplateId = report.TemplateId,
TemplateName = report.TemplateName,
GeneratedContent = report.GeneratedContent,
CreationTime = report.CreationTime,
LastModificationTime = report.LastModificationTime,
CreatorId = report.CreatorId,
LastModifierId = report.LastModifierId
};
// Parameters deserialize
try
{
dto.Parameters = string.IsNullOrEmpty(report.Parameters)
? new Dictionary<string, string>()
: JsonSerializer.Deserialize<Dictionary<string, string>>(report.Parameters);
}
catch
{
dto.Parameters = [];
}
// Template mapping
if (report.ReportTemplate != null)
{
dto.ReportTemplate = MapToReportTemplateDto(report.ReportTemplate);
}
return dto;
}
}

View file

@ -1,91 +0,0 @@
using AutoMapper;
using Erp.Platform.Entities;
using System.Text.Json;
using System.Collections.Generic;
namespace Erp.Platform.Reports
{
public class ReportAutoMapperProfile : Profile
{
public ReportAutoMapperProfile()
{
CreateMap<ReportTemplate, ReportTemplateDto>()
.ForMember(dest => dest.Tags, opt => opt.MapFrom(src =>
ConvertTagsFromJson(src.Tags)))
.ForMember(dest => dest.Parameters, opt => opt.MapFrom(src => src.Parameters));
CreateMap<CreateReportTemplateDto, ReportTemplate>()
.ForMember(dest => dest.Tags, opt => opt.MapFrom(src => ConvertTagsToJson(src.Tags)))
.ForMember(dest => dest.Parameters, opt => opt.Ignore());
CreateMap<UpdateReportTemplateDto, ReportTemplate>()
.ForMember(dest => dest.Tags, opt => opt.MapFrom(src => ConvertTagsToJson(src.Tags)))
.ForMember(dest => dest.Parameters, opt => opt.Ignore());
CreateMap<ReportParameter, ReportParameterDto>();
CreateMap<CreateReportParameterDto, ReportParameter>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.TemplateId, opt => opt.Ignore());
CreateMap<UpdateReportParameterDto, ReportParameter>()
.ForMember(dest => dest.TemplateId, opt => opt.Ignore());
CreateMap<ReportGenerated, ReportGeneratedDto>()
.ForMember(dest => dest.Parameters, opt => opt.MapFrom(src =>
ConvertParametersFromJson(src.Parameters)))
.ForMember(dest => dest.ReportTemplate, opt => opt.MapFrom(src => src.ReportTemplate));
CreateMap<ReportGenerateDto, ReportGenerated>()
.ForMember(dest => dest.Parameters, opt => opt.MapFrom(src => ConvertParametersToJson(src.Parameters)))
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.TemplateId, opt => opt.MapFrom(src => src.TemplateId))
.ForMember(dest => dest.TemplateName, opt => opt.Ignore())
.ForMember(dest => dest.GeneratedContent, opt => opt.Ignore());
CreateMap<ReportCategory, ReportCategoryDto>();
}
private static List<string> ConvertTagsFromJson(string tags)
{
if (string.IsNullOrEmpty(tags))
return new List<string>();
try
{
return JsonSerializer.Deserialize<List<string>>(tags) ?? new List<string>();
}
catch
{
return new List<string>();
}
}
private static string ConvertTagsToJson(List<string> tags)
{
return JsonSerializer.Serialize(tags ?? new List<string>());
}
private static Dictionary<string, string> ConvertParametersFromJson(string parameters)
{
if (string.IsNullOrEmpty(parameters))
return new Dictionary<string, string>();
try
{
return JsonSerializer.Deserialize<Dictionary<string, string>>(parameters) ?? new Dictionary<string, string>();
}
catch
{
return new Dictionary<string, string>();
}
}
private static string ConvertParametersToJson(Dictionary<string, string> parameters)
{
return JsonSerializer.Serialize(parameters ?? new Dictionary<string, string>());
}
}
}

View file

@ -1335,315 +1335,9 @@
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.All",
"en": "All",
"tr": "Tümü"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.AllDescription",
"en": "Reports from all categories",
"tr": "Tüm kategorilere ait raporlar"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.SearchTemplate",
"en": "Search template...",
"tr": "Şablon ara..."
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.NewTemplate",
"en": "New Template",
"tr": "Yeni Şablon"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.TotalTemplates",
"en": "Total Templates",
"tr": "Toplam Şablon"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.CategoryTemplates",
"en": "Category Templates",
"tr": "Kategori Şablonları"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.ActiveCategories",
"en": "Active Categories",
"tr": "Aktif Kategoriler"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.TotalParameters",
"en": "Total Parameters",
"tr": "Toplam Parametre"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.CategoryParameters",
"en": "Category Parameters",
"tr": "Kategori Parametreleri"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.NoTemplates",
"en": "No templates created yet",
"tr": "Henüz şablon oluşturulmamış"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.TemplateNotFound",
"en": "No template found",
"tr": "Şablon bulunamadı"
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.CreateFirstTemplate",
"en": "Start by creating your first report template.",
"tr": "İlk rapor şablonunuzu oluşturarak başlayın."
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.NoSearchResults",
"en": "No templates match your search criteria.",
"tr": "Arama kriterlerinize uygun şablon bulunamadı."
},
{
"resourceName": "Platform",
"key": "App.Reports.Dashboard.DeleteTemplateConfirmation",
"en": "Are you sure you want to delete this template?",
"tr": "Bu şablonu silmek istediğinizden emin misiniz?"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportGenerator.ReportParameters",
"en": "Report Parameters",
"tr": "Rapor Parametreleri"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportGenerator.ParameterValues",
"en": "Parameter Values",
"tr": "Parametre Değerleri"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportGenerator.RequiredFieldsNote",
"en": "* Required fields",
"tr": "* Zorunlu alanlar"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportGenerator.NoParameters",
"en": "No parameters defined for this template.",
"tr": "Bu şablon için parametre tanımlanmamış."
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportGenerator.DirectGenerate",
"en": "You can directly generate the report.",
"tr": "Direkt rapor oluşturabilirsiniz."
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportGenerator.GenerateReport",
"en": "Generate Report",
"tr": "Rapor Oluştur"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportHtmlViewer.Placeholder",
"en": "Write your report template here...",
"tr": "Rapor şablonunuzu buraya yazın..."
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.LoadingTitle",
"en": "Loading report...",
"tr": "Rapor yükleniyor..."
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.LoadingSubtitle",
"en": "Please wait",
"tr": "Lütfen bekleyin"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.ErrorNotFound",
"en": "Report not found",
"tr": "Rapor bulunamadı"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.ErrorNotFoundDescription",
"en": "The report you are looking for may not exist or has been deleted.",
"tr": "Aradığınız rapor mevcut değil veya silinmiş olabilir."
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.DownloadPDF",
"en": "Download Pdf",
"tr": "Pdf İndir"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.Print",
"en": "Print",
"tr": "Yazdır"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.Page",
"en": "Page",
"tr": "Sayfa"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateCard.Show",
"en": "Show",
"tr": "Göster"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.TitleEdit",
"en": "Edit Template",
"tr": "Şablon Düzenle"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.TitleNew",
"en": "Create New Template",
"tr": "Yeni Şablon Oluştur"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Tab.Info",
"en": "Template Info",
"tr": "Şablon Bilgileri"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Tab.Parameters",
"en": "Parameters",
"tr": "Parametreler"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Tab.Content",
"en": "HTML Content",
"tr": "HTML İçerik"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Label.Name",
"en": "Template Name",
"tr": "Şablon Adı"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Placeholder.Name",
"en": "Report template name",
"tr": "Rapor şablonu adı"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Label.Category",
"en": "Category",
"tr": "Kategori"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Placeholder.SelectCategory",
"en": "Select Category",
"tr": "Kategori Seç"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Label.Tags",
"en": "Tags",
"tr": "Etiketler"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Placeholder.AddTag",
"en": "Add tag...",
"tr": "Etiket ekle..."
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Button.Add",
"en": "Add",
"tr": "Ekle"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Label.Description",
"en": "Template Description",
"tr": "Şablon Açıklaması"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Placeholder.Description",
"en": "Write detailed description about the template...",
"tr": "Şablon hakkında detaylııklama yazın..."
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.NoParameters",
"en": "No parameters detected yet",
"tr": "Henüz parametre algılanmadı"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.NoParametersDescription",
"en": "When you use parameters in the format @@PARAMETER in HTML content, they will appear here.",
"tr": "HTML içeriğinde @@PARAMETRE formatında parametreler kullandığınızda burada görünecek."
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Placeholder.ParameterDescription",
"en": "Parameter description",
"tr": "Parametre açıklaması"
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Button.Saving",
"en": "Saving...",
"tr": "Kaydediliyor..."
},
{
"resourceName": "Platform",
"key": "App.Reports.TemplateEditor.Button.Save",
"en": "Save",
"tr": "Kaydet"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.ReportIdMissing",
"en": "Report not found",
"tr": "Rapor bulunamadı"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.ReportLoadError",
"en": "An error occurred while loading the report",
"tr": "Rapor yüklenirken bir hata oluştu"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.ZoomIn",
"en": "Zoom In",
"tr": "Yakınlaştır"
},
{
"resourceName": "Platform",
"key": "App.Reports.ReportViewer.ZoomOut",
"en": "Zoom Out",
"tr": "Uzaklaştır"
"key": "App.Reports.ReportTemplates",
"en": "Report Templates",
"tr": "Rapor Şablonları"
},
{
"resourceName": "Platform",

View file

@ -5938,6 +5938,196 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
#endregion
}
#endregion
#region ReportTemplates
listFormName = AppCodes.Reports.ReportTemplates;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
new ListForm()
{
ListFormType = ListFormTypeEnum.List,
ExportJson = DefaultExportJson,
IsSubForm = false,
ShowNote = true,
LayoutJson = DefaultLayoutJson(),
CultureName = LanguageCodes.En,
ListFormCode = listFormName,
Name = listFormName,
Title = listFormName,
DataSourceCode = SeedConsts.DataSources.DefaultCode,
IsTenant = true,
IsBranch = false,
IsOrganizationUnit = false,
Description = listFormName,
SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportTemplate)),
KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson,
HeaderFilterJson = DefaultHeaderFilterJson,
SearchPanelJson = DefaultSearchPanelJson,
GroupPanelJson = DefaultGroupPanelJson,
SelectionJson = DefaultSelectionSingleJson,
ColumnOptionJson = DefaultColumnOptionJson,
PermissionJson = DefaultPermissionJson(listFormName),
DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.ReportTemplate)),
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
PagerOptionJson = DefaultPagerOptionJson,
EditingOptionJson = DefaultEditingOptionJson(listFormName, 900, 500, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>()
{
new() {
Order = 1, ColCount = 1, ColSpan = 1, ItemType = "group", Items =
[
new EditingFormItemDto { Order = 1, DataField = "Name", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 2, DataField = "Description", ColSpan = 1, EditorType2 = EditorTypes.dxTextArea },
new EditingFormItemDto { Order = 3, DataField = "CategoryId", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 4, DataField = "HtmlContent", ColSpan = 1, EditorType2 = EditorTypes.dxHtmlEditor, EditorOptions = EditorOptionValues.HtmlEditorOptions },
new EditingFormItemDto { Order = 5, DataField = "Status", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox },
]}
}),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
new() { FieldName = "Status", FieldDbType = DbType.String, Value = "Aktif", CustomValueType = FieldCustomValueTypeEnum.Value },
}),
CommandColumnJson = JsonSerializer.Serialize(new CommandColumnDto[] {
new() {
Hint = "View",
Text ="View",
UrlTarget="_blank",
AuthName = listFormName,
Url=$"/admin/reports/@Id/view",
IsVisible = true,
},
new() {
Hint = "Design",
Text ="Design",
UrlTarget="_blank",
AuthName = listFormName + ".Update",
Url=$"/admin/reports/@Id/design",
IsVisible = true,
},
}),
}
);
#region HtmlReports Fields
await _listFormFieldRepository.InsertManyAsync(
[
// Id
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "Id",
Width = 100,
ListOrderNo = 1,
Visible = false,
IsActive = true,
IsDeleted = false,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
// String alanlar
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Name",
Width = 250,
ListOrderNo = 2,
Visible = true,
IsActive = true,
IsDeleted = false,
SortIndex = 1,
SortDirection = GridColumnOptions.SortOrderAsc,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "CategoryId",
Width = 300,
ListOrderNo = 3,
Visible = true,
IsActive = true,
IsDeleted = false,
LookupJson = LookupQueryValues.DefaultLookupQueryJson(nameof(TableNameEnum.ReportCategory), "Id", "Name"),
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto{ C = AppCodes.Definitions.Lawyer + ".Create", R = AppCodes.Definitions.Lawyer, U = AppCodes.Definitions.Lawyer + ".Update", E = true, I = true, Deny = false }),
PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto{ IsPivot = true })
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Status",
Width = 100,
ListOrderNo = 4,
Visible = true,
IsActive = true,
IsDeleted = false,
LookupJson = JsonSerializer.Serialize(new LookupDto {
DataSourceType = UiLookupDataSourceTypeEnum.StaticData,
DisplayExpr = "name",
ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key="Aktif", Name="Aktif" },
new () { Key="Pasif", Name="Pasif" },
}),
}),
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto{ C = AppCodes.Definitions.Lawyer + ".Create", R = AppCodes.Definitions.Lawyer, U = AppCodes.Definitions.Lawyer + ".Update", E = true, I = true, Deny = false }),
PivotSettingsJson = JsonSerializer.Serialize(new ListFormFieldPivotSettingsDto{ IsPivot = true })
},
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Description",
Width = 250,
ListOrderNo = 5,
Visible = true,
IsActive = true,
IsDeleted = false,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "HtmlContent",
Width = 250,
ListOrderNo = 6,
Visible = true,
IsActive = true,
IsDeleted = false,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
]);
#endregion
}
#endregion
}
}

View file

@ -364,20 +364,6 @@
"routeType": "protected",
"authority": ["App.DeveloperKit.Components"]
},
{
"key": "admin.reportManagement",
"path": "/admin/reports/management",
"componentPath": "@/views/report/DashboardPage",
"routeType": "protected",
"authority": ["App.Reports.Management"]
},
{
"key": "admin.reports.view",
"path": "/admin/reports/:id",
"componentPath": "@/views/report/ReportViewerPage",
"routeType": "protected",
"authority": ["App.Reports.Categories"]
},
{
"key": "admin.fileManagement",
"path": "/admin/files",
@ -457,14 +443,14 @@
},
{
"key": "admin.devexpressReportView",
"path": "/admin/reports/reportviewer/:id",
"path": "/admin/reports/:id/view",
"componentPath": "@/views/report/DevexpressReportViewer",
"routeType": "protected",
"authority": []
},
{
"key": "admin.devexpressReportDesigner",
"path": "/admin/reports/reportdesigner/:id",
"path": "/admin/reports/:id/design",
"componentPath": "@/views/report/DevexpressReportDesigner",
"routeType": "protected",
"authority": []
@ -1024,151 +1010,21 @@
"RequiredPermissionName": "App.AuditLogs",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.Files",
"DisplayName": "App.Files",
"Order": 4,
"Url": "/admin/files",
"Icon": "FcFolder",
"RequiredPermissionName": "App.Files",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.Forum",
"DisplayName": "App.Forum",
"Order": 5,
"Order": 4,
"Url": "/admin/forum",
"Icon": "FcLink",
"RequiredPermissionName": "App.ForumManagement.Publish",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.Reports.Management",
"DisplayName": "App.Reports.Management",
"Order": 6,
"Url": null,
"Icon": "FcDocument",
"RequiredPermissionName": null,
"IsDisabled": false
},
{
"ParentCode": "App.Reports.Management",
"Code": "App.Reports.Categories",
"DisplayName": "App.Reports.Categories",
"Order": 1,
"Url": "/admin/list/App.Reports.Categories",
"Icon": "FcProcess",
"RequiredPermissionName": "App.Reports.Categories",
"IsDisabled": false
},
{
"ParentCode": "App.Reports.Management",
"Code": "App.Reports",
"DisplayName": "App.Reports",
"Order": 2,
"Url": "/admin/reports/management",
"Icon": "FcSimCardChip",
"RequiredPermissionName": "App.Reports.Management",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.DeveloperKit",
"DisplayName": "App.DeveloperKit",
"Order": 8,
"Url": null,
"Icon": "FcAndroidOs",
"RequiredPermissionName": null,
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Dashboard",
"DisplayName": "App.Coordinator.Classroom.Dashboard",
"Order": 1,
"Url": "/admin/developerkit",
"Icon": "FcBinoculars",
"RequiredPermissionName": "App.DeveloperKit",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Entity",
"DisplayName": "App.DeveloperKit.Entity",
"Order": 2,
"Url": "/admin/developerkit/entities",
"Icon": "FcAddRow",
"RequiredPermissionName": "App.DeveloperKit.Entity",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Migrations",
"DisplayName": "App.DeveloperKit.Migrations",
"Order": 3,
"Url": "/admin/developerkit/migrations",
"Icon": "FcAddDatabase",
"RequiredPermissionName": "App.DeveloperKit.Migrations",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.CrudEndpoints",
"DisplayName": "App.DeveloperKit.CrudEndpoints",
"Order": 4,
"Url": "/admin/developerkit/endpoints",
"Icon": "FcOrgUnit",
"RequiredPermissionName": "App.DeveloperKit.CrudEndpoints",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.CustomEndpoints",
"DisplayName": "App.DeveloperKit.CustomEndpoints",
"Order": 5,
"Url": "/admin/list/App.DeveloperKit.CustomEndpoints",
"Icon": "FcMindMap",
"RequiredPermissionName": "App.DeveloperKit.CustomEndpoints",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Components",
"DisplayName": "App.DeveloperKit.Components",
"Order": 6,
"Url": "/admin/developerkit/components",
"Icon": "FcBiohazard",
"RequiredPermissionName": "App.DeveloperKit.Components",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.DynamicServices",
"DisplayName": "App.DeveloperKit.DynamicServices",
"Order": 7,
"Url": "/admin/developerkit/dynamic-services",
"Icon": "FcCommandLine",
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.SqlQueryManager",
"DisplayName": "App.SqlQueryManager",
"Order": 9,
"Url": "/admin/sqlQueryManager",
"Icon": "FaDatabase",
"RequiredPermissionName": "App.SqlQueryManager",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.Intranet",
"DisplayName": "App.Intranet",
"Order": 7,
"Order": 5,
"Url": null,
"Icon": "FcDataConfiguration",
"RequiredPermissionName": null,
@ -1268,7 +1124,7 @@
"ParentCode": "App.Administration",
"Code": "App.Public",
"DisplayName": "App.Public",
"Order": 8,
"Order": 6,
"Url": null,
"Icon": "FcGenealogy",
"RequiredPermissionName": null,
@ -1374,6 +1230,136 @@
"RequiredPermissionName": "App.Contact",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.Files",
"DisplayName": "App.Files",
"Order": 7,
"Url": "/admin/files",
"Icon": "FcFolder",
"RequiredPermissionName": "App.Files",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.Reports.Management",
"DisplayName": "App.Reports.Management",
"Order": 8,
"Url": null,
"Icon": "FcDocument",
"RequiredPermissionName": null,
"IsDisabled": false
},
{
"ParentCode": "App.Reports.Management",
"Code": "App.Reports.Categories",
"DisplayName": "App.Reports.Categories",
"Order": 1,
"Url": "/admin/list/App.Reports.Categories",
"Icon": "FcProcess",
"RequiredPermissionName": "App.Reports.Categories",
"IsDisabled": false
},
{
"ParentCode": "App.Reports.Management",
"Code": "App.Reports.ReportTemplates",
"DisplayName": "App.Reports.ReportTemplates",
"Order": 2,
"Url": "/admin/list/App.Reports.ReportTemplates",
"Icon": "FcSimCardChip",
"RequiredPermissionName": "App.Reports.ReportTemplates",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.DeveloperKit",
"DisplayName": "App.DeveloperKit",
"Order": 9,
"Url": null,
"Icon": "FcAndroidOs",
"RequiredPermissionName": null,
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Dashboard",
"DisplayName": "App.Coordinator.Classroom.Dashboard",
"Order": 1,
"Url": "/admin/developerkit",
"Icon": "FcBinoculars",
"RequiredPermissionName": "App.DeveloperKit",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Entity",
"DisplayName": "App.DeveloperKit.Entity",
"Order": 2,
"Url": "/admin/developerkit/entities",
"Icon": "FcAddRow",
"RequiredPermissionName": "App.DeveloperKit.Entity",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Migrations",
"DisplayName": "App.DeveloperKit.Migrations",
"Order": 3,
"Url": "/admin/developerkit/migrations",
"Icon": "FcAddDatabase",
"RequiredPermissionName": "App.DeveloperKit.Migrations",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.CrudEndpoints",
"DisplayName": "App.DeveloperKit.CrudEndpoints",
"Order": 4,
"Url": "/admin/developerkit/endpoints",
"Icon": "FcOrgUnit",
"RequiredPermissionName": "App.DeveloperKit.CrudEndpoints",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.CustomEndpoints",
"DisplayName": "App.DeveloperKit.CustomEndpoints",
"Order": 5,
"Url": "/admin/list/App.DeveloperKit.CustomEndpoints",
"Icon": "FcMindMap",
"RequiredPermissionName": "App.DeveloperKit.CustomEndpoints",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Components",
"DisplayName": "App.DeveloperKit.Components",
"Order": 6,
"Url": "/admin/developerkit/components",
"Icon": "FcBiohazard",
"RequiredPermissionName": "App.DeveloperKit.Components",
"IsDisabled": false
},
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.DynamicServices",
"DisplayName": "App.DeveloperKit.DynamicServices",
"Order": 7,
"Url": "/admin/developerkit/dynamic-services",
"Icon": "FcCommandLine",
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
"IsDisabled": false
},
{
"ParentCode": "App.Administration",
"Code": "App.SqlQueryManager",
"DisplayName": "App.SqlQueryManager",
"Order": 10,
"Url": "/admin/sqlQueryManager",
"Icon": "FaDatabase",
"RequiredPermissionName": "App.SqlQueryManager",
"IsDisabled": false
},
{
"ParentCode": null,
"Code": "App.Participant",

View file

@ -2531,17 +2531,17 @@
},
{
"GroupName": "App.Administration",
"Name": "App.Reports.Management",
"Name": "App.Reports.ReportTemplates",
"ParentName": null,
"DisplayName": "App.Reports.Management",
"DisplayName": "App.Reports.ReportTemplates",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Administration",
"Name": "App.Reports.Management.Create",
"ParentName": "App.Reports.Management",
"Name": "App.Reports.ReportTemplates.Create",
"ParentName": "App.Reports.ReportTemplates",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 3,
@ -2549,8 +2549,8 @@
},
{
"GroupName": "App.Administration",
"Name": "App.Reports.Management.Delete",
"ParentName": "App.Reports.Management",
"Name": "App.Reports.ReportTemplates.Delete",
"ParentName": "App.Reports.ReportTemplates",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 3,
@ -2558,8 +2558,8 @@
},
{
"GroupName": "App.Administration",
"Name": "App.Reports.Management.Export",
"ParentName": "App.Reports.Management",
"Name": "App.Reports.ReportTemplates.Export",
"ParentName": "App.Reports.ReportTemplates",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 3,
@ -2567,8 +2567,8 @@
},
{
"GroupName": "App.Administration",
"Name": "App.Reports.Management.Import",
"ParentName": "App.Reports.Management",
"Name": "App.Reports.ReportTemplates.Import",
"ParentName": "App.Reports.ReportTemplates",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 3,
@ -2576,8 +2576,8 @@
},
{
"GroupName": "App.Administration",
"Name": "App.Reports.Management.Note",
"ParentName": "App.Reports.Management",
"Name": "App.Reports.ReportTemplates.Note",
"ParentName": "App.Reports.ReportTemplates",
"DisplayName": "Note",
"IsEnabled": true,
"MultiTenancySide": 3,
@ -2585,8 +2585,8 @@
},
{
"GroupName": "App.Administration",
"Name": "App.Reports.Management.Update",
"ParentName": "App.Reports.Management",
"Name": "App.Reports.ReportTemplates.Update",
"ParentName": "App.Reports.ReportTemplates",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 3,

View file

@ -37,8 +37,6 @@ public enum TableNameEnum
CustomComponent,
ReportCategory,
ReportTemplate,
ReportParameter,
ReportGenerated,
IpRestriction,
Sector,
ContactTag,

View file

@ -59,8 +59,6 @@ public static class TableNameResolver
{ nameof(TableNameEnum.DynamicService), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ReportCategory), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ReportTemplate), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ReportParameter), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ReportGenerated), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.IpRestriction), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.Sector), (TablePrefix.TenantByName, MenuPrefix.Saas) },

View file

@ -373,6 +373,7 @@ public static class SeedConsts
{
public const string Default = Prefix.App + ".Reports";
public const string Categories = Default + ".Categories";
public const string ReportTemplates = Default + ".ReportTemplates";
}
//Web Site
public const string About = Prefix.App + ".About";

View file

@ -14,7 +14,6 @@ namespace Erp.Platform.Entities
public string Icon { get; set; }
public ICollection<ReportTemplate> ReportTemplates { get; set; }
}
}

View file

@ -1,38 +0,0 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Erp.Platform.Entities
{
public class ReportGenerated : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid? TemplateId { get; set; }
public string TemplateName { get; set; }
public string GeneratedContent { get; set; }
public string Parameters { get; set; }
public virtual ReportTemplate ReportTemplate { get; set; }
public ReportGenerated()
{
}
public ReportGenerated(
Guid id,
Guid templateId,
string templateName,
string generatedContent,
string parameters
) : base(id)
{
TemplateId = templateId;
TemplateName = templateName;
GeneratedContent = generatedContent;
Parameters = parameters;
}
}
}

View file

@ -1,42 +0,0 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Erp.Platform.Entities
{
public class ReportParameter : FullAuditedEntity<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
public Guid? TemplateId { get; set; }
public string Name { get; set; }
public string Placeholder { get; set; }
public string Type { get; set; }
public string DefaultValue { get; set; }
public bool Required { get; set; }
public string Description { get; set; }
public virtual ReportTemplate ReportTemplate { get; set; }
public ReportParameter()
{
}
public ReportParameter(
Guid id,
Guid templateId,
string name,
string placeholder,
string type,
bool required = false
) : base(id)
{
TemplateId = templateId;
Name = name;
Placeholder = placeholder;
Type = type;
Required = required;
}
}
}

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
@ -13,33 +12,24 @@ namespace Erp.Platform.Entities
public string Description { get; set; }
public string HtmlContent { get; set; }
public Guid CategoryId { get; set; }
public string Tags { get; set; }
public string Status { get; set; }
public ReportCategory ReportCategory { get; set; }
public ICollection<ReportParameter> Parameters { get; set; }
public ICollection<ReportGenerated> Generated { get; set; }
public ReportTemplate()
{
Parameters = [];
Generated = [];
}
public ReportTemplate(
Guid id,
string name,
string description,
string htmlContent,
Guid categoryId
Guid categoryId,
string status
) : base(id)
{
Name = name;
Description = description;
HtmlContent = htmlContent;
CategoryId = categoryId;
Parameters = [];
Generated = [];
Status = status;
}
}
}

View file

@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Erp.Platform.Entities;
using Volo.Abp.Domain.Repositories;
namespace Erp.Platform.Repositories
{
public interface IGeneratedReportRepository : IRepository<ReportGenerated, Guid>
{
Task<List<ReportGenerated>> GetListAsync(
int skipCount = 0,
int maxResultCount = int.MaxValue,
string sorting = null,
string filter = null,
Guid? templateId = null
);
Task<long> GetCountAsync(string filter = null, Guid? templateId = null);
Task<List<ReportGenerated>> GetByTemplateIdAsync(Guid? templateId);
Task<ReportGenerated> GetByIdWithTemplateAsync(Guid id);
}
}

View file

@ -66,8 +66,6 @@ public class PlatformDbContext :
public DbSet<CustomComponent> CustomComponents { get; set; }
public DbSet<DynamicService> DynamicServices { get; set; }
public DbSet<ReportTemplate> ReportTemplates { get; set; }
public DbSet<ReportParameter> ReportParameters { get; set; }
public DbSet<ReportGenerated> ReportGenerated { get; set; }
public DbSet<ReportCategory> ReportCategories { get; set; }
public DbSet<Note> Activities { get; set; }
public DbSet<IpRestriction> IpRestrictions { get; set; }
@ -844,42 +842,7 @@ public class PlatformDbContext :
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
b.Property(x => x.Description).HasMaxLength(1024);
b.Property(x => x.HtmlContent).IsRequired();
b.Property(x => x.Tags).HasMaxLength(2048);
b.HasMany(t => t.Parameters)
.WithOne(p => p.ReportTemplate)
.HasForeignKey(p => p.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
b.HasMany(t => t.Generated)
.WithOne(p => p.ReportTemplate)
.HasForeignKey(p => p.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<ReportParameter>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportParameter)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.TemplateId).IsRequired();
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
b.Property(x => x.Placeholder).HasMaxLength(256);
b.Property(x => x.Type).IsRequired();
b.Property(x => x.DefaultValue).HasMaxLength(512);
b.Property(x => x.Required).IsRequired();
b.Property(x => x.Description).HasMaxLength(1024);
});
builder.Entity<ReportGenerated>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportGenerated)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.TemplateId).IsRequired(false);
b.Property(x => x.TemplateName).IsRequired().HasMaxLength(256);
b.Property(x => x.GeneratedContent).IsRequired();
b.Property(x => x.Parameters).HasMaxLength(4096); // JSON string
b.Property(x => x.Status).HasMaxLength(20);
});
//Administration

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Erp.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260107073550_Initial")]
[Migration("20260107104555_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -12877,141 +12877,6 @@ namespace Erp.Platform.Migrations
b.ToTable("Sas_T_ReportCategory", (string)null);
});
modelBuilder.Entity("Erp.Platform.Entities.ReportGenerated", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("GeneratedContent")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Parameters")
.HasMaxLength(4096)
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("TemplateId")
.HasColumnType("uniqueidentifier");
b.Property<string>("TemplateName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("TemplateId");
b.ToTable("Sas_T_ReportGenerated", (string)null);
});
modelBuilder.Entity("Erp.Platform.Entities.ReportParameter", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<string>("DefaultValue")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Placeholder")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("Required")
.HasColumnType("bit");
b.Property<Guid>("TemplateId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("TemplateId");
b.ToTable("Sas_T_ReportParameter", (string)null);
});
modelBuilder.Entity("Erp.Platform.Entities.ReportTemplate", b =>
{
b.Property<Guid>("Id")
@ -13063,9 +12928,9 @@ namespace Erp.Platform.Migrations
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("Tags")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<string>("Status")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
@ -21748,27 +21613,6 @@ namespace Erp.Platform.Migrations
b.Navigation("Branch");
});
modelBuilder.Entity("Erp.Platform.Entities.ReportGenerated", b =>
{
b.HasOne("Erp.Platform.Entities.ReportTemplate", "ReportTemplate")
.WithMany("Generated")
.HasForeignKey("TemplateId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("ReportTemplate");
});
modelBuilder.Entity("Erp.Platform.Entities.ReportParameter", b =>
{
b.HasOne("Erp.Platform.Entities.ReportTemplate", "ReportTemplate")
.WithMany("Parameters")
.HasForeignKey("TemplateId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ReportTemplate");
});
modelBuilder.Entity("Erp.Platform.Entities.ReportTemplate", b =>
{
b.HasOne("Erp.Platform.Entities.ReportCategory", "ReportCategory")
@ -22958,13 +22802,6 @@ namespace Erp.Platform.Migrations
b.Navigation("ReportTemplates");
});
modelBuilder.Entity("Erp.Platform.Entities.ReportTemplate", b =>
{
b.Navigation("Generated");
b.Navigation("Parameters");
});
modelBuilder.Entity("Erp.Platform.Entities.Request", b =>
{
b.Navigation("Items");

View file

@ -4479,7 +4479,7 @@ namespace Erp.Platform.Migrations
Description = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
HtmlContent = table.Column<string>(type: "nvarchar(max)", nullable: false),
CategoryId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Tags = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
Status = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -4873,67 +4873,6 @@ namespace Erp.Platform.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Sas_T_ReportGenerated",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
TemplateId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
TemplateName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
GeneratedContent = table.Column<string>(type: "nvarchar(max)", nullable: false),
Parameters = table.Column<string>(type: "nvarchar(max)", maxLength: 4096, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Sas_T_ReportGenerated", x => x.Id);
table.ForeignKey(
name: "FK_Sas_T_ReportGenerated_Sas_T_ReportTemplate_TemplateId",
column: x => x.TemplateId,
principalTable: "Sas_T_ReportTemplate",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Sas_T_ReportParameter",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
TemplateId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
Placeholder = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
Type = table.Column<string>(type: "nvarchar(max)", nullable: false),
DefaultValue = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
Required = table.Column<bool>(type: "bit", nullable: false),
Description = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Sas_T_ReportParameter", x => x.Id);
table.ForeignKey(
name: "FK_Sas_T_ReportParameter_Sas_T_ReportTemplate_TemplateId",
column: x => x.TemplateId,
principalTable: "Sas_T_ReportTemplate",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Sas_H_District",
columns: table => new
@ -9742,16 +9681,6 @@ namespace Erp.Platform.Migrations
table: "Sas_T_CustomEntityField",
column: "EntityId");
migrationBuilder.CreateIndex(
name: "IX_Sas_T_ReportGenerated_TemplateId",
table: "Sas_T_ReportGenerated",
column: "TemplateId");
migrationBuilder.CreateIndex(
name: "IX_Sas_T_ReportParameter_TemplateId",
table: "Sas_T_ReportParameter",
column: "TemplateId");
migrationBuilder.CreateIndex(
name: "IX_Sas_T_ReportTemplate_CategoryId",
table: "Sas_T_ReportTemplate",
@ -10766,10 +10695,7 @@ namespace Erp.Platform.Migrations
name: "Sas_T_Note");
migrationBuilder.DropTable(
name: "Sas_T_ReportGenerated");
migrationBuilder.DropTable(
name: "Sas_T_ReportParameter");
name: "Sas_T_ReportTemplate");
migrationBuilder.DropTable(
name: "Scp_T_ApprovalStep");
@ -10940,7 +10866,7 @@ namespace Erp.Platform.Migrations
name: "Sas_T_CustomEntity");
migrationBuilder.DropTable(
name: "Sas_T_ReportTemplate");
name: "Sas_T_ReportCategory");
migrationBuilder.DropTable(
name: "Scp_T_Approval");
@ -11041,9 +10967,6 @@ namespace Erp.Platform.Migrations
migrationBuilder.DropTable(
name: "Sas_H_ListForm");
migrationBuilder.DropTable(
name: "Sas_T_ReportCategory");
migrationBuilder.DropTable(
name: "Scp_T_Quotation");

View file

@ -12874,141 +12874,6 @@ namespace Erp.Platform.Migrations
b.ToTable("Sas_T_ReportCategory", (string)null);
});
modelBuilder.Entity("Erp.Platform.Entities.ReportGenerated", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("GeneratedContent")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Parameters")
.HasMaxLength(4096)
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("TemplateId")
.HasColumnType("uniqueidentifier");
b.Property<string>("TemplateName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.HasKey("Id");
b.HasIndex("TemplateId");
b.ToTable("Sas_T_ReportGenerated", (string)null);
});
modelBuilder.Entity("Erp.Platform.Entities.ReportParameter", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<string>("DefaultValue")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Placeholder")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("Required")
.HasColumnType("bit");
b.Property<Guid>("TemplateId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("TemplateId");
b.ToTable("Sas_T_ReportParameter", (string)null);
});
modelBuilder.Entity("Erp.Platform.Entities.ReportTemplate", b =>
{
b.Property<Guid>("Id")
@ -13060,9 +12925,9 @@ namespace Erp.Platform.Migrations
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("Tags")
.HasMaxLength(2048)
.HasColumnType("nvarchar(2048)");
b.Property<string>("Status")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
@ -21745,27 +21610,6 @@ namespace Erp.Platform.Migrations
b.Navigation("Branch");
});
modelBuilder.Entity("Erp.Platform.Entities.ReportGenerated", b =>
{
b.HasOne("Erp.Platform.Entities.ReportTemplate", "ReportTemplate")
.WithMany("Generated")
.HasForeignKey("TemplateId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("ReportTemplate");
});
modelBuilder.Entity("Erp.Platform.Entities.ReportParameter", b =>
{
b.HasOne("Erp.Platform.Entities.ReportTemplate", "ReportTemplate")
.WithMany("Parameters")
.HasForeignKey("TemplateId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ReportTemplate");
});
modelBuilder.Entity("Erp.Platform.Entities.ReportTemplate", b =>
{
b.HasOne("Erp.Platform.Entities.ReportCategory", "ReportCategory")
@ -22955,13 +22799,6 @@ namespace Erp.Platform.Migrations
b.Navigation("ReportTemplates");
});
modelBuilder.Entity("Erp.Platform.Entities.ReportTemplate", b =>
{
b.Navigation("Generated");
b.Navigation("Parameters");
});
modelBuilder.Entity("Erp.Platform.Entities.Request", b =>
{
b.Navigation("Items");

View file

@ -103,23 +103,6 @@
"dependencies": ["DynamicEntityComponent"]
}
],
"ReportCategories": [
{
"name": "Genel Raporlar",
"description": "Şirket içi genel tüm raporlar",
"icon": "📊"
},
{
"name": "Taahhütnameler",
"description": "Kursiyeler ile ilgili taahhütname raporları",
"icon": "✍️"
},
{
"name": "Sözleşmeler",
"description": "Tedarikçiler ile ilgili sözleşme raporları",
"icon": "📜"
}
],
"Abouts": [
{
"stats": [
@ -6636,5 +6619,31 @@
"description": "Üretim siparişi beklemede",
"isActive": true
}
],
"ReportCategories": [
{
"name": "Genel Raporlar",
"description": "Şirket içi genel tüm raporlar",
"icon": "📊"
},
{
"name": "Taahhütnameler",
"description": "Kursiyeler ile ilgili taahhütname raporları",
"icon": "✍️"
},
{
"name": "Sözleşmeler",
"description": "Tedarikçiler ile ilgili sözleşme raporları",
"icon": "📜"
}
],
"ReportTemplates": [
{
"name": "Standard Taahhütname",
"description": "Standard Taahhütname",
"categoryName": "Taahhütnameler",
"htmlContent": "<h1>Taahhütname</h1><p>Bu belge, tarafların belirli yükümlülüklerini ve taahhütlerini belirtir.</p>",
"status": "Aktif"
}
]
}

View file

@ -26,6 +26,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
private readonly IRepository<CustomEndpoint, Guid> _customEndpointRepository;
private readonly IRepository<CustomComponent, Guid> _customComponentRepository;
private readonly IRepository<ReportCategory, Guid> _reportCategoriesRepository;
private readonly IRepository<ReportTemplate, Guid> _reportTemplatesRepository;
private readonly IRepository<About, Guid> _aboutRepository;
private readonly IRepository<Product, Guid> _productRepository;
private readonly IRepository<PaymentMethod, string> _paymentMethodRepository;
@ -170,6 +171,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
IRepository<CustomEndpoint, Guid> customEndpointRepository,
IRepository<CustomComponent, Guid> customComponentRepository,
IRepository<ReportCategory, Guid> reportCategoriesRepository,
IRepository<ReportTemplate, Guid> reportTemplatesRepository,
IRepository<About, Guid> aboutRepository,
IRepository<Service, Guid> servicesRepository,
IRepository<Product, Guid> productRepository,
@ -313,6 +315,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
_installmentOptionRepository = installmentOptionRepository;
_customComponentRepository = customComponentRepository;
_reportCategoriesRepository = reportCategoriesRepository;
_reportTemplatesRepository = reportTemplatesRepository;
_servicesRepository = servicesRepository;
_aboutRepository = aboutRepository;
_contactRepository = contactRepository;
@ -2951,6 +2954,24 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
IsActive = item.IsActive
}, autoSave: true);
}
foreach (var item in items.ReportTemplates)
{
var exists = await _reportTemplatesRepository.AnyAsync(x => x.Name == item.Name);
if (exists)
continue;
var category = await _reportCategoriesRepository.FirstOrDefaultAsync(x => x.Name == item.CategoryName);
await _reportTemplatesRepository.InsertAsync(new ReportTemplate(
Guid.NewGuid(),
item.Name,
item.Description,
item.HtmlContent,
category.Id,
item.Status
), autoSave: true);
}
}
}

View file

@ -156,6 +156,18 @@ public class TenantSeederDto
public List<ProductionOrderTypeSeedDto> ProductionOrderTypes { get; set; }
public List<ProductionOrderStatusSeedDto> ProductionOrderStatuses { get; set; }
public List<ProductionWorkorderStatusSeedDto> ProductionWorkorderStatuses { get; set; }
//Report Templates
public List<ReportTemplateSeedDto> ReportTemplates { get; set; }
}
public class ReportTemplateSeedDto
{
public string Name { get; set; }
public string Description { get; set; }
public string CategoryName { get; set; }
public string HtmlContent { get; set; }
public string Status { get; set; }
}
public class ProductionWorkorderStatusSeedDto

View file

@ -1,322 +0,0 @@
import React, { useState, useMemo, useEffect } from 'react'
import { TemplateEditor } from '../reports/TemplateEditor'
import { ReportGenerator } from '../reports/ReportGenerator'
import { TemplateCard } from './TemplateCard'
import { Button, Input } from '../ui'
import { FaPlus, FaSearch, FaFilter, FaFileAlt, FaChartBar } from 'react-icons/fa'
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,
categories,
isLoading,
createTemplate,
updateTemplate,
deleteTemplate,
generateReport,
} = useReports()
const { translate } = useLocalization()
const tumuCategory = useMemo(
() => ({
id: 'tumu-category',
name: translate('::App.Reports.Dashboard.All'),
description: translate('::App.Reports.Dashboard.AllDescription'),
icon: '📋',
}),
[translate],
)
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<ReportCategoryDto | null>(null)
// Set initial selected category to tumuCategory
useEffect(() => {
if (!selectedCategory) {
setSelectedCategory(tumuCategory)
}
}, [tumuCategory, selectedCategory])
// Create category options with "tumu-category" at the top
const categoryOptions = useMemo(() => {
const categoryNames = [tumuCategory, ...categories.map((cat) => cat)]
return categoryNames
}, [categories, tumuCategory])
// Filter templates based on selected category and search query
const filteredTemplates = useMemo(() => {
let filtered = templates
// Filter by category (if not "tumu-category")
if (selectedCategory && selectedCategory.id !== 'tumu-category') {
filtered = filtered.filter((template) => template.categoryId === selectedCategory.id)
}
// Filter by search query
if (searchQuery) {
filtered = filtered.filter((template) => {
const matchesSearch =
template.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.tags.some((tag: any) => tag.toLowerCase().includes(searchQuery.toLowerCase()))
return matchesSearch
})
}
return filtered
}, [templates, selectedCategory, searchQuery])
// Calculate total parameters
const totalParameters = useMemo(() => {
return filteredTemplates.reduce((sum, template) => {
return sum + (Array.isArray(template.parameters) ? template.parameters.length : 0)
}, 0)
}, [filteredTemplates])
const handleCreateTemplate = () => {
setEditingTemplate(null)
setShowEditor(true)
}
const handleEditTemplate = (template: ReportTemplateDto) => {
setEditingTemplate(template)
setShowEditor(true)
}
const handleSaveTemplate = async (templateData: ReportTemplateDto) => {
try {
if (editingTemplate) {
await updateTemplate(editingTemplate.id, templateData)
} else {
await createTemplate(templateData)
}
setShowEditor(false)
setEditingTemplate(null)
} catch (error) {
console.error('Error saving template:', error)
// Handle error - could show toast notification
}
}
const handleDeleteTemplate = async (id: string) => {
if (window.confirm(translate('::App.Reports.Dashboard.DeleteTemplateConfirmation'))) {
try {
await deleteTemplate(id)
} catch (error) {
console.error('Error deleting template:', error)
// Handle error - could show toast notification
}
}
}
const handleGenerateReport = (template: ReportTemplateDto) => {
setGeneratingTemplate(template)
setShowGenerator(true)
}
const handleReportGeneration = async (
templateId: string,
parameters: Record<string, string>,
): Promise<string | null> => {
try {
const report = await generateReport(templateId, parameters)
return report ? report.id : null
} catch (error) {
console.error('Error generating report:', error)
return null
}
}
return (
<>
{isLoading ? (
<div className="flex items-center justify-center min-h-96">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">{translate('::App.Loading')}</p>
</div>
</div>
) : (
<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 p-2 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.id !== 'tumu-category' && (
<span className="text-sm text-gray-500 ml-2">
({templates.filter((t) => t.categoryId === category.id).length})
</span>
)}
{category.id === 'tumu-category' && (
<span className="text-sm text-gray-500 ml-2">({templates.length})</span>
)}
</button>
)
})}
</nav>
</div>
{/* Main Content */}
<div className="flex-1">
<div className="flex items-center justify-between mb-4">
<div className="flex-1">
<h2 className="text-2xl font-bold text-gray-900">
{translate('::' + selectedCategory?.name)}
</h2>
<div className="text-sm text-gray-500 ml-1">
{translate('::' + selectedCategory?.description)}
</div>
</div>
<div className="flex items-center space-x-4">
{/* Search Input */}
<div className="relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder={translate('::App.Reports.Dashboard.SearchTemplate')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 w-64"
/>
</div>
{/* New Template Button */}
<Button
variant="solid"
onClick={handleCreateTemplate}
className="bg-blue-600 hover:bg-blue-700 font-medium px-6 py-2.5 rounded-lg shadow-md hover:shadow-lg transition-all duration-200 flex items-center space-x-2"
>
<FaPlus className="h-5 w-5" />
<span>{translate('::App.Reports.Dashboard.NewTemplate')}</span>
</Button>
</div>
</div>
{/* 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">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
{selectedCategory?.id === 'tumu-category'
? translate('::App.Reports.Dashboard.TotalTemplates')
: translate('::App.Reports.Dashboard.CategoryTemplates')}
</p>
<p className="text-2xl font-bold text-gray-900">{filteredTemplates.length}</p>
</div>
<div className="bg-blue-100 p-3 rounded-full">
<FaFileAlt className="h-6 w-6 text-blue-600" />
</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-200">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
{translate('::App.Reports.Dashboard.ActiveCategories')}
</p>
<p className="text-2xl font-bold text-gray-900">{categories.length}</p>
</div>
<div className="bg-emerald-100 p-3 rounded-full">
<FaFilter className="h-6 w-6 text-emerald-600" />
</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-200">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
{selectedCategory?.id === 'tumu-category'
? translate('::App.Reports.Dashboard.TotalParameters')
: translate('::App.Reports.Dashboard.CategoryParameters')}
</p>
<p className="text-2xl font-bold text-gray-900">{totalParameters}</p>
</div>
<div className="bg-purple-100 p-3 rounded-full">
<FaChartBar className="h-6 w-6 text-purple-600" />
</div>
</div>
</div>
</div>
{/* Templates Grid */}
{filteredTemplates.length === 0 ? (
<div className="bg-white rounded-xl shadow-md p-12 border border-gray-200 text-center">
<FaFileAlt className="h-16 w-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">
{templates.length === 0 ? 'Henüz şablon oluşturulmamış' : 'Şablon bulunamadı'}
</h3>
<p className="text-gray-500 mb-6">
{templates.length === 0
? translate('::App.Reports.Dashboard.CreateFirstTemplate')
: translate('::App.Reports.Dashboard.NoSearchResults')}
</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredTemplates.map((template) => (
<TemplateCard
key={template.id}
template={template}
categories={categories}
onEdit={handleEditTemplate}
onDelete={handleDeleteTemplate}
onGenerate={handleGenerateReport}
/>
))}
</div>
)}
</div>
</div>
)}
{/* Modals */}
<TemplateEditor
isOpen={showEditor}
onClose={() => {
setShowEditor(false)
setEditingTemplate(null)
}}
onSave={handleSaveTemplate}
template={editingTemplate}
categories={categories}
/>
<ReportGenerator
isOpen={showGenerator}
onClose={() => {
setShowGenerator(false)
setGeneratingTemplate(null)
}}
template={generatingTemplate}
categories={categories}
onGenerate={handleReportGeneration}
/>
</>
)
}

View file

@ -1,146 +0,0 @@
import React, { useState } from 'react'
import { Button, Input, Dialog } from '../ui'
import { FaFileAlt } from 'react-icons/fa'
import { ReportCategoryDto, ReportTemplateDto } from '@/proxy/reports/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant'
interface ReportGeneratorProps {
isOpen: boolean
onClose: () => void
template: ReportTemplateDto | null
categories: ReportCategoryDto[]
onGenerate: (templateId: string, parameters: Record<string, string>) => Promise<string | null> // Rapor ID'si döndürmek için (async)
}
export const ReportGenerator: React.FC<ReportGeneratorProps> = ({
isOpen,
onClose,
template,
categories,
onGenerate,
}) => {
const [parameterValues, setParameterValues] = useState<Record<string, string>>({})
const [isGenerating, setIsGenerating] = useState(false)
const { translate } = useLocalization()
// const [showPrintPreview, setShowPrintPreview] = useState(false);
React.useEffect(() => {
if (template && isOpen) {
const initialValues: Record<string, string> = {}
template.parameters.forEach((param) => {
initialValues[param.name] = param.defaultValue || ''
})
setParameterValues(initialValues)
}
}, [template, isOpen])
const handleParameterChange = (paramName: string, value: string) => {
setParameterValues((prev) => ({
...prev,
[paramName]: value,
}))
}
const handleGenerateAndShow = async () => {
if (!template) return
setIsGenerating(true)
try {
// Rapor oluştur ve ID'yi al
const reportId = await onGenerate(template.id, parameterValues)
if (reportId) {
// Yeni sekmede rapor URL'sini aç
const reportUrl = ROUTES_ENUM.protected.saas.reports.view.replace(':id', reportId)
window.open(reportUrl, '_blank')
onClose() // Modal'ı kapat
}
} catch (error) {
console.error('Error generating report:', error)
// Handle error - could show toast notification
} finally {
setIsGenerating(false)
}
}
if (!template) return null
const isValid = template.parameters.every(
(param) =>
!param.required || (parameterValues[param.name] && parameterValues[param.name].trim()),
)
return (
<>
<Dialog isOpen={isOpen} onClose={onClose} width="60%">
<h5 className="mb-4">{`${template.name} - ${translate('::App.Reports.ReportGenerator.ReportParameters')}`}</h5>
<div className="space-y-6">
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-medium text-gray-900 mb-2">{template.name}</h3>
<p className="text-gray-600 text-sm">{template.description}</p>
<div className="flex items-center mt-2 space-x-2">
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
{categories.find((c) => c.id === template.categoryId)?.name}
</span>
{template.tags.map((tag) => (
<span key={tag} className="text-xs bg-gray-100 text-gray-800 px-2 py-1 rounded">
{tag}
</span>
))}
</div>
</div>
{template.parameters.length > 0 ? (
<div className="space-y-4">
<h4 className="font-medium text-gray-900">
{translate('::App.Reports.ReportGenerator.ParameterValues')}
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{template.parameters.map((param) => (
<Input
key={param.id}
type={param.type}
value={parameterValues[param.name] || ''}
onChange={(e) => handleParameterChange(param.name, e.target.value)}
placeholder={`${param.name} ${param.required ? '*' : ''}`}
title={param.description}
/>
))}
</div>
{template.parameters.some((p) => p.required) && (
<p className="text-sm text-gray-500">
{translate('::App.Reports.ReportGenerator.RequiredFieldsNote')}
</p>
)}
</div>
) : (
<div className="py-8 text-gray-500">
<FaFileAlt className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p>{translate('::App.Reports.ReportGenerator.NoParameters')}</p>
<p className="text-sm">{translate('::App.Reports.ReportGenerator.DirectGenerate')}</p>
</div>
)}
<div className="flex justify-end space-x-3">
<Button onClick={onClose} disabled={isGenerating}>
{translate('::Cancel')}
</Button>
<Button
variant="solid"
onClick={handleGenerateAndShow}
disabled={!isValid || isGenerating}
className="bg-blue-600 hover:bg-blue-700 font-medium px-2 sm:px-3 py-1.5 rounded text-xs flex items-center gap-1"
>
<FaFileAlt className="h-4 w-4 mr-2" />
{isGenerating
? translate('::App.DeveloperKit.Migration.Generating')
: translate('::App.Reports.ReportGenerator.GenerateReport')}
</Button>
</div>
</div>
</Dialog>
</>
)
}

View file

@ -1,82 +0,0 @@
import React from 'react'
import { HtmlEditor, ImageUpload, Item, MediaResizing, Toolbar } from 'devextreme-react/html-editor'
import 'devextreme/dist/css/dx.light.css'
import {
fontFamilyOptions,
fontSizeOptions,
fontValues,
headerOptions,
headerValues,
sizeValues,
} from '@/proxy/reports/data'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface ReportHtmlEditorProps {
value: string
onChange: (value: string) => void
placeholder?: string
height?: string
}
export const ReportHtmlEditor: React.FC<ReportHtmlEditorProps> = ({
value,
onChange,
height = '100%',
}) => {
const { translate } = useLocalization()
return (
<div className="flex flex-col space-y-2" style={{ height }}>
<HtmlEditor
value={value}
onValueChanged={(e) => onChange(e.value)}
height={height || '400px'}
placeholder={translate('::App.Reports.ReportHtmlViewer.Placeholder')}
>
<MediaResizing enabled={true} />
<ImageUpload fileUploadMode="base64" />
<Toolbar multiline={true}>
<Item name="undo" />
<Item name="redo" />
<Item name="separator" />
<Item name="size" acceptedValues={sizeValues} options={fontSizeOptions} />
<Item name="font" acceptedValues={fontValues} options={fontFamilyOptions} />
<Item name="separator" />
<Item name="bold" />
<Item name="italic" />
<Item name="strike" />
<Item name="underline" />
<Item name="separator" />
<Item name="alignLeft" />
<Item name="alignCenter" />
<Item name="alignRight" />
<Item name="alignJustify" />
<Item name="separator" />
<Item name="orderedList" />
<Item name="bulletList" />
<Item name="separator" />
<Item name="header" acceptedValues={headerValues} options={headerOptions} />
<Item name="separator" />
<Item name="color" />
<Item name="background" />
<Item name="separator" />
<Item name="link" />
<Item name="image" />
<Item name="separator" />
<Item name="clear" />
<Item name="codeBlock" />
<Item name="blockquote" />
<Item name="separator" />
<Item name="insertTable" />
<Item name="deleteTable" />
<Item name="insertRowAbove" />
<Item name="insertRowBelow" />
<Item name="deleteRow" />
<Item name="insertColumnLeft" />
<Item name="insertColumnRight" />
<Item name="deleteColumn" />
</Toolbar>
</HtmlEditor>
</div>
)
}

View file

@ -1,509 +0,0 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { Button } from '../ui/Button'
import {
FaArrowLeft,
FaCalendarAlt,
FaFileAlt,
FaDownload,
FaSearchPlus,
FaSearchMinus,
} from 'react-icons/fa'
import { ReportGeneratedDto, ReportTemplateDto } from '@/proxy/reports/models'
import { useReports } from '@/utils/hooks/useReports'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
export const ReportViewer: React.FC = () => {
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const [zoomLevel, setZoomLevel] = useState(100)
const [report, setReport] = useState<ReportGeneratedDto | null>(null)
const [template, setTemplate] = useState<ReportTemplateDto | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const { translate } = useLocalization()
const { getReportById, getTemplateById, categories } = useReports()
// İçeriği sayfalara bölen fonksiyon
const splitContentIntoPages = (content: string) => {
// Basit olarak içeriği paragraf ve tablo bazında bölelim
const tempDiv = document.createElement('div')
tempDiv.innerHTML = content
const elements = Array.from(tempDiv.children)
const pages: string[] = []
let currentPage = ''
let currentPageHeight = 0
const maxPageHeight = 257 // 297mm - 40mm padding (top+bottom)
elements.forEach((element) => {
const elementHtml = element.outerHTML
// Basit yükseklik tahmini (gerçek uygulamada daha karmaşık olabilir)
let estimatedHeight = 20 // Default height
if (element.tagName === 'TABLE') {
const rows = element.querySelectorAll('tr')
estimatedHeight = rows.length * 25 // Her satır için 25mm
} else if (element.tagName.startsWith('H')) {
estimatedHeight = 15
} else if (element.tagName === 'P') {
estimatedHeight = 10
}
if (currentPageHeight + estimatedHeight > maxPageHeight && currentPage) {
pages.push(currentPage)
currentPage = elementHtml
currentPageHeight = estimatedHeight
} else {
currentPage += elementHtml
currentPageHeight += estimatedHeight
}
})
if (currentPage) {
pages.push(currentPage)
}
return pages.length > 0 ? pages : [content]
}
const preloadPdfLibs = useCallback(() => {
// Hoverda ısıtma için (opsiyonel)
import('jspdf')
import('html2canvas')
}, [])
// YENİ: memoize edilmiş sayfalar
const memoizedPages = useMemo(() => {
return report ? splitContentIntoPages(report.generatedContent) : []
}, [report])
// Asenkron veri yükleme
useEffect(() => {
const loadReportData = async () => {
if (!id) {
setError("Rapor ID'si bulunamadı")
setIsLoading(false)
return
}
try {
setIsLoading(true)
setError(null)
// Raporu yükle
const reportData = await getReportById(id)
if (!reportData) {
setError('Rapor bulunamadı')
setIsLoading(false)
return
}
setReport(reportData)
// Şablonu yükle
if (reportData.templateId) {
const templateData = await getTemplateById(reportData.templateId)
setTemplate(templateData || null)
}
} catch (err) {
console.error('Error loading report data:', err)
setError('Rapor yüklenirken bir hata oluştu')
} finally {
setIsLoading(false)
}
}
loadReportData()
}, [id, getReportById, getTemplateById])
// Zoom fonksiyonları
const handleZoomIn = () => {
setZoomLevel((prev) => Math.min(prev + 25, 200)) // Maksimum %200
}
const handleZoomOut = () => {
setZoomLevel((prev) => Math.max(prev - 25, 50)) // Minimum %50
}
// Loading durumu
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<h2 className="text-2xl font-bold text-gray-900">
{translate('::App.Reports.ReportViewer.LoadingTitle')}
</h2>
<p className="text-gray-600">{translate('::App.Reports.ReportViewer.LoadingSubtitle')}</p>
</div>
</div>
)
}
// Error durumu
if (error || !id) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">
{error || translate('::App.Reports.ReportViewer.ErrorNotFound')}
</h1>
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<FaArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön
</Button>
</div>
</div>
)
}
if (!id) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">
{translate('::App.Reports.ReportViewer.ErrorNotFound')}
</h1>
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<FaArrowLeft className="h-4 w-4 mr-2" />
Ana Sayfaya Dön
</Button>
</div>
</div>
)
}
// Report yüklenmemiş ise
if (!report) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">
{translate('::App.Reports.ReportViewer.ErrorNotFound')}
</h1>
<p className="text-gray-600 mb-6">
{translate('::App.Reports.ReportViewer.ErrorNotFoundDescription')}
</p>
<Button onClick={() => navigate(ROUTES_ENUM.protected.dashboard)}>
<FaArrowLeft className="h-4 w-4 mr-2" />
{translate('::Public.notFound.button')}
</Button>
</div>
</div>
)
}
const handlePrint = () => {
// Yazdırma sırasında zoom seviyesini geçici olarak %100'e ayarla
const currentZoom = zoomLevel
setZoomLevel(100)
// DOM'un güncellenmesi için kısa bir gecikme
setTimeout(() => {
window.print()
// Yazdırma işlemi tamamlandıktan sonra orijinal zoom seviyesine geri dön
// Print dialog kapandıktan sonra zoom'u eski haline getir
setTimeout(() => {
setZoomLevel(currentZoom)
}, 100)
}, 100)
}
// DEĞİŞTİR: handleDownloadPdf
const handleDownloadPdf = async () => {
// Ağır kütüphaneleri ihtiyaç anında indir
const [jspdfMod, h2cMod] = await Promise.all([import('jspdf'), import('html2canvas')])
// jsPDF bazı dağıtımlarda default, bazılarında { jsPDF } olarak gelir
const jsPDFCtor = (jspdfMod as any).default ?? (jspdfMod as any).jsPDF
const html2canvas = (h2cMod as any).default ?? (h2cMod as any)
const pages = memoizedPages // aşağıdaki 2. adımda tanımlayacağız
try {
const pdf = new jsPDFCtor({ orientation: 'portrait', unit: 'mm', format: 'a4' })
for (let i = 0; i < pages.length; i++) {
const elementId = i === 0 ? 'report-content' : `report-content-page-${i + 1}`
const element = document.getElementById(elementId)
if (!element) continue
// Yakalama öncesi zoomu etkisizleştir (transform varsa kalite düşmesin)
const container = element.parentElement as HTMLElement | null
const prevTransform = container?.style.transform
if (container) container.style.transform = 'none'
const canvas = await html2canvas(element, {
scale: 2,
useCORS: true,
allowTaint: true,
backgroundColor: '#ffffff',
})
if (container) container.style.transform = prevTransform ?? ''
const imgData = canvas.toDataURL('image/png')
const imgWidth = 210
const imgHeight = 297
if (i > 0) pdf.addPage()
pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight)
}
pdf.save(
`${report!.templateName}_${new Date(report!.creationTime).toLocaleDateString('tr-TR')}.pdf`,
)
} catch (error) {
console.error('PDF oluşturma hatası:', error)
alert('PDF oluşturulurken bir hata oluştu.')
}
}
return (
<div className="min-h-screen bg-gray-50">
{/* Header - Print edilmeyecek */}
<div className="print:hidden bg-white shadow-sm border-b border-gray-200 sticky top-0 z-10">
<div className="w-full px-4">
<div className="flex items-center justify-between h-16">
<div className="flex items-center space-x-4 flex-1">
<div className="flex-1">
<h1 className="text-lg font-semibold text-gray-900">{report.templateName}</h1>
<div className="flex items-center space-x-4 text-sm text-gray-500">
<span className="flex items-center">
<FaCalendarAlt className="h-4 w-4 mr-1" />
{new Date(report.creationTime).toLocaleDateString('tr-TR', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</span>
{template && (
<span className="flex items-center">
<FaFileAlt className="h-4 w-4 mr-1" />
{categories.find((c) => c.id === template.categoryId)?.name}
</span>
)}
</div>
</div>
</div>
<div className="flex items-center space-x-2 flex-shrink-0">
<Button variant="solid" onClick={handleZoomOut} disabled={zoomLevel <= 50} size="sm">
<FaSearchMinus className="h-4 w-4" />
</Button>
<span className="text-sm text-gray-600 px-2 min-w-[4rem] text-center">
{zoomLevel}%
</span>
<Button variant="solid" onClick={handleZoomIn} disabled={zoomLevel >= 200} size="sm">
<FaSearchPlus className="h-4 w-4" />
</Button>
<div className="w-px h-6 bg-gray-300 mx-2"></div>
<Button
onMouseEnter={preloadPdfLibs} // ← opsiyonel prefetch
onClick={handleDownloadPdf}
className="bg-white-600 hover:bg-white-700 font-medium px-2 sm:px-3 py-1.5 rounded text-xs flex items-center gap-1"
>
<FaDownload className="h-4 w-4 mr-2" />
{translate('::App.Reports.ReportViewer.DownloadPDF')}
</Button>
<Button variant="solid" onClick={handlePrint}>
{translate('::App.Reports.ReportViewer.Print')}
</Button>
</div>
</div>
</div>
</div>
{/* Rapor İçeriği */}
<div className="flex justify-center bg-gray-200 py-8 print:bg-white print:py-0">
<div
className="space-y-8 print:space-y-0 transition-transform duration-300 ease-in-out"
style={{
transform: `scale(${zoomLevel / 100})`,
transformOrigin: 'top center',
}}
>
{memoizedPages.map((pageContent, index) => (
<div
key={index}
id={index === 0 ? 'report-content' : `report-content-page-${index + 1}`}
className="bg-white shadow-lg print:shadow-none page-container"
style={{
width: '210mm',
minHeight: '297mm',
padding: '0',
boxSizing: 'border-box',
position: 'relative',
display: 'flex',
flexDirection: 'column',
}}
>
{index === 0 && (
<style>
{`
/* Sayfa kırılması için CSS */
.page-container {
page-break-after: always;
margin-bottom: 20px;
}
.page-container:last-child {
page-break-after: auto;
margin-bottom: 0;
}
/* Header ve Footer stilleri */
.page-header {
height: 15mm;
width: 100%;
display: flex;
align-items: center;
justify-content: right;
background-color: transparent;
color: #666;
font-size: 10px;
padding: 0 20mm;
box-sizing: border-box;
}
.page-footer {
height: 15mm;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
background-color: transparent;
color: #666;
font-size: 10px;
padding: 0 20mm;
box-sizing: border-box;
margin-top: auto;
}
.page-content-area {
flex: 1;
padding: 0 20mm;
min-height: 267mm; /* 297mm - 15mm header - 15mm footer */
box-sizing: border-box;
}
/* İçerik için sayfa kırılması kuralları */
.page-container h1, .page-container h2, .page-container h3 {
page-break-after: avoid;
}
.page-container table {
page-break-inside: avoid;
border-collapse: collapse;
width: 100%;
margin: 0;
}
.page-container table td,
.page-container table th {
border: 1px solid #ddd;
text-align: left;
vertical-align: top;
}
/* Print specific styles */
@media print {
body {
margin: 0 !important;
padding: 0 !important;
}
.page-container {
width: 210mm !important;
min-height: 297mm !important;
margin: 0 !important;
padding: 0 !important;
box-shadow: none !important;
page-break-after: always !important;
}
.page-container:last-child {
page-break-after: auto !important;
}
.page-header {
height: 15mm !important;
color: #666 !important;
-webkit-print-color-adjust: exact !important;
}
.page-footer {
height: 15mm !important;
color: #666 !important;
-webkit-print-color-adjust: exact !important;
}
.page-content-area {
padding: 0 20mm !important;
min-height: 267mm !important;
}
.print\\:hidden {
display: none !important;
}
/* Table print styles */
.page-container table {
border-collapse: collapse !important;
page-break-inside: avoid !important;
}
.page-container table th,
.page-container table td {
border: 1px solid #000 !important;
page-break-inside: avoid !important;
}
.page-container table th {
background-color: #f0f0f0 !important;
-webkit-print-color-adjust: exact !important;
color-adjust: exact !important;
}
.page-container table tr:nth-child(even) {
background-color: #f5f5f5 !important;
-webkit-print-color-adjust: exact !important;
color-adjust: exact !important;
}
}
`}
</style>
)}
{/* Sayfa Header - Tarih ve Saat */}
<div className="page-header">
{new Date().toLocaleDateString('tr-TR', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</div>
{/* Sayfa İçeriği */}
<div className="page-content-area">
<div dangerouslySetInnerHTML={{ __html: pageContent }} />
</div>
{/* Sayfa Footer - Sayfa Numarası */}
<div className="page-footer">
{translate('::App.Reports.ReportViewer.Page')} {index + 1} / {memoizedPages.length}
</div>
</div>
))}
</div>
</div>
</div>
)
}

View file

@ -1,112 +0,0 @@
import React from 'react'
import { Button } from '../ui/Button'
import { FaFileAlt, FaEdit, FaTrash, FaPlay } from 'react-icons/fa'
import { ReportCategoryDto, ReportTemplateDto } from '@/proxy/reports/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface TemplateCardProps {
template: ReportTemplateDto
categories: ReportCategoryDto[]
onEdit: (template: ReportTemplateDto) => void
onDelete: (id: string) => void
onGenerate: (template: ReportTemplateDto) => void
}
export const TemplateCard: React.FC<TemplateCardProps> = ({
template,
categories,
onEdit,
onDelete,
onGenerate,
}) => {
const { translate } = useLocalization()
return (
<div className="bg-white rounded-xl shadow-md hover:shadow-lg transition-all duration-200 border border-gray-200 flex flex-col">
<div className="p-4 flex-1 flex flex-col">
{/* Header with title and parameter count */}
<div className="flex items-start justify-between mb-3 min-h-0">
<div className="flex-1 min-w-0 pr-3">
<h3 className="text-lg font-semibold text-gray-900 mb-2 truncate">{template.name}</h3>
<p className="text-gray-600 text-sm mb-3 line-clamp-2">{template.description}</p>
</div>
<div className="flex items-center gap-1 flex-shrink-0 bg-gray-50 px-2 py-1 rounded-lg">
<FaFileAlt className="h-4 w-4 text-blue-500" />
<span className="text-xs text-gray-500 whitespace-nowrap">
{template.parameters.length}
</span>
</div>
</div>
{/* Tags section with proper wrapping */}
<div className="flex items-center gap-1 mb-4 flex-wrap min-h-0">
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full font-medium flex-shrink-0">
{categories.find((c) => c.id === template.categoryId)?.name}
</span>
{template.tags.slice(0, 2).map((tag) => (
<span
key={tag}
className="text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded-full flex-shrink-0 max-w-20 truncate"
>
{tag}
</span>
))}
{template.tags.length > 2 && (
<span className="text-xs text-gray-500 flex-shrink-0">+{template.tags.length - 2}</span>
)}
</div>
{/* Footer with date and actions */}
<div className="flex flex-col gap-3 mt-auto pt-3 border-t border-gray-100">
<div className="text-xs text-gray-500">
<p className="truncate">
{new Date(template.lastModificationTime || template.creationTime).toLocaleString(
'tr-TR',
{
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
)}
</p>
</div>
<div className="flex items-center justify-center gap-2 w-full">
<Button
variant="solid"
size="sm"
onClick={() => onGenerate(template)}
className="bg-gray-600 hover:bg-gray-700 font-medium px-3 py-1.5 rounded text-xs flex items-center gap-1 flex-1 justify-center min-w-0"
>
<FaPlay className="h-3 w-3" />
<span className="truncate">{translate('::App.Reports.TemplateCard.Show')}</span>
</Button>
<Button
variant="default"
size="sm"
onClick={() => onEdit(template)}
className="font-medium px-3 py-1.5 rounded text-xs flex items-center gap-1 flex-1 justify-center min-w-0"
>
<FaEdit className="h-3 w-3" />
<span className="truncate">{translate('::Abp.Identity.OrganizationUnit.Edit')}</span>
</Button>
<Button
variant="solid"
size="sm"
onClick={() => onDelete(template.id)}
className="bg-red-600 hover:bg-red-700 font-medium px-3 py-1.5 rounded text-xs flex items-center justify-center flex-1 min-w-0"
>
<FaTrash className="h-3 w-3" />
<span className="truncate">{translate('::Delete')}</span>
</Button>
</div>
</div>
</div>
</div>
)
}

View file

@ -1,437 +0,0 @@
import React, { useState, useEffect } from 'react'
import { FaSave, FaTimes, FaFileAlt, FaCode, FaCog } from 'react-icons/fa'
import { ReportHtmlEditor } from './ReportHtmlEditor'
import { ReportParameterDto, ReportTemplateDto, ReportCategoryDto } from '@/proxy/reports/models'
import { Button, Input, Dialog } from '../ui'
import { useLocalization } from '@/utils/hooks/useLocalization'
interface TemplateEditorProps {
isOpen: boolean
onClose: () => void
onSave: (template: ReportTemplateDto) => Promise<void>
template?: ReportTemplateDto | null
categories: ReportCategoryDto[]
}
export const TemplateEditor: React.FC<TemplateEditorProps> = ({
isOpen,
onClose,
onSave,
template,
categories,
}) => {
const [activeTab, setActiveTab] = useState<'info' | 'parameters' | 'content'>('info')
const [formData, setFormData] = useState({
name: '',
description: '',
htmlContent: '',
categoryId: '',
tags: [] as string[],
parameters: [] as ReportParameterDto[],
})
const { translate } = useLocalization()
const [tagInput, setTagInput] = useState('')
const [isSaving, setIsSaving] = useState(false)
useEffect(() => {
if (template) {
setFormData({
name: template.name,
description: template.description || '',
htmlContent: template.htmlContent,
categoryId: template.categoryId!,
tags: template.tags,
parameters: template.parameters,
})
} else {
setFormData({
name: '',
description: '',
htmlContent: '',
categoryId: '',
tags: [],
parameters: [],
})
}
setActiveTab('info')
}, [template, isOpen])
// Otomatik parametre algılama
useEffect(() => {
const extractParameters = (htmlContent: string) => {
const paramRegex = /@@([\p{L}0-9_]+)/gu
const matches = [...htmlContent.matchAll(paramRegex)]
const uniqueParams = [...new Set(matches.map((match) => match[1]))]
const newParameters: ReportParameterDto[] = uniqueParams.map((paramName) => {
// Mevcut parametreyi kontrol et
const existingParam = formData.parameters.find((p) => p.name === paramName)
if (existingParam) {
return existingParam
}
// Yeni parametre oluştur
return {
id: crypto.randomUUID(),
templateId: template?.id || '',
name: paramName,
placeholder: `@@${paramName}`,
type: 'text',
required: true,
description: `${paramName} parametresi`,
}
})
setFormData((prev) => ({
...prev,
parameters: newParameters,
}))
}
if (formData.htmlContent) {
extractParameters(formData.htmlContent)
}
}, [formData.htmlContent])
const handleSave = async () => {
if (!formData.name.trim() || !formData.htmlContent.trim()) {
return
}
console.log('FormData before save:', formData)
console.log('Categories available:', categories)
setIsSaving(true)
try {
await onSave(formData as unknown as ReportTemplateDto)
onClose()
} catch (error) {
console.error('Error saving template:', error)
// Handle error - could show toast notification
} finally {
setIsSaving(false)
}
}
const addTag = () => {
if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) {
setFormData((prev) => ({
...prev,
tags: [...prev.tags, tagInput.trim()],
}))
setTagInput('')
}
}
const removeTag = (tagToRemove: string) => {
setFormData((prev) => ({
...prev,
tags: prev.tags.filter((tag) => tag !== tagToRemove),
}))
}
const updateParameter = (paramId: string, updates: Partial<ReportParameterDto>) => {
setFormData((prev) => ({
...prev,
parameters: prev.parameters.map((param) =>
param.id === paramId ? { ...param, ...updates } : param,
),
}))
}
const tabs = [
{ id: 'info', label: translate('::App.Reports.TemplateEditor.Tab.Info'), icon: FaFileAlt },
{
id: 'parameters',
label: translate('::App.Reports.TemplateEditor.Tab.Parameters'),
icon: FaCog,
},
{ id: 'content', label: translate('::App.Reports.TemplateEditor.Tab.Content'), icon: FaCode },
]
return (
<>
<Dialog isOpen={isOpen} onClose={onClose} width="100%">
<h5 className="mb-4">
{template
? translate('::App.Reports.TemplateEditor.TitleEdit')
: translate('::App.Reports.TemplateEditor.TitleNew')}
</h5>
<div className="flex flex-col h-full">
{/* Tab Navigation */}
<div className="border-b border-gray-200 bg-gray-50 flex-shrink-0">
<nav className="flex space-x-8 px-6">
{tabs.map((tab) => {
const Icon = tab.icon
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as 'info' | 'content' | 'parameters')}
className={`
flex items-center py-4 px-1 border-b-2 font-medium text-sm transition-colors
${
activeTab === tab.id
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}
`}
>
<Icon className="h-4 w-4 mr-2" />
{tab.label}
</button>
)
})}
</nav>
</div>
{/* Tab Content */}
<div className="flex-1 flex flex-col min-h-0">
{activeTab === 'info' && (
<div className="overflow-y-auto flex-1 p-3">
<div className="mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Left Column - Basic Info */}
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{translate('::App.Reports.TemplateEditor.Label.Name')}
</label>
<input
autoFocus
value={formData.name}
onChange={(e) =>
setFormData((prev) => ({
...prev,
name: e.target.value,
}))
}
placeholder={translate('::App.Reports.TemplateEditor.Placeholder.Name')}
className="w-full flex-1 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{translate('::App.Reports.TemplateEditor.Label.Category')}
</label>
<select
value={formData.categoryId}
onChange={(e) => {
console.log('Category selected:', e.target.value)
setFormData((prev) => ({
...prev,
categoryId: e.target.value,
}))
}}
className="block 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"
>
<option value="">
{translate('::App.Reports.TemplateEditor.Placeholder.SelectCategory')}
</option>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{translate('::App.Reports.TemplateEditor.Label.Tags')}
</label>
<div className="flex space-x-2 mb-3">
<input
type="text"
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTag()}
placeholder={translate(
'::App.Reports.TemplateEditor.Placeholder.AddTag',
)}
className="flex-1 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"
/>
<Button type="button" onClick={addTag} size="sm">
{translate('::App.Reports.TemplateEditor.Button.Add')}
</Button>
</div>
<div className="flex flex-wrap gap-2">
{formData.tags.map((tag) => (
<span
key={tag}
className="inline-flex items-center px-3 py-1 text-sm bg-blue-100 text-blue-800 rounded-full"
>
{tag}
<button
onClick={() => removeTag(tag)}
className="ml-2 text-blue-600 hover:text-blue-800"
>
<FaTimes className="h-3 w-3" />
</button>
</span>
))}
</div>
</div>
</div>
{/* Right Column - Description */}
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{translate('::App.Reports.TemplateEditor.Label.Description')}
</label>
<Input
value={formData.description}
onChange={(e) =>
setFormData((prev) => ({
...prev,
description: e.target.value,
}))
}
placeholder={translate(
'::App.Reports.TemplateEditor.Placeholder.Description',
)}
textArea={true}
rows={12}
className="text-left h-full"
/>
</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'content' && (
<div className="flex-1 flex flex-col min-h-0">
<div className="flex-1 p-3 min-h-0">
<ReportHtmlEditor
value={formData.htmlContent}
onChange={(content) =>
setFormData((prev) => ({ ...prev, htmlContent: content }))
}
height="50vh"
/>
</div>
</div>
)}
{activeTab === 'parameters' && (
<div className="overflow-y-auto flex-1 p-6">
<div className="max-w-full mx-auto">
{formData.parameters.length === 0 ? (
<div className="text-center py-12">
<FaCog className="h-16 w-16 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500 text-lg mb-2">
{translate('::App.Reports.TemplateEditor.NoParameters')}
</p>
<p className="text-gray-400 text-sm">
{translate('::App.Reports.TemplateEditor.NoParametersDescription')}
</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{formData.parameters.map((param) => (
<div
key={param.id}
className="bg-white p-3 rounded-lg border border-gray-200 shadow-sm hover:shadow-md transition-shadow min-w-0"
>
<div className="flex items-start justify-between mb-2 min-w-0">
<div className="flex items-center gap-1 min-w-0 flex-1">
<div className="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0"></div>
<code className="text-xs font-mono text-blue-700 bg-blue-50 px-1.5 py-0.5 rounded truncate">
@@{param.name}
</code>
</div>
<select
value={param.type}
onChange={(e) =>
updateParameter(param.id, { type: e.target.value as any })
}
className="text-xs bg-gray-100 text-gray-600 px-1.5 py-0.5 rounded-full flex-shrink-0 ml-1 border-none outline-none cursor-pointer"
>
<option value="text">text</option>
<option value="number">number</option>
<option value="date">date</option>
<option value="select">select</option>
<option value="checkbox">checkbox</option>
</select>
</div>
<div className="mb-2">
<input
type="text"
value={param.description || ''}
onChange={(e) =>
updateParameter(param.id, { description: e.target.value })
}
placeholder={translate(
'::App.Reports.TemplateEditor.Placeholder.ParameterDescription',
)}
className="w-full text-xs text-gray-600 bg-transparent border-none outline-none resize-none"
/>
</div>
<div className="mb-2">
<input
type="text"
value={param.defaultValue || ''}
onChange={(e) =>
updateParameter(param.id, { defaultValue: e.target.value })
}
placeholder={translate(
'::ListForms.ListFormEdit.ExtraDefaultValue',
)}
className="w-full text-xs bg-gray-50 px-1.5 py-0.5 rounded border border-gray-200 outline-none"
/>
</div>
<div className="flex items-center gap-2">
<label className="flex items-center gap-1 cursor-pointer">
<input
type="checkbox"
checked={param.required}
onChange={(e) =>
updateParameter(param.id, { required: e.target.checked })
}
className="w-3 h-3 text-red-600 rounded border-gray-300 focus:ring-red-500"
/>
<span className="text-xs text-gray-600">
{translate('::App.Required')}
</span>
</label>
</div>
</div>
))}
</div>
)}
</div>
</div>
)}
</div>
{/* Tab Footer */}
<div className="flex justify-between">
<Button variant="default" onClick={onClose} disabled={isSaving}>
{translate('::Cancel')}
</Button>
<Button
variant="solid"
onClick={handleSave}
className="bg-blue-600 hover:bg-blue-700 font-medium px-2 py-2 rounded-lg shadow-md hover:shadow-lg transition-all duration-200 flex items-center space-x-2"
>
<FaSave className="h-5 w-5" />
{isSaving
? translate('::App.Reports.TemplateEditor.Button.Saving')
: template
? translate('::Update')
: translate('::App.Reports.TemplateEditor.Button.Save')}
</Button>
</div>
</div>
</Dialog>
</>
)
}

View file

@ -1,22 +0,0 @@
import React from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
import { Dashboard } from '@/components/reports/Dashboard'
import { Container } from '@/components/shared'
const DashboardPage: React.FC = () => {
const { translate } = useLocalization()
return (
<Container>
<Helmet
titleTemplate="%s | Erp Platform"
title={translate('::' + 'App.Reports')}
defaultTitle="Erp Platform"
></Helmet>
<Dashboard />
</Container>
)
}
export default DashboardPage

View file

@ -1,22 +0,0 @@
import { ReportViewer } from '@/components/reports/ReportViewer'
import { Container } from '@/components/shared'
import { useLocalization } from '@/utils/hooks/useLocalization'
import React from 'react'
import { Helmet } from 'react-helmet'
const ReportViewerPage: React.FC = () => {
const { translate } = useLocalization()
return (
<Container>
<Helmet
titleTemplate="%s | Erp Platform"
title={translate('::' + 'App.Reports')}
defaultTitle="Erp Platform"
></Helmet>
<ReportViewer />
</Container>
)
}
export default ReportViewerPage