Skip to content

Commit

Permalink
Merge pull request #510 from iMMAP/feat/extra_filters_in_cluster_5w
Browse files Browse the repository at this point in the history
Feat/extra filters in cluster 5w
  • Loading branch information
shtayeb authored Dec 18, 2024
2 parents 29775d1 + bc08dfc commit 17c2678
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 358 deletions.
7 changes: 3 additions & 4 deletions .github/workflows/quality_assurance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ jobs:
run: |
make test
- name: Formating and Linting check with ruff
uses: astral-sh/ruff-action@v2
with:
args: check --output-format=github
src: "./src"
run: |
ruff check --output-format=github ./src
ruff format --check
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ serve:
vite:
cd src/static && npm run dev

.PHONY: ruff-watch
ruff-watch:
ruff check ./src --watch

.PHONY: dev
dev:
make -j3 serve vite
make -j3 serve vite ruff-watch

.PHONY: vite-host
vite-host:
Expand Down
71 changes: 20 additions & 51 deletions src/project_reports/exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from openpyxl.styles import Font, NamedStyle

from project_reports.filters import MonthlyReportsFilter, Organization5WFilter
from rh.models import Cluster, Organization, Project
from rh.models import Cluster, Organization, Project
from stock.models import StockMonthlyReport, StockReport
from stock.utils import write_csv_columns_and_rows
from users.utils import is_cluster_lead
Expand All @@ -30,31 +30,15 @@
def cluster_5w_dashboard_export(request, code):
cluster = get_object_or_404(Cluster, code=code)

body = json.loads(request.body)

from_date = body.get("from")
if not from_date:
from_date = datetime.date(datetime.datetime.now().year, 1, 1)

to_date = body.get("to")
if not to_date:
to_date = datetime.datetime.now().date()

user_country = request.user.profile.country

filter_params = {
"project__clusters__in": [cluster],
"state__in": ["submited", "completed"],
"from_date__lte": to_date,
"to_date__gte": from_date,
"project__user__profile__country": user_country,
"activityplanreport__activity_plan__activity_domain__clusters__in": [cluster],
}

organization_code = body.get("organization")
if organization_code:
filter_params["project__organization__code"] = organization_code

if not is_cluster_lead(
user=request.user,
clusters=[
Expand Down Expand Up @@ -86,9 +70,16 @@ def cluster_5w_dashboard_export(request, code):
.distinct()
)

monthly_reports_filter = Organization5WFilter(request.GET, queryset=project_reports, user=request.user)

today = datetime.datetime.now()
today_date = today.today().strftime("%d-%m-%Y")

response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = "attachment; filename=reports.csv"
write_projects_reports_to_csv(project_reports, response)
response["Content-Disposition"] = f"attachment; filename={cluster.code}_5w_reports_data_{today_date}.csv"

write_projects_reports_to_csv(monthly_reports_filter.qs, response)

return response

except Exception as e:
Expand Down Expand Up @@ -155,31 +146,7 @@ def org_5w_dashboard_export(request, code):
request.user, org.clusters.values_list("code", flat=True)
):
raise PermissionDenied



# from_date = body.get("from")
# if not from_date:
# from_date = datetime.date(datetime.datetime.now().year, 1, 1)

# to_date = body.get("to")
# if not to_date:
# to_date = datetime.datetime.now().date()

# filter_params = {
# "project__organization": org,
# "state__in": ["submited", "completed"],
# "from_date__lte": to_date,
# "to_date__gte": from_date,
# }

# cluster_code = body.get("cluster")
# if cluster_code:
# filter_params["project__clusters__code__in"] = [
# cluster_code,
# ]

print(f"This is the GET Request: {request.GET}")
try:
project_reports = (
ProjectMonthlyReport.objects.select_related("project")
Expand All @@ -198,24 +165,26 @@ def org_5w_dashboard_export(request, code):
)
),
)
).filter(project__organization=org).distinct()
)
monthly_reports_filter = Organization5WFilter(
request.GET,
queryset = project_reports,
user=request.user
)
.filter(project__organization=org)
.distinct()
)

