Skip to content
Open
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
57 changes: 14 additions & 43 deletions src/Forum/Components/MissionRSVPs.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,36 @@
namespace Forumify\PerscomPlugin\Forum\Components;

use Forumify\PerscomPlugin\Perscom\Entity\Mission;
use Forumify\PerscomPlugin\Perscom\Entity\MissionRSVP;
use Forumify\PerscomPlugin\Perscom\Entity\PerscomUser;
use Forumify\PerscomPlugin\Perscom\Entity\Unit;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsLiveComponent('Perscom\\MissionRSVPs', '@ForumifyPerscomPlugin/frontend/components/mission_rsvps.html.twig')]
#[AsTwigComponent('Perscom\\MissionStats', '@ForumifyPerscomPlugin/frontend/components/mission_stats.html.twig')]
class MissionRSVPs
{
#[LiveProp]
public Mission $mission;

use DefaultActionTrait;

/**
* @return array<int, array{ unit: Unit, rsvps: array<array{ rsvp: MissionRSVP, user: PerscomUser }>}>
*/
public function getRSVPs(): array
public function getGoing(): int
{
$rsvps = [];
$count = 0;

foreach ($this->mission->getRsvps() as $rsvp) {
$user = $rsvp->getUser();
if ($user === null) {
continue;
if ($rsvp->isGoing() === true) {
$count++;
}

$rsvps[] = [
'rsvp' => $rsvp,
'user' => $user,
];
}

return $this->groupByUnit($rsvps);
return $count;
}

/**
* @param array<array{ rsvp: MissionRSVP, user: PerscomUser }> $rsvps
* @return array<int, array{ unit: Unit, rsvps: array<array{ rsvp: MissionRSVP, user: PerscomUser }>}>
*/
private function groupByUnit(array $rsvps): array
public function getAbsent(): int
{
$units = [];
foreach ($rsvps as $rsvp) {
$user = $rsvp['user'];
$unit = $user->getUnit();
if ($unit === null) {
continue;
}
$count = 0;

$unitId = $unit->getPerscomId();
if (!isset($units[$unitId])) {
$units[$unitId]['unit'] = $unit;
foreach ($this->mission->getRsvps() as $rsvp) {
if ($rsvp->isGoing() === false) {
$count++;
}

$units[$unitId]['rsvps'][] = $rsvp;
}

uasort($units, fn (array $a, array $b): int => $a['unit']->getPosition() <=> $b['unit']->getPosition());
return $units;
return $count;
}
}
84 changes: 84 additions & 0 deletions src/Forum/Components/MissionRoster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Forumify\PerscomPlugin\Forum\Components;

use Forumify\PerscomPlugin\Perscom\Entity\Mission;
use Forumify\PerscomPlugin\Perscom\Entity\MissionRSVP;
use Forumify\PerscomPlugin\Perscom\Entity\PerscomUser;
use Forumify\PerscomPlugin\Perscom\Entity\Unit;
use Forumify\PerscomPlugin\Perscom\Service\PerscomUserService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent('Forumify\\Perscom\\MissionRoster', '@ForumifyPerscomPlugin/frontend/components/mission_roster.html.twig')]
class MissionRoster extends AbstractController
{

public Mission $mission;

private array $rsvpsByUserId = [];
private ?array $unitsWithRSVPs = null;

public function __construct(
private readonly PerscomUserService $userService,
) {
}

public function getUnitsWithRSVPs(): array
{
if ($this->unitsWithRSVPs !== null) {
return $this->unitsWithRSVPs;
}

$this->loadRSVPs();
$units = [];

foreach ($this->rsvpsByUserId as $rsvp) {
$user = $rsvp->getUser();
$unit = $user?->getUnit();

if ($unit && !isset($units[$unit->getId()])) {
$units[$unit->getId()] = $unit;
}
}

uasort($units, fn (Unit $a, Unit $b) => $a->getPosition() <=> $b->getPosition());
return $this->unitsWithRSVPs = $units;
Copy link
Member

Choose a reason for hiding this comment

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

Not a big fan of the assign-return, just split them up.

Suggested change
return $this->unitsWithRSVPs = $units;
$this->unitsWithRSVPs = $units;
return $this->unitsWithRSVPs;

}

public function getUsersInUnitWithRSVPs(Unit $unit): array
{
$this->loadRSVPs();
$users = [];

foreach ($unit->getUsers() as $user) {
if (isset($this->rsvpsByUserId[$user->getId()])) {
$users[$user->getId()] = $user;
}
}

$this->userService->sortPerscomUsers($users);
return $users;
}

public function getRSVPForUser(PerscomUser $user): ?MissionRSVP
{
$this->loadRSVPs();
return $this->rsvpsByUserId[$user->getId()] ?? null;
}

private function loadRSVPs(): void
{
if (!empty($this->rsvpsByUserId)) {
return;
}

foreach ($this->mission->getRsvps() as $rsvp) {
if ($user = $rsvp->getUser()) {
$this->rsvpsByUserId[$user->getId()] = $rsvp;
}
}
}
Comment on lines +72 to +83
Copy link
Member

Choose a reason for hiding this comment

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

Change this function to also return $this->rsvpsByUserId (maybe also change its name to match it). And then everywhere where this function is used, don't directly access $this->rsvpsByUserId anymore.

That way it's very clear in the usages what it returns, and it's not some magic private variable.

}
34 changes: 34 additions & 0 deletions templates/frontend/components/mission_roster.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div {{ attributes }}>
{% set units = this.unitsWithRSVPs %}

{% if units is not empty %}
<ul>
{% for unit in units %}
{% set users = this.getUsersInUnitWithRSVPs(unit) %}
{% if users is not empty %}
<li class="card mb-4">
<div class="card-title">{{ unit.name }}</div>
<ul class="card-body">
{% for user in users %}
<li class="text-small">
<a href="{{ path('perscom_user', { id: user.id }) }}" class="btn-link w-100">
{% include '@ForumifyPerscomPlugin/frontend/mission/components/soldier_row.html.twig' with {
soldier: user,
rsvp: this.getRSVPForUser(user)
} only %}
</a>
</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<div class="card">
<div class="card-body text-center text-muted">
No attendance has been marked yet.
</div>
</div>
{% endif %}
</div>
70 changes: 36 additions & 34 deletions templates/frontend/components/mission_rsvp_button.html.twig
Original file line number Diff line number Diff line change
@@ -1,45 +1,47 @@
{% set rsvp = this.rsvp %}
{% set going = rsvp is not null and rsvp.going %}
<div {{ attributes }}>
<div {{ stimulus_controller('forumify/forumify-platform/menu', { placement: 'bottom-end' }) }}>
{% if going %}
<button class="btn-link" {{ stimulus_target('forumify/forumify-platform/menu', 'openButton') }}>
<i class="ph ph-check" style="color: green"></i>{{ 'perscom.mission.rsvp.going'|trans }}
</button>
{% elseif rsvp is not null %}
<button class="btn-link" {{ stimulus_target('forumify/forumify-platform/menu', 'openButton') }}>
<i class="ph ph-x-circle" style="color: red"></i>{{ 'perscom.mission.rsvp.not_going'|trans }}
</button>
{% else %}
<button class="btn-primary" {{ stimulus_target('forumify/forumify-platform/menu', 'openButton') }}>
<i class="ph ph-calendar"></i>{{ 'perscom.mission.rsvp.rsvp'|trans }}
</button>
{% endif %}
<div class="menu" {{ stimulus_target('forumify/forumify-platform/menu', 'menu') }}>
{% if rsvp is null or not going %}
<button class="btn-link" {{
stimulus_action('live', 'action', parameters: { action: 'toggle', going: true })
|stimulus_action('forumify/forumify-platform/menu', 'close')
}}>
<i class="ph ph-check-circle" style="color: green"></i>{{ 'perscom.mission.rsvp.going'|trans }}
<div class="flex gap-2 items-center">
<div {{ stimulus_controller('forumify/forumify-platform/menu', { placement: 'bottom-end' }) }}>
{% if going %}
<button class="btn-link" {{ stimulus_target('forumify/forumify-platform/menu', 'openButton') }}>
<i class="ph ph-check" style="color: green"></i>{{ 'perscom.mission.rsvp.going'|trans }}
</button>
{% endif %}
{% if rsvp is null or going %}
<button class="btn-link" {{
stimulus_action('live', 'action', parameters: { action: 'toggle', going: false })
|stimulus_action('forumify/forumify-platform/menu', 'close')
}}>
{% elseif rsvp is not null %}
<button class="btn-link" {{ stimulus_target('forumify/forumify-platform/menu', 'openButton') }}>
<i class="ph ph-x-circle" style="color: red"></i>{{ 'perscom.mission.rsvp.not_going'|trans }}
</button>
{% endif %}
{% if rsvp is not null %}
<button class="btn-link" {{
stimulus_action('live', 'action', parameters: { action: 'cancel' })
|stimulus_action('forumify/forumify-platform/menu', 'close')
}}>
<i class="ph ph-prohibit-inset" style="color: gray"></i>{{ 'perscom.mission.rsvp.remove_rsvp'|trans }}
{% else %}
<button class="btn-primary" {{ stimulus_target('forumify/forumify-platform/menu', 'openButton') }}>
<i class="ph ph-calendar"></i>{{ 'perscom.mission.rsvp.rsvp'|trans }}
</button>
{% endif %}
<div class="menu" {{ stimulus_target('forumify/forumify-platform/menu', 'menu') }}>
{% if rsvp is null or not going %}
<button class="btn-link" {{
stimulus_action('live', 'action', parameters: { action: 'toggle', going: true })
|stimulus_action('forumify/forumify-platform/menu', 'close')
}}>
<i class="ph ph-check-circle" style="color: green"></i>{{ 'perscom.mission.rsvp.going'|trans }}
</button>
{% endif %}
{% if rsvp is null or going %}
<button class="btn-link" {{
stimulus_action('live', 'action', parameters: { action: 'toggle', going: false })
|stimulus_action('forumify/forumify-platform/menu', 'close')
}}>
<i class="ph ph-x-circle" style="color: red"></i>{{ 'perscom.mission.rsvp.not_going'|trans }}
</button>
{% endif %}
{% if rsvp is not null %}
<button class="btn-link" {{
stimulus_action('live', 'action', parameters: { action: 'cancel' })
|stimulus_action('forumify/forumify-platform/menu', 'close')
}}>
<i class="ph ph-prohibit-inset" style="color: gray"></i>{{ 'perscom.mission.rsvp.remove_rsvp'|trans }}
</button>
{% endif %}
</div>
</div>
</div>
</div>
29 changes: 0 additions & 29 deletions templates/frontend/components/mission_rsvps.html.twig

This file was deleted.

10 changes: 10 additions & 0 deletions templates/frontend/components/mission_stats.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div class="flex gap-10 justify-center mt-2">
<div class="flex flex-col">
<span style="color:green;">{{ this.getGoing }}</span>
{{ 'perscom.mission.rsvp.going'|trans }}
</div>
<div class="flex flex-col">
<span style="color:red;">{{ this.getAbsent }}</span>
{{ 'perscom.mission.rsvp.not_going'|trans }}
</div>
</div>
34 changes: 34 additions & 0 deletions templates/frontend/mission/components/soldier_row.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="flex justify-between items-center w-100">
<div class="w-5 text-left">
{% if soldier.rank is not null and soldier.rank.image is not null %}
<img width="24px" height="24px" src="{{ asset(soldier.rank.image, 'perscom.asset') }}" alt="">
{% endif %}
</div>
<div class="w-35 text-center">
<span class="hide-phone">
{% if soldier.rank is not null %}
{{ soldier.rank.name }}
{% endif %}
</span>
{{ soldier.name }}
</div>
<div class="w-20 text-center hide-phone">
{% if soldier.position is not null %}
{{ soldier.position.name }}
{% endif %}
</div>
<div class="w-20 text-center hide-phone">
{% if soldier.position is not null %}
{{ soldier.specialty.name }}
{% endif %}
</div>
<div class="w-15 text-center">
{% if rsvp is not null %}
{% include '@ForumifyPerscomPlugin/frontend/mission/components/status.html.twig' with {
rsvp: rsvp
} only %}
{% else %}
<span class="text-muted">No RSVP</span>
{% endif %}
</div>
</div>
14 changes: 14 additions & 0 deletions templates/frontend/mission/components/status.html.twig
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps it's a little overkill to have this in a separate twig file? Unless there's a good reason for it, like it's being used in multiple locations?

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% set statusClass = '' %}
{% set statusText = '' %}

{% if rsvp.going == '1' %}
{% set statusClass = 'badge-success' %}
{% set statusText = 'Going' %}
{% set statusColor = 'green' %}
{% elseif rsvp.going == '0' %}
{% set statusClass = 'badge-danger' %}
{% set statusText = 'Not Going' %}
{% set statusColor = 'red' %}
{% endif %}

<span class="badge {{ statusClass }}" style="color:{{ statusColor }};">{{ statusText }}</span>
Loading