Skip to content

Commit

Permalink
Fancy graph (#588)
Browse files Browse the repository at this point in the history
* Added view

* Base for react integration and dummy graph

* Added two test views and graph controls.

* Chart with dynamic loading!

* Added script for db dump import

* Added dataset working members, renamed a few service files.

* Added dataset purchasing members.

* Added dataset frozen members.

* Formatting.

* Added view number of long-term frozen members.

* Added view number of shift partners.

* Added view number of co purchasers

* Added view number of flying members

* Added view number of abcd members

* Added view number of investing and paused members

* Added view number of resignations

* Standardization of reference dates.

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* WIP generic absolute / relative views

* Graph styling

* Graph styling

* Fixed jumpy graph using refs.

* Moved dataset definition to a separate file.

* Use formatDate from utils

* Moved dataset picker to its own component.

* Moved date range picker to its own component.

* Moved ChartJS.register outside of the component

* Added download data button.

* Added view for exempted members.

* Translation files.

* Conflict solve translation file from master.

* Added a description

* Shift partner only from working members.

* Added caching system for datapoint views.

* Missing file for previous commit.

* Datapicker on the side and expandable.

* Date range picker in the graph header
  • Loading branch information
Theophile-Madet authored Dec 6, 2024
1 parent afa7ba1 commit 62cad3a
Show file tree
Hide file tree
Showing 22 changed files with 183 additions and 51 deletions.
49 changes: 24 additions & 25 deletions src/statistics/FancyGraphCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -58,6 +58,7 @@ const FancyGraphCard: React.FC = () => {
const [graphLabels, setGraphLabels] = useState<string[]>([]);
const [dates, setDates] = useState<Date[]>([]);
const [fetching, setFetching] = useState(false);
const [datapickerExpanded, setDatapickerExpanded] = useState(false);
const cachedData = useRef<CachedData>({});
const api = useApi(StatisticsApi);

Expand Down Expand Up @@ -228,24 +229,6 @@ const FancyGraphCard: React.FC = () => {

return (
<>
<Row className={"mb-2"}>
<Col>
<DatasetPickerCard
setEnabledDatasets={setEnabledDatasets}
enabledDatasetsRef={enabledDatasetsRef}
/>
</Col>
</Row>
<Row className={"mb-2"}>
<Col>
<DateRangePickerCard
dateFrom={dateFrom}
setDateFrom={setDateFrom}
dateTo={dateTo}
setDateTo={setDateTo}
/>
</Col>
</Row>
{error && (
<Row className={"mb-2"}>
<Col>
Expand All @@ -254,6 +237,14 @@ const FancyGraphCard: React.FC = () => {
</Row>
)}
<Row>
<Col className={"mb-2 " + (datapickerExpanded ? "" : "col-xxl-3")}>
<DatasetPickerCard
setEnabledDatasets={setEnabledDatasets}
enabledDatasetsRef={enabledDatasetsRef}
isExpanded={datapickerExpanded}
setIsExpanded={setDatapickerExpanded}
/>
</Col>
<Col>
<Card>
<Card.Header
Expand All @@ -262,12 +253,20 @@ const FancyGraphCard: React.FC = () => {
<h5>
{gettext("Graph")} {fetching && <Spinner size={"sm"} />}
</h5>
<TapirButton
variant={"outline-secondary"}
text={"Download as CSV"}
icon={Download}
onClick={downloadCurrentData}
/>
<span className={"d-flex gap-2"}>
<DateRangePicker
dateFrom={dateFrom}
setDateFrom={setDateFrom}
dateTo={dateTo}
setDateTo={setDateTo}
/>
<TapirButton
variant={"outline-secondary"}
text={"Download as CSV"}
icon={Download}
onClick={downloadCurrentData}
/>
</span>
</Card.Header>
<Card.Body className={"p-2 m-2"}>
<Chart
Expand Down
39 changes: 30 additions & 9 deletions src/statistics/components/DatasetPickerCard.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,48 @@
import React, { MutableRefObject } from "react";
import { Card, Form, Table } from "react-bootstrap";
import { datasets } from "../datasets.tsx";
import TapirButton from "../../components/TapirButton.tsx";
import {
ArrowsCollapseVertical,
ArrowsExpandVertical,
} from "react-bootstrap-icons";

declare let gettext: (english_text: string) => string;

interface DatasetPickerCardProps {
enabledDatasetsRef: MutableRefObject<Set<string>>;
setEnabledDatasets: (set: Set<string>) => void;
isExpanded: boolean;
setIsExpanded: (isExpanded: boolean) => void;
}

const DatasetPickerCard: React.FC<DatasetPickerCardProps> = ({
enabledDatasetsRef,
setEnabledDatasets,
isExpanded,
setIsExpanded,
}) => {
return (
<Card>
<Card.Header>
<Card.Header
className={"d-flex justify-content-between align-items-center"}
>
<h5>{gettext("Pick which data to display")}</h5>
<TapirButton
variant={"outline-secondary"}
icon={isExpanded ? ArrowsCollapseVertical : ArrowsExpandVertical}
onClick={() => setIsExpanded(!isExpanded)}
/>
</Card.Header>
<Card.Body style={{ padding: "0" }}>
<Table className={"table-striped table-hover"}>
<Table className={"table-striped table-hover table-bordered table-sm"}>
<thead>
<tr>
<th>Data</th>
<th>Color</th>
<th>Absolute</th>
<th>Relative</th>
<th>Description</th>
{isExpanded && <th>Description</th>}
</tr>
</thead>
<tbody>
Expand All @@ -38,10 +54,13 @@ const DatasetPickerCard: React.FC<DatasetPickerCardProps> = ({
return (
<tr key={datasetId} style={{ verticalAlign: "middle" }}>
<td>{dataset.display_name}</td>
<td className={"fs-2"} style={{ color: dataset.color }}>
<td
className={"fs-2 text-center"}
style={{ color: dataset.color }}
>
&#9632;
</td>
<td>
<td className={"text-center"}>
<Form.Check
type={"switch"}
onChange={(e) => {
Expand All @@ -54,7 +73,7 @@ const DatasetPickerCard: React.FC<DatasetPickerCardProps> = ({
}}
/>
</td>
<td>
<td className={"text-center"}>
<Form.Check
key={datasetRelativeId}
type={"switch"}
Expand All @@ -68,9 +87,11 @@ const DatasetPickerCard: React.FC<DatasetPickerCardProps> = ({
}}
/>
</td>
<td>
<div>{dataset.description}</div>
</td>
{isExpanded && (
<td>
<div>{dataset.description}</div>
</td>
)}
</tr>
);
})}
Expand Down
56 changes: 56 additions & 0 deletions src/statistics/components/DateRangePicker.tsx
Original file line number Diff line number Diff line change
@@ -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<DateRangePickerProps> = ({
dateFrom,
setDateFrom,
dateTo,
setDateTo,
}) => {
return (
<>
<Form.Group>
<FloatingLabel label={"Date from"}>
<Form.Control
type={"date"}
value={
!isNaN(dateFrom.getTime())
? dateFrom.toISOString().substring(0, 10)
: undefined
}
onChange={(event) => {
setDateFrom(getFirstOfMonth(new Date(event.target.value)));
}}
/>
</FloatingLabel>
</Form.Group>
<Form.Group>
<FloatingLabel label={"Date from"}>
<Form.Control
type={"date"}
value={
!isNaN(dateTo.getTime())
? dateTo.toISOString().substring(0, 10)
: undefined
}
onChange={(event) => {
setDateTo(getFirstOfMonth(new Date(event.target.value)));
}}
/>
</FloatingLabel>
</Form.Group>
</>
);
};

export default DateRangePicker;
2 changes: 1 addition & 1 deletion src/statistics/fancy_graph_entry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ if (domNode) {
const root = createRoot(domNode);
root.render(<FancyGraphCard />);
} else {
console.error("Failed to render welcome desk from React");
console.error("Failed to render fancy graph from React");
}
30 changes: 30 additions & 0 deletions tapir/statistics/migrations/0004_fancygraphcache.py
Original file line number Diff line number Diff line change
@@ -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()),
],
),
]
6 changes: 6 additions & 0 deletions tapir/statistics/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
22 changes: 21 additions & 1 deletion tapir/statistics/views/fancy_graph/base_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading

0 comments on commit 62cad3a

Please sign in to comment.