monthly_reports_filter = Organization5WFilter(request.GET, queryset=project_reports, user=request.user)

today = datetime.datetime.now()
today_date = today.today().strftime("%d-%m-%Y")
monthly_reports_queryset = monthly_reports_filter.qs

response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f"attachment; filename={org.code}_5w_reports_data_{today_date}.csv"

write_projects_reports_to_csv(monthly_reports_queryset, response)

return response
except Exception as e:
print(f"Error: {e}")
return HttpResponse(status=500)
return HttpResponse(status=500, content=e)


# export monthly report for single project
Expand Down
7 changes: 5 additions & 2 deletions src/project_reports/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,16 @@ def clean(self):
from_date = cleaned_data.get("from_date")
to_date = cleaned_data.get("to_date")
obj = self.initial.get("project")

if isinstance(obj, int):
project = get_object_or_404(Project, pk=obj)
else:
project = obj

if from_date and to_date:
if from_date.strftime("%B-%Y") > today_date.strftime("%B-%Y"):
if from_date > today_date:
self.add_error("from_date", "Unable to select future date.")
if to_date.strftime("%B-%Y") > today_date.strftime("%B-%Y"):
if to_date > today_date:
self.add_error("to_date", "Unable to select future date.")
if from_date.month != to_date.month:
self.add_error("from_date", "From date and to date must be in the same month.")
Expand All @@ -69,6 +71,7 @@ def clean(self):
self.add_error("to_date", f"It must not precede the project start date {(project.start_date).date()}")
if to_date > (project.end_date.date()):
self.add_error("to_date", f"It must not exceed the project end date {(project.end_date).date()}")

