Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions src/staff/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,10 @@ def __init__(
class Meta:
model = Sample
fields = [
# "order",
# "guid",
"genlab_id",
"name",
"species",
"type",
# "year",
# "location",
# "pop_id",
# "type",
# "desired_extractions",
]


Expand Down Expand Up @@ -210,10 +203,6 @@ def __init__(
class Meta:
model = Sample
fields = [
# "order",
# "order__status",
# "order__genrequest__project",
"guid",
"name",
"genlab_id",
"species",
Expand All @@ -222,8 +211,6 @@ class Meta:
"location",
"pop_id",
"type",
# "desired_extractions",
"plate_positions__plate",
]


Expand Down
135 changes: 120 additions & 15 deletions src/staff/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,21 +328,6 @@ def render_sample__plate_positions(self, value: Any) -> str:
return ""


class SampleTable(SampleBaseTable):
genlab_id = tables.Column(
linkify=("staff:samples-detail", {"pk": tables.A("id")}),
orderable=False,
empty_values=(),
)

class Meta(SampleBaseTable.Meta):
fields = SampleBaseTable.Meta.fields + [
"order",
"order__status",
"order__genrequest__project",
]


class PlateTable(tables.Table):
id = tables.Column(
linkify=("staff:plates-detail", {"pk": tables.A("id")}),
Expand Down Expand Up @@ -388,6 +373,126 @@ def render_status(self, value: Order.OrderStatus, record: Order) -> str:
)


class StatusMixinTableSamples(tables.Table):
sample_status = tables.Column(
verbose_name="Sample Status", empty_values=(), orderable=True
)

order__status = tables.Column(
verbose_name="Order Status", empty_values=(), orderable=True
)

def render_order__status(self, value: Order.OrderStatus, record: Order) -> str:
status_colors = {
"Processing": "bg-yellow-100 text-yellow-800",
"Completed": "bg-green-100 text-green-800",
"Delivered": "bg-red-100 text-red-800",
}
status_text = {
"Processing": "Processing",
"Completed": "Completed",
"Delivered": "Not started",
}
color_class = status_colors.get(value, "bg-gray-100 text-gray-800")
status_text = status_text.get(value, "Unknown")
return mark_safe( # noqa: S308
f'<span class="px-2 py-1 text-xs font-medium rounded-full whitespace-nowrap {color_class}">{status_text}</span>' # noqa: E501
)

def render_sample_status(self, value: Any, record: Sample) -> str:
order = record.order

# Determine status label
if isinstance(order, ExtractionOrder):
if record.is_isolated:
status = "Isolated"
elif record.is_plucked:
status = "Plucked"
elif record.is_marked:
status = "Marked"
else:
status = "Not started"
else:
status = getattr(order, "sample_status", "Unknown")

# Define color map
status_colors = {
"Marked": "bg-orange-100 text-orange-800",
"Plucked": "bg-yellow-100 text-yellow-800",
"Isolated": "bg-green-100 text-green-800",
"Not started": "bg-red-100 text-red-800",
"Unknown": "bg-gray-100 text-gray-800",
}

# Use computed status, not value
color_class = status_colors.get(status, "bg-gray-100 text-gray-800")

return mark_safe( # noqa: S308
f'<span class="px-2 py-1 text-xs font-medium rounded-full whitespace-nowrap {color_class}">{status}</span>' # noqa: E501
)


class SampleTable(SampleBaseTable, StatusMixinTableSamples):
STATUS_PRIORITY = {
"Not started": 0,
"Marked": 1,
"Plucked": 2,
"Isolated": 3,
}

genlab_id = tables.Column(
linkify=("staff:samples-detail", {"pk": tables.A("id")}),
orderable=False,
empty_values=(),
)

sample_status = tables.Column(
verbose_name="Sample Status", empty_values=(), orderable=True
)

order__status = tables.Column(
verbose_name="Order Status", empty_values=(), orderable=True
)

class Meta(SampleBaseTable.Meta):
fields = SampleBaseTable.Meta.fields + [
"order",
"order__status",
"order__genrequest__project",
"order__responsible_staff",
]
sequence = SampleBaseTable.Meta.sequence + (
"sample_status",
"order",
"order__status",
"order__responsible_staff",
"notes",
)
exclude = ("guid", "plate_positions", "checked", "is_prioritised")

def order_sample_status(
self, records: Sequence[Any], is_descending: bool
) -> tuple[list[Any], bool]:
def get_status_value(record: Any) -> int:
if isinstance(record.order, ExtractionOrder):
if record.is_isolated:
return self.STATUS_PRIORITY["Isolated"]
elif record.is_plucked:
return self.STATUS_PRIORITY["Plucked"]
elif record.is_marked:
return self.STATUS_PRIORITY["Marked"]
else:
return self.STATUS_PRIORITY["Not started"]
else:
# fallback for other types of orders
return self.STATUS_PRIORITY.get(
getattr(record.order, "sample_status", ""), -1
)

sorted_records = sorted(records, key=get_status_value, reverse=is_descending)
return (sorted_records, True)


class StaffIDMixinTable(tables.Table):
id = tables.Column(
orderable=False,
Expand Down
26 changes: 22 additions & 4 deletions src/staff/templates/staff/sample_filter.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,35 @@
{% render_table table %}
</form>

<form id="prioritise-form" method="post" action="{% url 'staff:order-extraction-samples' pk=order.pk %}" style="display:none;">
{% csrf_token %}
<input type="hidden" name="sample_id" id="prioritise-sample-id">
</form>
{% else %}

<form method="get" class="py-3 px-4 border mb-3 ">
<div class="flex flex-wrap gap-4">
{{ filter.form | crispy }}
<button class="btn custom_order_button_blue mt-6" type="submit">Search</button>
</div>
</form>
<input type="hidden" name="sort" value="{{ request.GET.sort|default:'' }}">

{% render_table table %}
{% endif %}

<form id="prioritise-form" method="post" style="display:none;">
{% csrf_token %}
<input type="hidden" name="sample_id" id="prioritise-sample-id">
<input type="hidden" name="next" id="prioritise-next-url" value="{{ request.get_full_path }}">
</form>

<script>
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll('.toggle-prioritise').forEach(btn => {
btn.addEventListener('click', function () {
const sampleId = this.dataset.sampleId;
const orderId = this.dataset.orderId;
const form = document.getElementById('prioritise-form');
const actionUrl = `/staff/orders/extraction/${orderId}/samples/`;
form.action = actionUrl;
form.querySelector('#prioritise-next-url').value = window.location.href;
form.querySelector('#prioritise-sample-id').value = sampleId;
form.submit();
});
Expand Down
13 changes: 11 additions & 2 deletions src/staff/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.db.models.functions import Cast
from django.forms import Form
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy
from django.utils.timezone import now
from django.utils.translation import gettext as _
Expand Down Expand Up @@ -275,6 +275,10 @@ def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
sample.is_prioritised = not sample.is_prioritised
sample.save()

next_url = request.POST.get("next")
if next_url:
return redirect(next_url)

return self.get(
request, *args, **kwargs
) # Re-render the view with updated data
Expand Down Expand Up @@ -311,6 +315,8 @@ def get_context_data(self, **kwargs) -> dict[str, Any]:


class SamplesListView(StaffMixin, SingleTableMixin, FilterView):
table_pagination = False

model = Sample
table_class = SampleTable
filterset_class = SampleFilter
Expand All @@ -327,7 +333,10 @@ def get_queryset(self) -> models.QuerySet[Sample]:
"order__genrequest",
"order__genrequest__project",
)
.prefetch_related("plate_positions")
.prefetch_related(
"plate_positions",
"order__responsible_staff",
)
.exclude(order__status=Order.OrderStatus.DRAFT)
.order_by("species__name", "year", "location__name", "name")
)
Expand Down