mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-12 10:45:54 +00:00
feat: add date filter for character activity
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
import { Dialog } from 'primereact/dialog';
|
import { Dialog } from 'primereact/dialog';
|
||||||
|
import { Menu } from 'primereact/menu';
|
||||||
|
import { MenuItem } from 'primereact/menuitem';
|
||||||
|
import { useState, useCallback, useRef, useMemo } from 'react';
|
||||||
import { CharacterActivityContent } from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/CharacterActivityContent.tsx';
|
import { CharacterActivityContent } from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/CharacterActivityContent.tsx';
|
||||||
|
|
||||||
interface CharacterActivityProps {
|
interface CharacterActivityProps {
|
||||||
@@ -6,17 +9,69 @@ interface CharacterActivityProps {
|
|||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const periodOptions = [
|
||||||
|
{ value: 30, label: '30 Days' },
|
||||||
|
{ value: 365, label: '1 Year' },
|
||||||
|
{ value: null, label: 'All Time' },
|
||||||
|
];
|
||||||
|
|
||||||
export const CharacterActivity = ({ visible, onHide }: CharacterActivityProps) => {
|
export const CharacterActivity = ({ visible, onHide }: CharacterActivityProps) => {
|
||||||
|
const [selectedPeriod, setSelectedPeriod] = useState<number | null>(30);
|
||||||
|
const menuRef = useRef<Menu>(null);
|
||||||
|
|
||||||
|
const handlePeriodChange = useCallback((days: number | null) => {
|
||||||
|
setSelectedPeriod(days);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const menuItems: MenuItem[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: 'Period',
|
||||||
|
items: periodOptions.map(option => ({
|
||||||
|
label: option.label,
|
||||||
|
icon: selectedPeriod === option.value ? 'pi pi-check' : undefined,
|
||||||
|
command: () => handlePeriodChange(option.value),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[selectedPeriod, handlePeriodChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedPeriodLabel = useMemo(
|
||||||
|
() => periodOptions.find(opt => opt.value === selectedPeriod)?.label || 'All Time',
|
||||||
|
[selectedPeriod],
|
||||||
|
);
|
||||||
|
|
||||||
|
const headerIcons = (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="p-dialog-header-icon p-link"
|
||||||
|
onClick={e => menuRef.current?.toggle(e)}
|
||||||
|
aria-label="Filter options"
|
||||||
|
>
|
||||||
|
<span className="pi pi-bars" />
|
||||||
|
</button>
|
||||||
|
<Menu model={menuItems} popup ref={menuRef} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
header="Character Activity"
|
header={
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>Character Activity</span>
|
||||||
|
<span className="text-xs text-stone-400">({selectedPeriodLabel})</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
className="w-[550px] max-h-[90vh]"
|
className="w-[550px] max-h-[90vh]"
|
||||||
onHide={onHide}
|
onHide={onHide}
|
||||||
dismissableMask
|
dismissableMask
|
||||||
contentClassName="p-0 h-full flex flex-col"
|
contentClassName="p-0 h-full flex flex-col"
|
||||||
|
icons={headerIcons}
|
||||||
>
|
>
|
||||||
<CharacterActivityContent />
|
<CharacterActivityContent selectedPeriod={selectedPeriod} />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,16 +7,28 @@ import {
|
|||||||
} from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/helpers.tsx';
|
} from '@/hooks/Mapper/components/mapRootContent/components/CharacterActivity/helpers.tsx';
|
||||||
import { Column } from 'primereact/column';
|
import { Column } from 'primereact/column';
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import { useMemo } from 'react';
|
import { useMemo, useEffect } from 'react';
|
||||||
|
import { useCharacterActivityHandlers } from '@/hooks/Mapper/components/mapRootContent/hooks/useCharacterActivityHandlers';
|
||||||
|
|
||||||
export const CharacterActivityContent = () => {
|
interface CharacterActivityContentProps {
|
||||||
|
selectedPeriod: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CharacterActivityContent = ({ selectedPeriod }: CharacterActivityContentProps) => {
|
||||||
const {
|
const {
|
||||||
data: { characterActivityData },
|
data: { characterActivityData },
|
||||||
} = useMapRootState();
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const { handleShowActivity } = useCharacterActivityHandlers();
|
||||||
|
|
||||||
const activity = useMemo(() => characterActivityData?.activity || [], [characterActivityData]);
|
const activity = useMemo(() => characterActivityData?.activity || [], [characterActivityData]);
|
||||||
const loading = useMemo(() => characterActivityData?.loading !== false, [characterActivityData]);
|
const loading = useMemo(() => characterActivityData?.loading !== false, [characterActivityData]);
|
||||||
|
|
||||||
|
// Reload activity data when period changes
|
||||||
|
useEffect(() => {
|
||||||
|
handleShowActivity(selectedPeriod);
|
||||||
|
}, [selectedPeriod, handleShowActivity]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-full w-full">
|
<div className="flex flex-col items-center justify-center h-full w-full">
|
||||||
|
|||||||
@@ -23,17 +23,17 @@ export const useCharacterActivityHandlers = () => {
|
|||||||
/**
|
/**
|
||||||
* Handle showing the character activity dialog
|
* Handle showing the character activity dialog
|
||||||
*/
|
*/
|
||||||
const handleShowActivity = useCallback(() => {
|
const handleShowActivity = useCallback((days?: number | null) => {
|
||||||
// Update local state to show the dialog
|
// Update local state to show the dialog
|
||||||
update(state => ({
|
update(state => ({
|
||||||
...state,
|
...state,
|
||||||
showCharacterActivity: true,
|
showCharacterActivity: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Send the command to the server
|
// Send the command to the server with optional days parameter
|
||||||
outCommand({
|
outCommand({
|
||||||
type: OutCommand.showActivity,
|
type: OutCommand.showActivity,
|
||||||
data: {},
|
data: days !== undefined ? { days } : {},
|
||||||
});
|
});
|
||||||
}, [outCommand, update]);
|
}, [outCommand, update]);
|
||||||
|
|
||||||
|
|||||||
@@ -68,4 +68,5 @@ export interface ActivitySummary {
|
|||||||
passages: number;
|
passages: number;
|
||||||
connections: number;
|
connections: number;
|
||||||
signatures: number;
|
signatures: number;
|
||||||
|
timestamp?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,13 +43,14 @@ defmodule WandererApp.Character.Activity do
|
|||||||
## Parameters
|
## Parameters
|
||||||
- `map_id`: ID of the map
|
- `map_id`: ID of the map
|
||||||
- `current_user`: Current user struct (used only to get user settings)
|
- `current_user`: Current user struct (used only to get user settings)
|
||||||
|
- `days`: Optional number of days to filter activity (nil for all time)
|
||||||
|
|
||||||
## Returns
|
## Returns
|
||||||
- List of processed activity data
|
- List of processed activity data
|
||||||
"""
|
"""
|
||||||
def process_character_activity(map_id, current_user) do
|
def process_character_activity(map_id, current_user, days \\ nil) do
|
||||||
with {:ok, map_user_settings} <- get_map_user_settings(map_id, current_user.id),
|
with {:ok, map_user_settings} <- get_map_user_settings(map_id, current_user.id),
|
||||||
{:ok, raw_activity} <- WandererApp.Map.get_character_activity(map_id),
|
{:ok, raw_activity} <- WandererApp.Map.get_character_activity(map_id, days),
|
||||||
{:ok, user_characters} <-
|
{:ok, user_characters} <-
|
||||||
WandererApp.Api.Character.active_by_user(%{user_id: current_user.id}) do
|
WandererApp.Api.Character.active_by_user(%{user_id: current_user.id}) do
|
||||||
process_activity_data(raw_activity, map_user_settings, user_characters)
|
process_activity_data(raw_activity, map_user_settings, user_characters)
|
||||||
|
|||||||
@@ -463,7 +463,8 @@ defmodule WandererApp.Esi.ApiClient do
|
|||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
# Check if this is a Finch pool error
|
# Check if this is a Finch pool error
|
||||||
if is_exception(reason) and Exception.message(reason) =~ "unable to provide a connection" do
|
if is_exception(reason) and
|
||||||
|
Exception.message(reason) =~ "unable to provide a connection" do
|
||||||
:telemetry.execute(
|
:telemetry.execute(
|
||||||
[:wanderer_app, :finch, :pool_exhausted],
|
[:wanderer_app, :finch, :pool_exhausted],
|
||||||
%{count: 1},
|
%{count: 1},
|
||||||
@@ -677,7 +678,8 @@ defmodule WandererApp.Esi.ApiClient do
|
|||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
# Check if this is a Finch pool error
|
# Check if this is a Finch pool error
|
||||||
if is_exception(reason) and Exception.message(reason) =~ "unable to provide a connection" do
|
if is_exception(reason) and
|
||||||
|
Exception.message(reason) =~ "unable to provide a connection" do
|
||||||
:telemetry.execute(
|
:telemetry.execute(
|
||||||
[:wanderer_app, :finch, :pool_exhausted],
|
[:wanderer_app, :finch, :pool_exhausted],
|
||||||
%{count: 1},
|
%{count: 1},
|
||||||
|
|||||||
@@ -30,14 +30,17 @@ defmodule WandererAppWeb.MapActivityEventHandler do
|
|||||||
|
|
||||||
def handle_ui_event(
|
def handle_ui_event(
|
||||||
"show_activity",
|
"show_activity",
|
||||||
_,
|
params,
|
||||||
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
|
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
|
||||||
) do
|
) do
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
try do
|
try do
|
||||||
|
# Extract days parameter (nil if not provided)
|
||||||
|
days = Map.get(params, "days")
|
||||||
|
|
||||||
# Get raw activity data from the domain logic
|
# Get raw activity data from the domain logic
|
||||||
result =
|
result =
|
||||||
WandererApp.Character.Activity.process_character_activity(map_id, current_user)
|
WandererApp.Character.Activity.process_character_activity(map_id, current_user, days)
|
||||||
|
|
||||||
# Group activities by user_id and summarize
|
# Group activities by user_id and summarize
|
||||||
summarized_result =
|
summarized_result =
|
||||||
|
|||||||
Reference in New Issue
Block a user