AuditLog
SubForms kısmında ListFormCode dahil edildi
This commit is contained in:
parent
119c3650f0
commit
1c472a7d9a
7 changed files with 233 additions and 65 deletions
|
|
@ -0,0 +1,9 @@
|
||||||
|
using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform.AuditLogs;
|
||||||
|
|
||||||
|
public class AuditLogListRequestDto : PagedAndSortedResultRequestDto
|
||||||
|
{
|
||||||
|
public string ListFormCode { get; set; }
|
||||||
|
public string EntityId { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -1,30 +1,43 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Sozsoft.Platform.ListForms;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.AuditLogging;
|
using Volo.Abp.AuditLogging;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Uow;
|
using Volo.Abp.Uow;
|
||||||
using static Sozsoft.Platform.Data.Seeds.SeedConsts;
|
using static Sozsoft.Platform.Data.Seeds.SeedConsts;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.AuditLogs;
|
namespace Sozsoft.Platform.AuditLogs;
|
||||||
|
|
||||||
public interface IAuditLogAppService
|
public interface IAuditLogAppService
|
||||||
: ICrudAppService<AuditLogDto, Guid>
|
: ICrudAppService<AuditLogDto, Guid, AuditLogListRequestDto>
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(AppCodes.IdentityManagement.AuditLogs)]
|
[Authorize(AppCodes.IdentityManagement.AuditLogs)]
|
||||||
public class AuditLogAppService
|
public class AuditLogAppService : CrudAppService<
|
||||||
: CrudAppService<AuditLog, AuditLogDto, Guid>
|
AuditLog,
|
||||||
, IAuditLogAppService
|
AuditLogDto,
|
||||||
|
Guid,
|
||||||
|
AuditLogListRequestDto>, IAuditLogAppService
|
||||||
{
|
{
|
||||||
public AuditLogAppService(IAuditLogRepository auditLogRepository) : base(auditLogRepository)
|
private readonly IRepository<ListForm, Guid> _listFormRepository;
|
||||||
|
|
||||||
|
public AuditLogAppService(
|
||||||
|
IAuditLogRepository auditLogRepository,
|
||||||
|
IRepository<ListForm, Guid> listFormRepository
|
||||||
|
) : base(auditLogRepository)
|
||||||
{
|
{
|
||||||
|
_listFormRepository = listFormRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<AuditLogDto> GetAsync(Guid id)
|
public override async Task<AuditLogDto> GetAsync(Guid id)
|
||||||
|
|
@ -35,27 +48,30 @@ public class AuditLogAppService
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnitOfWork]
|
[UnitOfWork]
|
||||||
public override async Task<PagedResultDto<AuditLogDto>> GetListAsync(PagedAndSortedResultRequestDto input)
|
public override async Task<PagedResultDto<AuditLogDto>> GetListAsync(AuditLogListRequestDto input)
|
||||||
{
|
{
|
||||||
var query = await CreateFilteredQueryAsync(input);
|
var query = await Repository.WithDetailsAsync();
|
||||||
|
|
||||||
|
if (!input.ListFormCode.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var filterRules = await GetListFormFilterRulesAsync(input.ListFormCode);
|
||||||
|
query = ApplyAuditLogActionParametersFilter(query, filterRules, input.EntityId);
|
||||||
|
}
|
||||||
|
else if (!input.EntityId.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
query = query.Where(a => a.Actions.Any(action => action.Parameters.Contains(input.EntityId)));
|
||||||
|
}
|
||||||
|
|
||||||
var totalCount = await AsyncExecuter.CountAsync(query);
|
var totalCount = await AsyncExecuter.CountAsync(query);
|
||||||
|
|
||||||
query = ApplySorting(query, input);
|
query = ApplySorting(query, input);
|
||||||
query = ApplyPaging(query, input);
|
query = ApplyPaging(query, input);
|
||||||
|
|
||||||
// EntityChanges ile birlikte getir (N+1 query önlenir)
|
var auditLogsWithDetails = await AsyncExecuter.ToListAsync(query);
|
||||||
var auditLogRepository = (IAuditLogRepository)Repository;
|
|
||||||
var auditLogsWithDetails = await auditLogRepository.GetListAsync(
|
|
||||||
sorting: input.Sorting,
|
|
||||||
maxResultCount: input.MaxResultCount,
|
|
||||||
skipCount: input.SkipCount,
|
|
||||||
includeDetails: true
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mapping tek seferde yap
|
// Mapping tek seferde yap
|
||||||
var entityDtos = ObjectMapper.Map<List<AuditLog>, List<AuditLogDto>>(auditLogsWithDetails);
|
var entityDtos = ObjectMapper.Map<List<AuditLog>, List<AuditLogDto>>(auditLogsWithDetails);
|
||||||
|
|
||||||
// EntityChangeCount'u doldur (artık EntityChanges yüklü)
|
// EntityChangeCount'u doldur (artık EntityChanges yüklü)
|
||||||
foreach (var dto in entityDtos)
|
foreach (var dto in entityDtos)
|
||||||
{
|
{
|
||||||
|
|
@ -69,6 +85,102 @@ public class AuditLogAppService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<AuditLogListFormFilterRule>> GetListFormFilterRulesAsync(string listFormCode)
|
||||||
|
{
|
||||||
|
var rules = new List<AuditLogListFormFilterRule>
|
||||||
|
{
|
||||||
|
new(listFormCode, null)
|
||||||
|
};
|
||||||
|
|
||||||
|
var listForm = await _listFormRepository.FirstOrDefaultAsync(a => a.ListFormCode == listFormCode);
|
||||||
|
if (listForm?.SubFormsJson.IsNullOrWhiteSpace() != false)
|
||||||
|
{
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var subForms = JsonSerializer.Deserialize<List<SubFormDto>>(listForm.SubFormsJson) ?? [];
|
||||||
|
foreach (var subForm in subForms.Where(a => !a.Code.IsNullOrWhiteSpace()))
|
||||||
|
{
|
||||||
|
var childFieldNames = subForm.Relation?
|
||||||
|
.Select(a => a.ChildFieldName)
|
||||||
|
.Where(a => !a.IsNullOrWhiteSpace())
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList() ?? [];
|
||||||
|
|
||||||
|
rules.AddRange(childFieldNames.Select(childFieldName => new AuditLogListFormFilterRule(subForm.Code, childFieldName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
// Invalid subform JSON should not block audit log listing for the main form.
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules
|
||||||
|
.DistinctBy(a => $"{a.ListFormCode}|{a.ChildFieldName}".ToLowerInvariant())
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IQueryable<AuditLog> ApplyAuditLogActionParametersFilter(
|
||||||
|
IQueryable<AuditLog> query,
|
||||||
|
List<AuditLogListFormFilterRule> rules,
|
||||||
|
string entityId)
|
||||||
|
{
|
||||||
|
var validRules = rules
|
||||||
|
.Where(rule => !rule.ListFormCode.IsNullOrWhiteSpace())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (validRules.Count == 0)
|
||||||
|
{
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
var auditLog = Expression.Parameter(typeof(AuditLog), "auditLog");
|
||||||
|
var action = Expression.Parameter(typeof(AuditLogAction), "action");
|
||||||
|
var parameters = Expression.Property(action, nameof(AuditLogAction.Parameters));
|
||||||
|
|
||||||
|
Expression actionBody = Expression.Constant(false);
|
||||||
|
foreach (var rule in validRules)
|
||||||
|
{
|
||||||
|
Expression ruleBody = Contains(parameters, rule.ListFormCode);
|
||||||
|
if (!entityId.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
ruleBody = Expression.AndAlso(ruleBody, Contains(parameters, entityId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rule.ChildFieldName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
ruleBody = Expression.AndAlso(ruleBody, Contains(parameters, rule.ChildFieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
actionBody = Expression.OrElse(actionBody, ruleBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
var actions = Expression.Property(auditLog, nameof(AuditLog.Actions));
|
||||||
|
var actionPredicate = Expression.Lambda<Func<AuditLogAction, bool>>(actionBody, action);
|
||||||
|
var anyCall = Expression.Call(
|
||||||
|
typeof(Enumerable),
|
||||||
|
nameof(Enumerable.Any),
|
||||||
|
[typeof(AuditLogAction)],
|
||||||
|
actions,
|
||||||
|
actionPredicate);
|
||||||
|
|
||||||
|
var auditLogPredicate = Expression.Lambda<Func<AuditLog, bool>>(anyCall, auditLog);
|
||||||
|
return query.Where(auditLogPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodCallExpression Contains(MemberExpression source, string value)
|
||||||
|
{
|
||||||
|
return Expression.Call(
|
||||||
|
source,
|
||||||
|
nameof(string.Contains),
|
||||||
|
Type.EmptyTypes,
|
||||||
|
Expression.Constant(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record AuditLogListFormFilterRule(string ListFormCode, string? ChildFieldName);
|
||||||
|
|
||||||
// Audit Log kayitlarini gormek istiyoruz fakat degistirmek istemiyoruz
|
// Audit Log kayitlarini gormek istiyoruz fakat degistirmek istemiyoruz
|
||||||
[RemoteService(IsEnabled = false)]
|
[RemoteService(IsEnabled = false)]
|
||||||
public override Task<AuditLogDto> CreateAsync(AuditLogDto input)
|
public override Task<AuditLogDto> CreateAsync(AuditLogDto input)
|
||||||
|
|
|
||||||
|
|
@ -2302,7 +2302,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, false, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 550, true, true, true, false, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new() {
|
new() {
|
||||||
Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
|
Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
|
||||||
|
|
|
||||||
24
ui/src/services/auditLog.service.ts
Normal file
24
ui/src/services/auditLog.service.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { PagedResultDto } from '@/proxy'
|
||||||
|
import { AuditLogDto } from '@/proxy/auditLog/audit-log'
|
||||||
|
import apiService from '@/services/api.service'
|
||||||
|
|
||||||
|
export interface AuditLogListRequestDto {
|
||||||
|
skipCount?: number
|
||||||
|
maxResultCount?: number
|
||||||
|
sorting?: string
|
||||||
|
listFormCode?: string
|
||||||
|
entityId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuditLogService {
|
||||||
|
async getList(params?: AuditLogListRequestDto): Promise<PagedResultDto<AuditLogDto>> {
|
||||||
|
const response = await apiService.fetchData<PagedResultDto<AuditLogDto>>({
|
||||||
|
url: '/api/app/audit-log',
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const auditLogService = new AuditLogService()
|
||||||
|
|
@ -17,16 +17,17 @@ import TabNav from '@/components/ui/Tabs/TabNav'
|
||||||
import { NoteDto } from '@/proxy/note/models'
|
import { NoteDto } from '@/proxy/note/models'
|
||||||
import { AVATAR_URL } from '@/constants/app.constant'
|
import { AVATAR_URL } from '@/constants/app.constant'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
import apiService from '@/services/api.service'
|
|
||||||
import { PagedResultDto } from '@/proxy'
|
|
||||||
import { AuditLogActionDto, AuditLogDto } from '@/proxy/auditLog/audit-log'
|
import { AuditLogActionDto, AuditLogDto } from '@/proxy/auditLog/audit-log'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import { auditLogService } from '@/services/auditLog.service'
|
||||||
|
|
||||||
interface NoteListProps {
|
interface NoteListProps {
|
||||||
notes: NoteDto[]
|
notes: NoteDto[]
|
||||||
entityName: string
|
entityName: string
|
||||||
entityId: string
|
entityId: string
|
||||||
|
isVisible?: boolean
|
||||||
onAddNote?: () => void
|
onAddNote?: () => void
|
||||||
|
onRefreshNotes?: () => void
|
||||||
onDeleteNote?: (noteId: string) => void
|
onDeleteNote?: (noteId: string) => void
|
||||||
onDownloadFile?: (fileData: any) => void
|
onDownloadFile?: (fileData: any) => void
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +36,9 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
notes,
|
notes,
|
||||||
entityName,
|
entityName,
|
||||||
entityId,
|
entityId,
|
||||||
|
isVisible = true,
|
||||||
onAddNote,
|
onAddNote,
|
||||||
|
onRefreshNotes,
|
||||||
onDeleteNote,
|
onDeleteNote,
|
||||||
onDownloadFile,
|
onDownloadFile,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
@ -215,7 +218,18 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
const getRowLabelIfMatches = (input: any): string | null => {
|
const getRowLabelIfMatches = (input: any): string | null => {
|
||||||
if (!entityIdNormalized) return null
|
if (!entityIdNormalized) return null
|
||||||
const inputFormCode = normalize(input?.listFormCode)
|
const inputFormCode = normalize(input?.listFormCode)
|
||||||
if (!inputFormCode || inputFormCode !== listFormCodeNormalized) return null
|
if (!inputFormCode) return null
|
||||||
|
const data = input?.data
|
||||||
|
const isMainListForm = inputFormCode === listFormCodeNormalized
|
||||||
|
|
||||||
|
if (!isMainListForm) {
|
||||||
|
if (!data || typeof data !== 'object') return null
|
||||||
|
const hit = findMatchingValueInData(data, entityIdNormalized)
|
||||||
|
if (!hit) return null
|
||||||
|
|
||||||
|
const nameValue = (data as any)?.Name ?? (data as any)?.name
|
||||||
|
return nameValue ? String(nameValue) : String(hit.value)
|
||||||
|
}
|
||||||
|
|
||||||
const keys = getKeysFromInput(input)
|
const keys = getKeysFromInput(input)
|
||||||
.map((k) => normalize(k))
|
.map((k) => normalize(k))
|
||||||
|
|
@ -227,7 +241,6 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some entities may use a different PK than the visible row key; allow strict match via input.data too.
|
// Some entities may use a different PK than the visible row key; allow strict match via input.data too.
|
||||||
const data = input?.data
|
|
||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
const hit = findMatchingValueInData(data, entityIdNormalized)
|
const hit = findMatchingValueInData(data, entityIdNormalized)
|
||||||
if (hit) {
|
if (hit) {
|
||||||
|
|
@ -240,7 +253,6 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert: keys is null, match by scanning input.data for entity id/name/code/etc.
|
// insert: keys is null, match by scanning input.data for entity id/name/code/etc.
|
||||||
const data = input?.data
|
|
||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
const hit = findMatchingValueInData(data, entityIdNormalized)
|
const hit = findMatchingValueInData(data, entityIdNormalized)
|
||||||
if (!hit) return null
|
if (!hit) return null
|
||||||
|
|
@ -277,17 +289,15 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
setAuditLoading(true)
|
setAuditLoading(true)
|
||||||
setAuditError(null)
|
setAuditError(null)
|
||||||
try {
|
try {
|
||||||
const response = await apiService.fetchData<PagedResultDto<AuditLogDto>>({
|
const response = await auditLogService.getList({
|
||||||
method: 'GET',
|
skipCount: 0,
|
||||||
url: '/api/app/audit-log',
|
maxResultCount: 200,
|
||||||
params: {
|
sorting: 'ExecutionTime DESC',
|
||||||
skipCount: 0,
|
listFormCode: entityName,
|
||||||
maxResultCount: 200,
|
entityId,
|
||||||
sorting: 'ExecutionTime DESC',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const items = response.data?.items ?? []
|
const items = response?.items ?? []
|
||||||
const filtered = items
|
const filtered = items
|
||||||
.map((log) => ({ log, matchedActions: buildMatchedActions(log) }))
|
.map((log) => ({ log, matchedActions: buildMatchedActions(log) }))
|
||||||
.filter((x) => x.matchedActions.length > 0)
|
.filter((x) => x.matchedActions.length > 0)
|
||||||
|
|
@ -305,13 +315,13 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentTab !== 'audit') return
|
if (!isVisible) return
|
||||||
if (!listFormCodeNormalized && !entityIdNormalized) return
|
if (!listFormCodeNormalized && !entityIdNormalized) return
|
||||||
const key = `${listFormCodeNormalized}|${entityIdNormalized}`
|
const key = `${listFormCodeNormalized}|${entityIdNormalized}`
|
||||||
if (auditLoadedKey === key) return
|
if (auditLoadedKey === key) return
|
||||||
loadAuditLogs()
|
loadAuditLogs()
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentTab, listFormCodeNormalized, entityIdNormalized])
|
}, [isVisible, listFormCodeNormalized, entityIdNormalized])
|
||||||
|
|
||||||
const getStatusBadge = (statusCode?: number) => {
|
const getStatusBadge = (statusCode?: number) => {
|
||||||
if (!statusCode) return <Badge className="bg-gray-500" content="?" />
|
if (!statusCode) return <Badge className="bg-gray-500" content="?" />
|
||||||
|
|
@ -330,7 +340,7 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
onChange={(val) => setCurrentTab(val as 'notes' | 'audit')}
|
onChange={(val) => setCurrentTab(val as 'notes' | 'audit')}
|
||||||
variant="underline"
|
variant="underline"
|
||||||
>
|
>
|
||||||
<TabList className="mb-4 border-0 dark:bg-gray-800">
|
<TabList className="mb-2 border-0 dark:bg-gray-800">
|
||||||
<TabNav value="notes">
|
<TabNav value="notes">
|
||||||
{translate('::ListForms.ListForm.Notes')}
|
{translate('::ListForms.ListForm.Notes')}
|
||||||
<Badge className="ml-2 bg-blue-500" content={`${notes?.length ?? 0}`} />
|
<Badge className="ml-2 bg-blue-500" content={`${notes?.length ?? 0}`} />
|
||||||
|
|
@ -341,21 +351,48 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
</TabNav>
|
</TabNav>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabContent value="notes">
|
<div className="mb-2 flex min-h-10 items-center justify-end border-y border-gray-200 bg-gray-50 px-1 py-1 dark:border-gray-700 dark:bg-gray-900">
|
||||||
<div className="flex items-center justify-end mb-2">
|
{currentTab === 'notes' ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="xs"
|
||||||
|
icon={<FaPlus />}
|
||||||
|
type="button"
|
||||||
|
onClick={onAddNote}
|
||||||
|
disabled={!onAddNote}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
{translate('::ListForms.ListForm.AddNote')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="default"
|
||||||
|
type="button"
|
||||||
|
icon={<FaSyncAlt />}
|
||||||
|
onClick={onRefreshNotes}
|
||||||
|
disabled={!onRefreshNotes}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
{translate('::ListForms.ListForm.Refresh')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
|
size="xs"
|
||||||
variant="default"
|
variant="default"
|
||||||
size="sm"
|
|
||||||
icon={<FaPlus className="mr-1" />}
|
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onAddNote}
|
icon={<FaSyncAlt />}
|
||||||
disabled={!onAddNote}
|
onClick={loadAuditLogs}
|
||||||
|
disabled={auditLoading}
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
>
|
>
|
||||||
{translate('::ListForms.ListForm.AddNote')}
|
{translate('::ListForms.ListForm.Refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TabContent value="notes">
|
||||||
{(notes?.length ?? 0) === 0 ? (
|
{(notes?.length ?? 0) === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-32 text-gray-500">
|
<div className="flex flex-col items-center justify-center h-32 text-gray-500">
|
||||||
<FaStickyNote className="text-4xl mb-2 opacity-50" />
|
<FaStickyNote className="text-4xl mb-2 opacity-50" />
|
||||||
|
|
@ -449,20 +486,6 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|
||||||
<TabContent value="audit">
|
<TabContent value="audit">
|
||||||
<div className="flex items-center justify-end mb-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="default"
|
|
||||||
type="button"
|
|
||||||
icon={<FaSyncAlt className="mr-1" />}
|
|
||||||
onClick={loadAuditLogs}
|
|
||||||
disabled={auditLoading}
|
|
||||||
className="flex items-center"
|
|
||||||
>
|
|
||||||
{translate('::ListForms.ListForm.Refresh')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{auditLoading ? (
|
{auditLoading ? (
|
||||||
<div className="flex items-center justify-center py-10">
|
<div className="flex items-center justify-center py-10">
|
||||||
<Spinner size={32} />
|
<Spinner size={32} />
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ function NoteModalContent({
|
||||||
{/* Başlık */}
|
{/* Başlık */}
|
||||||
<div className="flex items-center justify-between mb-5 flex-shrink-0">
|
<div className="flex items-center justify-between mb-5 flex-shrink-0">
|
||||||
<h3 className="text-xl font-semibold flex items-center gap-3">
|
<h3 className="text-xl font-semibold flex items-center gap-3">
|
||||||
<div className="p-2 bg-purple-100 rounded-full">
|
<div className="p-1 bg-purple-100 rounded-full">
|
||||||
<FaPlus className="text-purple-600 text-lg" />
|
<FaPlus className="text-purple-600 text-lg" />
|
||||||
</div>
|
</div>
|
||||||
{translate('::ListForms.ListForm.AddNote')}
|
{translate('::ListForms.ListForm.AddNote')}
|
||||||
|
|
@ -116,7 +116,7 @@ function NoteModalContent({
|
||||||
{types.map((t) => (
|
{types.map((t) => (
|
||||||
<label
|
<label
|
||||||
key={t.value}
|
key={t.value}
|
||||||
className="flex items-center gap-2 px-2 py-1 text-black rounded-md cursor-pointer transition-all duration-200 dark:bg-gray-800 dark:text-gray-300"
|
className="flex items-center gap-2 px-1 py-1 text-black rounded-md cursor-pointer transition-all duration-200 dark:bg-gray-800 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
<Radio
|
<Radio
|
||||||
value={t.value}
|
value={t.value}
|
||||||
|
|
@ -139,8 +139,9 @@ function NoteModalContent({
|
||||||
<Field
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
name="subject"
|
name="subject"
|
||||||
as={Input}
|
|
||||||
placeholder={translate('::ListForms.ListForm.NoteModal.Subject')}
|
placeholder={translate('::ListForms.ListForm.NoteModal.Subject')}
|
||||||
|
component={Input}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -198,7 +199,7 @@ function NoteModalContent({
|
||||||
|
|
||||||
{/* DOSYA YÜKLEME */}
|
{/* DOSYA YÜKLEME */}
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-3 text-center hover:border-purple-400 transition-colors duration-200">
|
<div className="border-2 border-dashed border-gray-300 rounded-lg p-2 text-center hover:border-purple-400 transition-colors duration-200">
|
||||||
<Upload
|
<Upload
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
showList={false}
|
showList={false}
|
||||||
|
|
@ -255,7 +256,7 @@ function NoteModalContent({
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
|
|
||||||
{/* ALT BUTONLAR */}
|
{/* ALT BUTONLAR */}
|
||||||
<Dialog.Footer className="mt-5 flex justify-between items-center pt-4 border-t border-gray-200">
|
<Dialog.Footer className="mt-2 flex justify-between items-center pt-4 border-t border-gray-200">
|
||||||
<Button variant="default" size="md" onClick={onClose} disabled={uploading}>
|
<Button variant="default" size="md" onClick={onClose} disabled={uploading}>
|
||||||
{translate('::Cancel')}
|
{translate('::Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isVisible) fetchActivities()
|
if (isVisible) fetchActivities()
|
||||||
}, [isVisible])
|
}, [isVisible, entityName, entityId])
|
||||||
|
|
||||||
const handleDownloadFile = async (fileData: any) => {
|
const handleDownloadFile = async (fileData: any) => {
|
||||||
if (!fileData?.SavedFileName) return
|
if (!fileData?.SavedFileName) return
|
||||||
|
|
@ -62,8 +62,6 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTotalCount = () => activities.length
|
|
||||||
|
|
||||||
// Draggable button handlers
|
// Draggable button handlers
|
||||||
const handleMouseDown = (e: React.MouseEvent) => {
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
if (!buttonRef.current) return
|
if (!buttonRef.current) return
|
||||||
|
|
@ -134,7 +132,6 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isVisible ? <FaChevronRight /> : <FaChevronLeft />}
|
{isVisible ? <FaChevronRight /> : <FaChevronLeft />}
|
||||||
{getTotalCount() > 0 && <Badge content={getTotalCount()} />}
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -187,7 +184,9 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
notes={activities}
|
notes={activities}
|
||||||
entityName={entityName}
|
entityName={entityName}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
isVisible={isVisible}
|
||||||
onAddNote={() => setShowAddModal(true)}
|
onAddNote={() => setShowAddModal(true)}
|
||||||
|
onRefreshNotes={fetchActivities}
|
||||||
onDeleteNote={handleDeleteActivity}
|
onDeleteNote={handleDeleteActivity}
|
||||||
onDownloadFile={handleDownloadFile}
|
onDownloadFile={handleDownloadFile}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue