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
3 changes: 2 additions & 1 deletion webapp/src/Controller/BaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ protected function buildDeleteTree(array $entities, array $relations): array {
}
$primaryKeyData[] = $primaryKeyDataTemp;
}
return [$isError, $primaryKeyData, $messages];
return [$isError, $primaryKeyData, array_values(array_unique($messages))];
}

/**
Expand Down Expand Up @@ -445,6 +445,7 @@ protected function deleteEntities(
'showModalSubmit' => !$isError,
'modalUrl' => $request->getRequestUri(),
'redirectUrl' => $redirectUrl,
'count' => count($entities),
];
if ($request->isXmlHttpRequest()) {
return $this->render('jury/delete_modal.html.twig', $data);
Expand Down
62 changes: 62 additions & 0 deletions webapp/src/Controller/Jury/ProblemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ public function indexAction(): Response
'type' => ['title' => 'type', 'sort' => true],
];

if ($this->isGranted('ROLE_ADMIN')) {
$table_fields = array_merge(
['checkbox' => ['title' => '<input type="checkbox" class="select-all" title="Select all problems">', 'sort' => false, 'search' => false, 'raw' => true]],
$table_fields
);
}

$contestCountData = $this->em->createQueryBuilder()
->from(ContestProblem::class, 'cp')
->select('COUNT(cp.shortname) AS count', 'p.probid')
Expand All @@ -109,6 +116,28 @@ public function indexAction(): Response
$p = $row[0];
$problemdata = [];
$problemactions = [];

if ($this->isGranted('ROLE_ADMIN')) {
$problemIsLocked = false;
foreach ($p->getContestProblems() as $contestProblem) {
if ($contestProblem->getContest()->isLocked()) {
$problemIsLocked = true;
break;
}
}

if (!$problemIsLocked) {
$problemdata['checkbox'] = [
'value' => sprintf(
'<input type="checkbox" name="ids[]" value="%s" class="problem-checkbox">',
$p->getProbid()
)
];
} else {
$problemdata['checkbox'] = ['value' => ''];
}
}

// Get whatever fields we can from the problem object itself.
foreach ($table_fields as $k => $v) {
if ($propertyAccessor->isReadable($p, $k)) {
Expand Down Expand Up @@ -999,6 +1028,39 @@ public function editAction(Request $request, int $probId): Response
]);
}

#[IsGranted('ROLE_ADMIN')]
#[Route(path: '/delete-multiple', name: 'jury_problem_delete_multiple', methods: ['GET', 'POST'])]
public function deleteMultipleAction(Request $request): Response
{
$ids = $request->query->all('ids');
if (empty($ids)) {
throw new BadRequestHttpException('No IDs specified for deletion');
}

$problems = $this->em->getRepository(Problem::class)->findBy(['probid' => $ids]);

$deletableProblems = [];
foreach ($problems as $problem) {
$isLocked = false;
foreach ($problem->getContestProblems() as $contestProblem) {
if ($contestProblem->getContest()->isLocked()) {
$isLocked = true;
break;
}
}
if (!$isLocked) {
$deletableProblems[] = $problem;
}
}

if (empty($deletableProblems)) {
$this->addFlash('warning', 'No problems could be deleted (they might be locked).');
return $this->redirectToRoute('jury_problems');
}

return $this->deleteEntities($request, $deletableProblems, $this->generateUrl('jury_problems'));
}

#[IsGranted('ROLE_ADMIN')]
#[Route(path: '/{probId<\d+>}/delete', name: 'jury_problem_delete')]
public function deleteAction(Request $request, int $probId): Response
Expand Down
19 changes: 17 additions & 2 deletions webapp/templates/jury/delete_modal.html.twig
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{% extends "partials/modal.html.twig" %}

{% block title %}Delete {{ type }} {{ primaryKey }} - "{{ description }}"{% endblock %}
{% block title %}
{% if count > 1 %}
Delete {{ count }} {{ type }}s
{% else %}
Delete {{ type }} {{ primaryKey }} - "{{ description }}"
{% endif %}
{% endblock %}

{% block content %}

Expand All @@ -9,7 +15,16 @@
<strong>Error: {{ messages.0 }}</strong>
</div>
{% else %}
<p>You're about to delete {{ type }} {{ primaryKey }} "{{ description }}".</p>
{% if count > 1 %}
<p>You're about to delete the following {{ count }} {{ type }}s:</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: the plural doesn't work for "category", not sure that's important enough to fix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, I will see how to solve it when I get to team categories

<ul>
{% for desc in description|split(',') %}
<li>{{ desc|trim }}</li>
{% endfor %}
</ul>
{% else %}
<p>You're about to delete {{ type }} {{ primaryKey }} "{{ description }}".</p>
{% endif %}

{% if messages is not empty %}
<p>
Expand Down
4 changes: 2 additions & 2 deletions webapp/templates/jury/jury_macros.twig
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
<th scope="col" class="
{%- if column.sort is defined and column.sort %}sortable{%- endif %}
{%- if (column.search is not defined) or column.search %} searchable{%- endif %}">
{{- column.title|nl2br -}}
{%- if column.raw|default(false) %}{{- column.title|raw -}}{% else %}{{- column.title|nl2br -}}{% endif -%}
</th>
{%- endfor %}
{%- if num_actions > 0 %}
Expand Down Expand Up @@ -149,7 +149,7 @@
{% elseif (column.render | default('')) == "entity_id_badge" %}
{% if item.value %}{{- item.value|entityIdBadge(item.idPrefix|default('')) -}}{% endif %}
{% else %}
{{- (item.value|default(item.default|default(''))) -}}
{%- if column.raw|default(false) %}{{- (item.value|default(item.default|default('')))|raw -}}{% else %}{{- (item.value|default(item.default|default(''))) -}}{% endif -%}
{% endif %}
{% if item.icon is defined %}<i class="fas fa-{{ item.icon }}"></i>{%- endif %}
{%- if item.link is defined or (row.link is defined and not item.toggle_partial is defined) -%}</a>{% endif %}
Expand Down
57 changes: 56 additions & 1 deletion webapp/templates/jury/problems.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,64 @@
{% endif %}

{% if is_granted('ROLE_ADMIN') %}
<p>
<p class="mt-4">
{% if problems_current is not empty or problems_other is not empty %}
<button type="button" class="btn btn-danger me-2" id="delete-selected-button" disabled><i class="fas fa-trash-alt"></i> Delete selected</button>
{% endif %}
{{ button(path('jury_problem_add'), 'Add new problem', 'primary', 'plus') }}
{{ button(path('jury_import_export', {'_fragment':'problemarchive'}), 'Import problem', 'primary', 'upload') }}
</p>
{% endif %}


{% endblock %}

{% block extrafooter %}
{{ parent() }}
<script>
$(function() {
var $deleteButton = $('#delete-selected-button');

function toggleDeleteButton() {
var checkedCount = $('.problem-checkbox:checked').length;
$deleteButton.prop('disabled', checkedCount === 0);
}

// Use delegated events to handle clicks on checkboxes, as DataTables might redraw the table.
$(document).on('change', '.problem-checkbox', function() {
var table = $(this).closest('table');
var $tableCheckboxes = table.find('.problem-checkbox');
var $selectAllInTable = table.find('.select-all');
$selectAllInTable.prop('checked', $tableCheckboxes.length > 0 && $tableCheckboxes.length === $tableCheckboxes.filter(':checked').length);
toggleDeleteButton();
});

$(document).on('change', '.select-all', function() {
var table = $(this).closest('table');
table.find('.problem-checkbox').prop('checked', $(this).is(':checked'));
toggleDeleteButton();
});

// Initial state on page load.
toggleDeleteButton();

$deleteButton.on('click', function () {
var ids = $('.problem-checkbox:checked').map(function () {
return 'ids[]=' + $(this).val();
}).get();
if (ids.length === 0) return;

var url = "{{ path('jury_problem_delete_multiple') }}" + '?' + ids.join('&');

// Create a temporary link and click it to trigger the existing AJAX modal logic.
var $tempLink = $('<a>', {
'href': url,
'data-ajax-modal': ''
}).hide().appendTo('body');

$tempLink.trigger('click');
$tempLink.remove();
});
});
</script>
{% endblock %}
Loading