diff --git a/src/statistics/FancyGraphCard.tsx b/src/statistics/FancyGraphCard.tsx index ef168345d..a240225cd 100644 --- a/src/statistics/FancyGraphCard.tsx +++ b/src/statistics/FancyGraphCard.tsx @@ -20,9 +20,9 @@ import { datasets } from "./datasets.tsx"; import { formatDate } from "../utils/formatDate.ts"; import DatasetPickerCard from "./components/DatasetPickerCard.tsx"; import { getFirstOfMonth } from "./utils.tsx"; -import DateRangePickerCard from "./components/DateRangePickerCard.tsx"; import TapirButton from "../components/TapirButton.tsx"; import { Download } from "react-bootstrap-icons"; +import DateRangePicker from "./components/DateRangePicker.tsx"; declare let gettext: (english_text: string) => string; @@ -58,6 +58,7 @@ const FancyGraphCard: React.FC = () => { const [graphLabels, setGraphLabels] = useState([]); const [dates, setDates] = useState([]); const [fetching, setFetching] = useState(false); + const [datapickerExpanded, setDatapickerExpanded] = useState(false); const cachedData = useRef({}); const api = useApi(StatisticsApi); @@ -228,24 +229,6 @@ const FancyGraphCard: React.FC = () => { return ( <> - - - - - - - - - - {error && ( @@ -254,6 +237,14 @@ const FancyGraphCard: React.FC = () => { )} + + + {
{gettext("Graph")} {fetching && }
- + + + +
string; interface DatasetPickerCardProps { enabledDatasetsRef: MutableRefObject>; setEnabledDatasets: (set: Set) => void; + isExpanded: boolean; + setIsExpanded: (isExpanded: boolean) => void; } const DatasetPickerCard: React.FC = ({ enabledDatasetsRef, setEnabledDatasets, + isExpanded, + setIsExpanded, }) => { return ( - +
{gettext("Pick which data to display")}
+ setIsExpanded(!isExpanded)} + />
- +
- + {isExpanded && } @@ -38,10 +54,13 @@ const DatasetPickerCard: React.FC = ({ return ( - - - - + {isExpanded && ( + + )} ); })} diff --git a/src/statistics/components/DateRangePicker.tsx b/src/statistics/components/DateRangePicker.tsx new file mode 100644 index 000000000..61a9840b0 --- /dev/null +++ b/src/statistics/components/DateRangePicker.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { FloatingLabel, Form } from "react-bootstrap"; +import { getFirstOfMonth } from "../utils.tsx"; + +declare let gettext: (english_text: string) => string; + +interface DateRangePickerProps { + dateFrom: Date; + setDateFrom: (date: Date) => void; + dateTo: Date; + setDateTo: (date: Date) => void; +} + +const DateRangePicker: React.FC = ({ + dateFrom, + setDateFrom, + dateTo, + setDateTo, +}) => { + return ( + <> + + + { + setDateFrom(getFirstOfMonth(new Date(event.target.value))); + }} + /> + + + + + { + setDateTo(getFirstOfMonth(new Date(event.target.value))); + }} + /> + + + + ); +}; + +export default DateRangePicker; diff --git a/src/statistics/fancy_graph_entry.tsx b/src/statistics/fancy_graph_entry.tsx index c8ff6ec26..5845ff56f 100644 --- a/src/statistics/fancy_graph_entry.tsx +++ b/src/statistics/fancy_graph_entry.tsx @@ -6,5 +6,5 @@ if (domNode) { const root = createRoot(domNode); root.render(); } else { - console.error("Failed to render welcome desk from React"); + console.error("Failed to render fancy graph from React"); } diff --git a/tapir/statistics/migrations/0004_fancygraphcache.py b/tapir/statistics/migrations/0004_fancygraphcache.py new file mode 100644 index 000000000..fc64f57b3 --- /dev/null +++ b/tapir/statistics/migrations/0004_fancygraphcache.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1.3 on 2024-12-06 11:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("statistics", "0003_alter_purchasebasket_tapir_user"), + ] + + operations = [ + migrations.CreateModel( + name="FancyGraphCache", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("view_name", models.CharField(max_length=255)), + ("date", models.DateField()), + ("value", models.IntegerField()), + ], + ), + ] diff --git a/tapir/statistics/models.py b/tapir/statistics/models.py index 4bb470298..a9421c6d5 100644 --- a/tapir/statistics/models.py +++ b/tapir/statistics/models.py @@ -23,3 +23,9 @@ class PurchaseBasket(models.Model): first_net_amount = models.FloatField() # VKNetto_SUM second_net_amount = models.FloatField() # EKNetto_SUM discount = models.FloatField() # Rabatt_SUM + + +class FancyGraphCache(models.Model): + view_name = models.CharField(max_length=255) + date = models.DateField() + value = models.IntegerField() diff --git a/tapir/statistics/views/fancy_graph/base_view.py b/tapir/statistics/views/fancy_graph/base_view.py index 7f193a901..971b4c64f 100644 --- a/tapir/statistics/views/fancy_graph/base_view.py +++ b/tapir/statistics/views/fancy_graph/base_view.py @@ -22,6 +22,7 @@ ShiftAttendanceModeService, ) from tapir.shifts.services.shift_expectation_service import ShiftExpectationService +from tapir.statistics.models import FancyGraphCache class FancyGraphView(LoginRequiredMixin, PermissionRequiredMixin, generic.TemplateView): @@ -38,9 +39,28 @@ class DatapointView(LoginRequiredMixin, PermissionRequiredMixin, APIView, ABC): permission_required = PERMISSION_COOP_MANAGE @abstractmethod - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: pass + def get_datapoint(self, reference_time: datetime.datetime): + reference_date = reference_time.date() + view_name = f"{self.__class__.__module__}.{self.__class__.__name__}" + + if reference_date < timezone.now().date(): + # Only use the cache for dates in the past: + # someone may make changes and check the results on the graph on the same day. + cached_value = FancyGraphCache.objects.filter( + view_name=view_name, date=reference_date + ).first() + if cached_value: + return cached_value.value + + value = self.calculate_datapoint(reference_time) + FancyGraphCache.objects.create( + view_name=view_name, date=reference_date, value=value + ) + return value + @staticmethod def get_reference_time(request): at_date = request.query_params.get("at_date") diff --git a/tapir/statistics/views/fancy_graph/number_of_abcd_members_view.py b/tapir/statistics/views/fancy_graph/number_of_abcd_members_view.py index 5e0febb0c..148b97628 100644 --- a/tapir/statistics/views/fancy_graph/number_of_abcd_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_abcd_members_view.py @@ -11,7 +11,7 @@ class NumberOfAbcdMembersAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: shift_user_datas = ( get_shift_user_datas_of_working_members_annotated_with_attendance_mode( reference_time diff --git a/tapir/statistics/views/fancy_graph/number_of_active_members_view.py b/tapir/statistics/views/fancy_graph/number_of_active_members_view.py index 7dcc364ec..a81bd7baf 100644 --- a/tapir/statistics/views/fancy_graph/number_of_active_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_active_members_view.py @@ -5,7 +5,7 @@ class NumberOfActiveMembersAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: reference_date = reference_time.date() return ShareOwner.objects.with_status( MemberStatus.ACTIVE, reference_date diff --git a/tapir/statistics/views/fancy_graph/number_of_co_purchasers_view.py b/tapir/statistics/views/fancy_graph/number_of_co_purchasers_view.py index c26b6c6f4..770c322a2 100644 --- a/tapir/statistics/views/fancy_graph/number_of_co_purchasers_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_co_purchasers_view.py @@ -11,7 +11,7 @@ class NumberOfCoPurchasersAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: tapir_users = TapirUser.objects.all() purchasing_members = NumberOfPurchasingMembersAtDateView.get_purchasing_members( reference_time diff --git a/tapir/statistics/views/fancy_graph/number_of_created_resignations_view.py b/tapir/statistics/views/fancy_graph/number_of_created_resignations_view.py index 20698b2d5..f2dde1b7a 100644 --- a/tapir/statistics/views/fancy_graph/number_of_created_resignations_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_created_resignations_view.py @@ -5,7 +5,7 @@ class NumberOfCreatedResignationsInSameMonthView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: reference_date = reference_time.date() return MembershipResignation.objects.filter( diff --git a/tapir/statistics/views/fancy_graph/number_of_exempted_members_view.py b/tapir/statistics/views/fancy_graph/number_of_exempted_members_view.py index 6b05a19a2..b14cb0b3f 100644 --- a/tapir/statistics/views/fancy_graph/number_of_exempted_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_exempted_members_view.py @@ -9,7 +9,7 @@ class NumberOfExemptedMembersAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: reference_date = reference_time.date() active_members = ShareOwner.objects.with_status( MemberStatus.ACTIVE, reference_date diff --git a/tapir/statistics/views/fancy_graph/number_of_flying_members_view.py b/tapir/statistics/views/fancy_graph/number_of_flying_members_view.py index 34cdd8199..44a4741b2 100644 --- a/tapir/statistics/views/fancy_graph/number_of_flying_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_flying_members_view.py @@ -11,7 +11,7 @@ class NumberOfFlyingMembersAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: shift_user_datas = ( get_shift_user_datas_of_working_members_annotated_with_attendance_mode( reference_time diff --git a/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py b/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py index f82f399c9..70e693eaf 100644 --- a/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_frozen_members_view.py @@ -14,7 +14,7 @@ class NumberOfFrozenMembersAtDateView(DatapointView): permission_required = PERMISSION_COOP_MANAGE - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: return self.get_members_frozen_at_datetime(reference_time).count() @staticmethod diff --git a/tapir/statistics/views/fancy_graph/number_of_investing_members_view.py b/tapir/statistics/views/fancy_graph/number_of_investing_members_view.py index eece21e8f..0d40dcc3e 100644 --- a/tapir/statistics/views/fancy_graph/number_of_investing_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_investing_members_view.py @@ -5,7 +5,7 @@ class NumberOfInvestingMembersAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: reference_date = reference_time.date() return ShareOwner.objects.with_status( diff --git a/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py b/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py index 1b39bbc41..027f5d26d 100644 --- a/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_long_term_frozen_members_view.py @@ -11,7 +11,7 @@ class NumberOfLongTermFrozenMembersAtDateView(DatapointView): permission_required = PERMISSION_COOP_MANAGE - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: share_owners = NumberOfFrozenMembersAtDateView.get_members_frozen_at_datetime( reference_time ).prefetch_related("user") diff --git a/tapir/statistics/views/fancy_graph/number_of_members_view.py b/tapir/statistics/views/fancy_graph/number_of_members_view.py index 7195ba10f..19ec04373 100644 --- a/tapir/statistics/views/fancy_graph/number_of_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_members_view.py @@ -5,7 +5,7 @@ class NumberOfMembersAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: reference_date = reference_time.date() total_count = 0 for member_status in [ diff --git a/tapir/statistics/views/fancy_graph/number_of_paused_members_view.py b/tapir/statistics/views/fancy_graph/number_of_paused_members_view.py index 320adb85b..5d9da6603 100644 --- a/tapir/statistics/views/fancy_graph/number_of_paused_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_paused_members_view.py @@ -5,7 +5,7 @@ class NumberOfPausedMembersAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: reference_date = reference_time.date() return ShareOwner.objects.with_status( MemberStatus.PAUSED, reference_date diff --git a/tapir/statistics/views/fancy_graph/number_of_pending_resignations_view.py b/tapir/statistics/views/fancy_graph/number_of_pending_resignations_view.py index 7d2c57238..c0ddaa2d0 100644 --- a/tapir/statistics/views/fancy_graph/number_of_pending_resignations_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_pending_resignations_view.py @@ -5,7 +5,7 @@ class NumberOfPendingResignationsAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: reference_date = reference_time.date() return MembershipResignation.objects.filter( diff --git a/tapir/statistics/views/fancy_graph/number_of_purchasing_members_view.py b/tapir/statistics/views/fancy_graph/number_of_purchasing_members_view.py index 6ed42a28b..7dec53175 100644 --- a/tapir/statistics/views/fancy_graph/number_of_purchasing_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_purchasing_members_view.py @@ -9,7 +9,7 @@ class NumberOfPurchasingMembersAtDateView(DatapointView): - def get_datapoint(self, reference_time) -> int: + def calculate_datapoint(self, reference_time) -> int: return len(self.get_purchasing_members(reference_time)) @staticmethod diff --git a/tapir/statistics/views/fancy_graph/number_of_shift_partners_view.py b/tapir/statistics/views/fancy_graph/number_of_shift_partners_view.py index 5c9aafbd9..4b5cdfe35 100644 --- a/tapir/statistics/views/fancy_graph/number_of_shift_partners_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_shift_partners_view.py @@ -8,7 +8,7 @@ class NumberOfShiftPartnersAtDateView(DatapointView): - def get_datapoint(self, reference_time) -> int: + def calculate_datapoint(self, reference_time) -> int: shift_user_datas = ( get_shift_user_datas_of_working_members_annotated_with_attendance_mode( reference_time diff --git a/tapir/statistics/views/fancy_graph/number_of_working_members_view.py b/tapir/statistics/views/fancy_graph/number_of_working_members_view.py index c413f3e36..10fc6f4ec 100644 --- a/tapir/statistics/views/fancy_graph/number_of_working_members_view.py +++ b/tapir/statistics/views/fancy_graph/number_of_working_members_view.py @@ -13,7 +13,7 @@ class NumberOfWorkingMembersAtDateView(DatapointView): - def get_datapoint(self, reference_time: datetime.datetime) -> int: + def calculate_datapoint(self, reference_time: datetime.datetime) -> int: reference_date = reference_time.date() shift_user_datas = (
Data Color Absolute RelativeDescriptionDescription
{dataset.display_name} + + { @@ -54,7 +73,7 @@ const DatasetPickerCard: React.FC = ({ }} /> + = ({ }} /> -
{dataset.description}
-
+
{dataset.description}
+