return cleaned_data


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
<div class="top-panel pb-2 pt-4 flex flex-col items-center justify-between gap-2 sm:flex-row">
<h2 class="locations text-red-be">{{ request.resolver_match.kwargs.cluster|capfirst }} 5W Dashboard</h2>
<div class="actions-panel">
<button class="export-button btn btn-gray"
data-url="{% url 'cluster-5w-stock-report-export' request.resolver_match.kwargs.cluster %}"
<a class="export-button btn btn-gray"
href="{% url 'cluster-5w-stock-report-export' request.resolver_match.kwargs.cluster %}"
title="{{request.resolver_match.kwargs.cluster }}_5w_stock_reports_">
<span class="btn-text">Export Stocks Reports</span>
<span class="icon-download"></span>
Expand All @@ -32,9 +32,9 @@ <h2 class="locations text-red-be">{{ request.resolver_match.kwargs.cluster|capfi
style="width: 1.2rem"
src="{% static 'images/spinner.gif' %}" />
<!-- spinner end -->
</button>
<button class="export-button btn btn-red"
data-url="{% url 'export-cluster-5w-dashboard' request.resolver_match.kwargs.cluster %}"
</a>
<a class="export-button btn btn-red"
href="{% url 'export-cluster-5w-dashboard' request.resolver_match.kwargs.cluster %}?{{request.GET.urlencode}}"
title="{{request.resolver_match.kwargs.cluster }}_5w_projects_reports_">
<span class="btn-text">Export Projects Reports</span>
<span class="icon-download"></span>
Expand All @@ -43,59 +43,15 @@ <h2 class="locations text-red-be">{{ request.resolver_match.kwargs.cluster|capfi
style="width: 1.2rem"
src="{% static 'images/spinner.gif' %}" />
<!-- spinner end -->
</button>
</a>
{% include "components/_filter_drawer.html" with filter=dashboard_filter %}
</div>
</div>
<p class="flex items-center gap-2 pb-3">
<span class="text-sm text-gray-700">Cluster 5w dashboard.</span>
</p>
{% comment %} {% endcomment %}
<section class="space-y-4 pt-2">
{% comment %} filter {% endcomment %}
<form id="5w-filter-form" method="GET" class="py-4 bg-gray-fa border border-gray-d1 p-4 rounded-lg">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="flex items-center gap-2">
<div class="inline-flex justify-center rounded-md text-sm font-medium flex-col items-start w-full h-auto">
<label class="font-semibold uppercase text-gray" for="from">From</label>
<input
type="date"
name="from"
value="{{ request.GET.from }}"
id="from">
</div>
<div class="inline-flex justify-center rounded-md text-sm font-medium flex-col items-start w-full h-auto">
<label class="font-semibold text-gray uppercase text-[0.65rem]" for="to">To</label>
<input type="date" name="to" value="{{ request.GET.to }}" id="to">
</div>
<div class="inline-flex justify-center rounded-md text-sm font-medium flex-col items-start w-full h-auto">
<label class="font-semibold uppercase text-gray" for="organization">Organization</label>
<script>
function checkUserKeydown(event) {
return event instanceof KeyboardEvent
}
</script>
<input
placeholder="Enter the organization code..."
id="organization"
name="organization"
list="orgList"
hx-get="{% url 'organizations-search' %}"
hx-target="#orgList"
value="{{request.GET.organization}}"
hx-trigger="keyup[checkUserKeydown.call(this, event)] changed delay:500ms"
/>
<datalist id="orgList">
{% comment %} data from the htmx {% endcomment %}
</datalist>
</div>
</div>

<div class="flex items-center justify-end gap-2">
<a class="btn btn-gray-outline" href="{% url 'clusters-5w' request.resolver_match.kwargs.cluster %}">Reset</a>
<button type="submit" class="btn btn-red">Filter</button>
</div>
</div>
</form>
{% comment %} cards {% endcomment %}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="rounded-lg shadow-sm p-6 border border-gray-d1">
Expand Down Expand Up @@ -179,16 +135,6 @@ <h2 class="locations text-red-be">{{ request.resolver_match.kwargs.cluster|capfi
</div>
{% comment %} Grid {% endcomment %}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
{% comment %} <div class="rounded-lg shadow-sm p-6 border border-gray-d1">
<div class="flex items-center justify-between pb-4 border-b border-gray-e6">
<h3 class="text-lg font-medium">Location Heatmap</h3>
</div>
<div class="mt-4">
<div class="relative w-full bg-gray-d1 h-96">
</div>
</div>
</div> {% endcomment %}
{% comment %} People reached by activities {% endcomment %}
<div class="col-span-2 rounded-lg shadow-sm p-6 border border-gray-d1">
<div class="flex items-center justify-between pb-4 border-b border-gray-e6">
<h3 class="text-lg font-medium ">People reached by activities</h3>
Expand Down Expand Up @@ -274,68 +220,5 @@ <h3 class="text-lg font-medium pb-2 ">People Reached Monthly Trend</h3>
}
}
});

{% comment %} export {% endcomment %}
document.querySelectorAll(".export-button")?.forEach(button => {
button.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();

const exportUrl = button.getAttribute("data-url");
const iconDownload = button.querySelector(".icon-download");
const iconSpinner = button.querySelector(".downloading");
// extract the file name
let name = button.getAttribute("title");
const today = new Date()
const formatDate = today.toISOString().split('T')[0];
let file_name = name+formatDate;

// Get the form data
const formData = new FormData(document.getElementById('5w-filter-form'));

// Convert the form data to JSON
const jsonData = JSON.stringify(Object.fromEntries(formData));

// Include the JSON data in the fetch body
const requestBody = JSON.stringify({...JSON.parse(jsonData) });
iconDownload.style.display='none';
iconSpinner.style.display="block";
button.disabled = true;
console.log((event.loaded / event.total) * 100);
// Send an AJAX request to the export URL
fetch(exportUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrftoken,
},
body: requestBody,
})
.then(response => {
return response.blob();
})
.then(blob =>{
const link = document.createElement("a");
const url = URL.createObjectURL(blob);
link.href = url;
link.download = file_name+".csv";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
})
.catch((error) => {
if (error.status === 400) {
console.log("Error: No records selected for export");
} else {
console.log(`Error: ${error.message}`);
}
}).finally(() => {
button.disabled = false;
iconSpinner.style.display = "none";
iconDownload.style.display = "inline";
});
})});

</script>
{% endblock scripts %}
Loading

0 comments on commit 17c2678

Please sign in to comment.