Статистика для дашборда, расширенная #7

This commit is contained in:
2026-04-28 23:04:17 +03:00
parent c87d56bb49
commit 3da4ee28d4
11 changed files with 261 additions and 70 deletions
+19 -14
View File
@@ -4,20 +4,25 @@
update_role/2, block/1, unblock/1, generate_id/0]).
create(Email, Password, Role) ->
Id = generate_id(),
{ok, Hash} = argon2:hash(Password),
Now = calendar:universal_time(),
Admin = #admin{
id = Id,
email = Email,
password_hash = Hash,
role = Role,
status = active,
created_at = Now,
updated_at = Now
},
mnesia:dirty_write(Admin),
{ok, Admin}.
case get_by_email(Email) of
{ok, _} ->
{error, email_exists};
{error, not_found} ->
Id = generate_id(),
{ok, Hash} = argon2:hash(Password),
Now = calendar:universal_time(),
Admin = #admin{
id = Id,
email = Email,
password_hash = Hash,
role = Role,
status = active,
created_at = Now,
updated_at = Now
},
mnesia:dirty_write(Admin),
{ok, Admin}
end.
get_by_email(Email) ->
Match = #admin{email = Email, _ = '_'},
+28 -9
View File
@@ -1,6 +1,7 @@
-module(core_admin_audit).
-include("records.hrl").
-export([log/7, list/0, list/1]).
-export([count_actions_by_admin/2]).
log(AdminId, Email, Role, Action, EntityType, EntityId, Ip) ->
log(AdminId, Email, Role, Action, EntityType, EntityId, Ip, undefined).
@@ -26,15 +27,33 @@ list() ->
%% Фильтрация по параметрам (простая версия)
list(Filters) ->
All = list(),
All = list(), % все записи
lists:filter(fun(E) ->
% Фильтр по admin_id
case proplists:get_value(admin_id, Filters) of
undefined -> true;
Id -> E#admin_audit.admin_id =:= Id
end andalso
case proplists:get_value(action, Filters) of
undefined -> true;
Act -> E#admin_audit.action =:= Act
end
% можно добавить фильтр по дате и т.д.
end, All).
Id -> E#admin_audit.admin_id =:= Id
end
andalso
% Фильтр по action
case proplists:get_value(action, Filters) of
undefined -> true;
Act -> E#admin_audit.action =:= Act
end
andalso
% Фильтр по дате с
case proplists:get_value(date_from, Filters) of
undefined -> true;
From -> E#admin_audit.timestamp >= From
end
andalso
% Фильтр по дате по
case proplists:get_value(date_to, Filters) of
undefined -> true;
To -> E#admin_audit.timestamp =< To
end
end, All).
count_actions_by_admin(AdminId, Action) ->
Match = #admin_audit{admin_id = AdminId, action = Action, _ = '_'},
length(mnesia:dirty_match_object(Match)).
+17 -1
View File
@@ -4,7 +4,7 @@
-export([create/4, create_recurring/5, get_by_id/1, list_by_calendar/1,
update/2, delete/1, materialize_occurrence/3]).
-export([generate_id/0]).
-export([count_events/0]).
-export([count_events/0, count_events_by_date/2]).
%% Создание одиночного события
create(CalendarId, Title, StartTime, Duration) ->
@@ -171,6 +171,22 @@ delete(Id) ->
count_events() ->
mnesia:table_info(event, size).
count_events_by_date(From, To) ->
All = mnesia:dirty_match_object(#event{_ = '_'}),
Filtered = lists:filter(fun(E) ->
E#event.created_at >= From andalso E#event.created_at =< To
end, All),
Counts = lists:foldl(fun(E, Acc) ->
Day = date_part(E#event.created_at),
case lists:keyfind(Day, 1, Acc) of
false -> [{Day, 1} | Acc];
{Day, C} -> lists:keyreplace(Day, 1, Acc, {Day, C+1})
end
end, [], Filtered),
lists:sort(Counts).
date_part({{Y,M,D}, _}) -> {Y,M,D}.
%% Внутренние функции
generate_id() ->
base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}).
+17
View File
@@ -5,6 +5,7 @@
-export([update_status/3, get_count_by_target/2]).
-export([generate_id/0]).
-export([count_reports_by_status/1, count_reports_by_admin/2]).
-export([count_reports_resolved_by_admin/2, avg_resolution_time/1]).
%% Создание жалобы
create(ReporterId, TargetType, TargetId, Reason) ->
@@ -92,6 +93,22 @@ count_reports_by_admin(AdminId, Status) ->
Match = #report{resolved_by = AdminId, status = Status, _ = '_'},
length(mnesia:dirty_match_object(Match)).
count_reports_resolved_by_admin(AdminId, Status) ->
Match = #report{resolved_by = AdminId, status = Status, _ = '_'},
length(mnesia:dirty_match_object(Match)).
avg_resolution_time(Status) ->
Match = #report{status = Status, _ = '_'},
Reports = mnesia:dirty_match_object(Match),
Resolved = lists:filter(fun(R) -> R#report.resolved_at =/= undefined end, Reports),
case Resolved of
[] -> 0;
_ ->
TotalSeconds = lists:sum([calendar:datetime_to_gregorian_seconds(R#report.resolved_at) -
calendar:datetime_to_gregorian_seconds(R#report.created_at) || R <- Resolved]),
TotalSeconds / length(Resolved) / 3600.0
end.
%% Внутренние функции
generate_id() ->
base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}).
+23 -12
View File
@@ -8,6 +8,7 @@
create_ticket/1,
list_by_user/1]).
-export([count_tickets_by_status/1, count_tickets_by_admin/2]).
-export([avg_resolution_time/0]).
list_all() ->
mnesia:dirty_match_object(#ticket{_ = '_'}).
@@ -69,6 +70,27 @@ create_ticket(Data) ->
list_by_user(UserId) ->
mnesia:dirty_match_object(#ticket{reporter_id = UserId, _ = '_'}).
count_by_status(Status, Tickets) ->
length([T || T <- Tickets, T#ticket.status =:= Status]).
count_tickets_by_status(Status) ->
Match = #ticket{status = Status, _ = '_'},
length(mnesia:dirty_match_object(Match)).
count_tickets_by_admin(AdminId, Status) ->
Match = #ticket{assigned_to = AdminId, status = Status, _ = '_'},
length(mnesia:dirty_match_object(Match)).
avg_resolution_time() ->
Tickets = mnesia:dirty_match_object(#ticket{status = closed, _ = '_'}),
case Tickets of
[] -> 0;
_ ->
TotalSeconds = lists:sum([calendar:datetime_to_gregorian_seconds(T#ticket.last_seen) -
calendar:datetime_to_gregorian_seconds(T#ticket.first_seen) || T <- Tickets]),
TotalSeconds / length(Tickets) / 3600.0
end.
%% ── внутренние ─────────────────────────────────────────
apply_updates(Ticket, Updates) ->
lists:foldl(fun({Key, Value}, Acc) ->
@@ -81,15 +103,4 @@ apply_updates(Ticket, Updates) ->
<<"context">> -> Acc#ticket{context = Value};
_ -> Acc
end
end, Ticket, maps:to_list(Updates)).
count_by_status(Status, Tickets) ->
length([T || T <- Tickets, T#ticket.status =:= Status]).
count_tickets_by_status(Status) ->
Match = #ticket{status = Status, _ = '_'},
length(mnesia:dirty_match_object(Match)).
count_tickets_by_admin(AdminId, Status) ->
Match = #ticket{assigned_to = AdminId, status = Status, _ = '_'},
length(mnesia:dirty_match_object(Match)).
end, Ticket, maps:to_list(Updates)).
+17 -1
View File
@@ -6,7 +6,7 @@
-export([generate_id/0]).
-export([list_users/0]).
-export([block/1, unblock/1]).
-export([count_users/0]).
-export([count_users/0, count_users_by_date/2]).
%% Создание пользователя
create(Email, Password) ->
@@ -126,6 +126,22 @@ unblock(Id) ->
count_users() ->
mnesia:table_info(user, size).
count_users_by_date(From, To) ->
All = mnesia:dirty_match_object(#user{_ = '_'}),
Filtered = lists:filter(fun(U) ->
U#user.created_at >= From andalso U#user.created_at =< To
end, All),
Counts = lists:foldl(fun(U, Acc) ->
Day = date_part(U#user.created_at),
case lists:keyfind(Day, 1, Acc) of
false -> [{Day, 1} | Acc];
{Day, C} -> lists:keyreplace(Day, 1, Acc, {Day, C+1})
end
end, [], Filtered),
lists:sort(Counts).
date_part({{Y,M,D}, _}) -> {Y,M,D}.
%% Внутренние функции
generate_id() ->
base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}).