Skip to content

Commit

Permalink
Merge pull request #320 from HiEventsDev/feature/attendee-notes
Browse files Browse the repository at this point in the history
Add ability to add notes to an attendee + Refactor Attendee modal
  • Loading branch information
daveearley authored Dec 18, 2024
2 parents 92fd1f3 + 82b5491 commit 8de344e
Show file tree
Hide file tree
Showing 26 changed files with 382 additions and 293 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ abstract class AttendeeDomainObjectAbstract extends \HiEvents\DomainObjects\Abst
final public const UPDATED_AT = 'updated_at';
final public const DELETED_AT = 'deleted_at';
final public const LOCALE = 'locale';
final public const NOTES = 'notes';

protected int $id;
protected int $order_id;
Expand All @@ -47,6 +48,7 @@ abstract class AttendeeDomainObjectAbstract extends \HiEvents\DomainObjects\Abst
protected string $updated_at;
protected ?string $deleted_at = null;
protected string $locale = 'en';
protected ?string $notes = null;

public function toArray(): array
{
Expand All @@ -69,6 +71,7 @@ public function toArray(): array
'updated_at' => $this->updated_at ?? null,
'deleted_at' => $this->deleted_at ?? null,
'locale' => $this->locale ?? null,
'notes' => $this->notes ?? null,
];
}

Expand Down Expand Up @@ -269,4 +272,15 @@ public function getLocale(): string
{
return $this->locale;
}

public function setNotes(?string $notes): self
{
$this->notes = $notes;
return $this;
}

public function getNotes(): ?string
{
return $this->notes;
}
}
1 change: 1 addition & 0 deletions backend/app/Http/Actions/Attendees/EditAttendeeAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function __invoke(EditAttendeeRequest $request, int $eventId, int $attend
'product_price_id' => $request->input('product_price_id'),
'event_id' => $eventId,
'attendee_id' => $attendeeId,
'notes' => $request->input('notes'),
]));
} catch (NoTicketsAvailableException $exception) {
throw ValidationException::withMessages([
Expand Down
3 changes: 3 additions & 0 deletions backend/app/Http/Actions/Orders/GetOrderAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace HiEvents\Http\Actions\Orders;

use HiEvents\DomainObjects\AttendeeDomainObject;
use HiEvents\DomainObjects\EventDomainObject;
use HiEvents\DomainObjects\OrderItemDomainObject;
use HiEvents\DomainObjects\QuestionAndAnswerViewDomainObject;
use HiEvents\Http\Actions\BaseAction;
Expand All @@ -21,6 +22,8 @@ public function __construct(OrderRepositoryInterface $orderRepository)

public function __invoke(int $eventId, int $orderId): JsonResponse
{
$this->isActionAuthorized($eventId, EventDomainObject::class);

$order = $this->orderRepository
->loadRelation(OrderItemDomainObject::class)
->loadRelation(AttendeeDomainObject::class)
Expand Down
2 changes: 2 additions & 0 deletions backend/app/Http/Request/Attendee/EditAttendeeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function rules(): array
'last_name' => RulesHelper::REQUIRED_STRING,
'product_id' => RulesHelper::REQUIRED_NUMERIC,
'product_price_id' => RulesHelper::REQUIRED_NUMERIC,
'notes' => RulesHelper::OPTIONAL_TEXT_MEDIUM_LENGTH,
];
}

Expand All @@ -29,6 +30,7 @@ public function messages(): array
'product_price_id.required' => __('Product price is required'),
'product_id.numeric' => '',
'product_price_id.numeric' => '',
'notes.max' => __('Notes must be less than 2000 characters'),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public function rules(): array
'name' => ['string', 'required', 'max:50'],
'description' => ['string', 'max:255', 'nullable'],
'is_hidden' => ['boolean', 'required'],
'no_products_message' => ['string', 'max:255', 'required'],
'no_products_message' => ['string', 'max:255', 'nullable'],
];
}
}
1 change: 1 addition & 0 deletions backend/app/Resources/Attendee/AttendeeResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function toArray(Request $request): array
'public_id' => $this->getPublicId(),
'short_id' => $this->getShortId(),
'locale' => $this->getLocale(),
'notes' => $this->getNotes(),
'product' => $this->when(
!is_null($this->getProduct()),
fn() => new ProductResource($this->getProduct()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
class EditAttendeeDTO extends BaseDTO
{
public function __construct(
public string $first_name,
public string $last_name,
public string $email,
public int $product_id,
public int $product_price_id,
public int $event_id,
public int $attendee_id,
public string $first_name,
public string $last_name,
public string $email,
public int $product_id,
public int $product_price_id,
public int $event_id,
public int $attendee_id,
public ?string $notes = null,
)
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use HiEvents\DomainObjects\Enums\ProductPriceType;
use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\ProductDomainObjectAbstract;
use HiEvents\DomainObjects\ProductDomainObject;
use HiEvents\DomainObjects\ProductPriceDomainObject;
use HiEvents\Exceptions\NoTicketsAvailableException;
use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface;
Expand Down Expand Up @@ -59,6 +60,7 @@ private function updateAttendee(EditAttendeeDTO $editAttendeeDTO): AttendeeDomai
'last_name' => $editAttendeeDTO->last_name,
'email' => $editAttendeeDTO->email,
'product_id' => $editAttendeeDTO->product_id,
'notes' => $editAttendeeDTO->notes,
], [
'event_id' => $editAttendeeDTO->event_id,
]);
Expand All @@ -70,6 +72,7 @@ private function updateAttendee(EditAttendeeDTO $editAttendeeDTO): AttendeeDomai
*/
private function validateProductId(EditAttendeeDTO $editAttendeeDTO): void
{
/** @var ProductDomainObject $product */
$product = $this->productRepository
->loadRelation(ProductPriceDomainObject::class)
->findFirstWhere([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function handle(UpsertProductCategoryDTO $dto): ProductCategoryDomainObje
isHidden: $dto->is_hidden,
eventId: $dto->event_id,
description: $dto->description,
noProductsMessage: $dto->no_products_message,
noProductsMessage: $dto->no_products_message ?? __('There are no products available in this category'),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function handle(UpsertProductCategoryDTO $dto): ProductCategoryDomainObje
'name' => $dto->name,
'is_hidden' => $dto->is_hidden,
'description' => $dto->description,
'no_products_message' => $dto->no_products_message,
'no_products_message' => $dto->no_products_message ?? __('There are no products available in this category'),
],
where: [
'id' => $dto->product_category_id,
Expand Down
79 changes: 54 additions & 25 deletions backend/app/Services/Domain/Event/DuplicateEventService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use HiEvents\DomainObjects\CapacityAssignmentDomainObject;
use HiEvents\DomainObjects\CheckInListDomainObject;
use HiEvents\DomainObjects\Enums\EventImageType;
use HiEvents\DomainObjects\Enums\QuestionBelongsTo;
use HiEvents\DomainObjects\EventDomainObject;
use HiEvents\DomainObjects\EventSettingDomainObject;
use HiEvents\DomainObjects\ImageDomainObject;
Expand Down Expand Up @@ -79,8 +80,12 @@ public function duplicateEvent(
cloneEventSettings: $duplicateSettings,
);

if ($duplicateQuestions) {
$this->clonePerOrderQuestions($event, $newEvent->getId());
}

if ($duplicateProducts) {
$this->cloneExistingProducts(
$this->cloneExistingTickets(
event: $event,
newEventId: $newEvent->getId(),
duplicateQuestions: $duplicateQuestions,
Expand Down Expand Up @@ -131,7 +136,7 @@ private function cloneExistingEvent(EventDomainObject $event, bool $cloneEventSe
/**
* @throws Throwable
*/
private function cloneExistingProducts(
private function cloneExistingTickets(
EventDomainObject $event,
int $newEventId,
bool $duplicateQuestions,
Expand All @@ -140,20 +145,20 @@ private function cloneExistingProducts(
bool $duplicateCheckInLists,
): array
{
$oldProductToNewProductMap = [];
$oldTicketToNewTicketMap = [];

foreach ($event->getProducts() as $product) {
$product->setEventId($newEventId);
$newProduct = $this->createProductService->createProduct(
product: $product,
foreach ($event->getProducts() as $ticket) {
$ticket->setEventId($newEventId);
$newTicket = $this->createProductService->createTicket(
ticket: $ticket,
accountId: $event->getAccountId(),
taxAndFeeIds: $product->getTaxAndFees()?->map(fn($taxAndFee) => $taxAndFee->getId())?->toArray(),
taxAndFeeIds: $ticket->getTaxAndFees()?->map(fn($taxAndFee) => $taxAndFee->getId())?->toArray(),
);
$oldProductToNewProductMap[$product->getId()] = $newProduct->getId();
$oldTicketToNewTicketMap[$ticket->getId()] = $newTicket->getId();
}

if ($duplicateQuestions) {
$this->cloneQuestions($event, $newEventId, $oldProductToNewProductMap);
$this->clonePerTicketQuestions($event, $newEventId, $oldTicketToNewTicketMap);
}

if ($duplicatePromoCodes) {
Expand All @@ -174,23 +179,47 @@ private function cloneExistingProducts(
/**
* @throws Throwable
*/
private function cloneQuestions(EventDomainObject $event, int $newEventId, array $oldProductToNewProductMap): void
private function clonePerTicketQuestions(EventDomainObject $event, int $newEventId, array $oldTicketToNewTicketMap): void
{
foreach ($event->getQuestions() as $question) {
$this->createQuestionService->createQuestion(
(new QuestionDomainObject())
->setTitle($question->getTitle())
->setEventId($newEventId)
->setBelongsTo($question->getBelongsTo())
->setType($question->getType())
->setRequired($question->getRequired())
->setOptions($question->getOptions())
->setIsHidden($question->getIsHidden()),
array_map(
static fn(ProductDomainObject $product) => $oldProductToNewProductMap[$product->getId()],
$question->getProducts()?->all(),
),
);
if ($question->getBelongsTo() === QuestionBelongsTo::TICKET->name) {
$this->createQuestionService->createQuestion(
(new QuestionDomainObject())
->setTitle($question->getTitle())
->setEventId($newEventId)
->setBelongsTo($question->getBelongsTo())
->setType($question->getType())
->setRequired($question->getRequired())
->setOptions($question->getOptions())
->setIsHidden($question->getIsHidden()),
array_map(
static fn(ProductDomainObject $ticket) => $oldTicketToNewTicketMap[$ticket->getId()],
$question->getTickets()?->all(),
),
);
}
}
}

/**
* @throws Throwable
*/
private function clonePerOrderQuestions(EventDomainObject $event, int $newEventId): void
{
foreach ($event->getQuestions() as $question) {
if ($question->getBelongsTo() === QuestionBelongsTo::ORDER->name) {
$this->createQuestionService->createQuestion(
(new QuestionDomainObject())
->setTitle($question->getTitle())
->setEventId($newEventId)
->setBelongsTo($question->getBelongsTo())
->setType($question->getType())
->setRequired($question->getRequired())
->setOptions($question->getOptions())
->setIsHidden($question->getIsHidden()),
[],
);
}
}
}

Expand Down
1 change: 1 addition & 0 deletions backend/app/Validators/Rules/RulesHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ class RulesHelper

public const REQUIRED_EMAIL = ['email' , 'required', 'max:100'];

public const OPTIONAL_TEXT_MEDIUM_LENGTH = ['string', 'max:2000', 'nullable'];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
public function up(): void
{
Schema::table('attendees', static function (Blueprint $table) {
$table->text('notes')->nullable();
});
}

public function down(): void
{
Schema::table('attendees', static function (Blueprint $table) {
$table->dropColumn('notes');
});
}
};
8 changes: 7 additions & 1 deletion docker/all-in-one/.env
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# See the README.md for informaiton on how to generate the JWT_SECRET and APP_KEY
# See the README.md file for informaiton on how to generate the JWT_SECRET and APP_KEY
APP_KEY=
JWT_SECRET=

# Frontend variables (Always prefixed with VITE_)
VITE_FRONTEND_URL=http://localhost:8123
VITE_API_URL_CLIENT=http://localhost:8123/api
VITE_API_URL_SERVER=http://localhost:80/api
VITE_STRIPE_PUBLISHABLE_KEY=pk_test

# Backend variables
# These values may not be suitable for production environments.
# Please refer to the documentation for more information on how to configure these values
# https://hi.events/docs/getting-started/deploying
LOG_CHANNEL=stderr
QUEUE_CONNECTION=sync
MAIL_MAILER=log
Expand All @@ -15,5 +20,6 @@ FILESYSTEM_PUBLIC_DISK=public
FILESYSTEM_PRIVATE_DISK=local

APP_CDN_URL=http://localhost:8123/storage
APP_FRONTEND_URL=http://localhost:8123

DATABASE_URL=postgresql://postgres:secret@postgres:5432/hi-events
1 change: 1 addition & 0 deletions docker/all-in-one/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
- QUEUE_CONNECTION=${QUEUE_CONNECTION}
- MAIL_MAILER=${MAIL_MAILER}
- APP_KEY=${APP_KEY}
- APP_FRONTEND_URL=${APP_FRONTEND_URL}
- JWT_SECRET=${JWT_SECRET}
- FILESYSTEM_PUBLIC_DISK=${FILESYSTEM_PUBLIC_DISK}
- FILESYSTEM_PRIVATE_DISK=${FILESYSTEM_PRIVATE_DISK}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/api/attendee.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface EditAttendeeRequest {
first_name: string;
last_name: string;
email: string;
notes?: string;
product_id?: IdParam;
product_price_id?: IdParam;
status?: string;
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/common/AttendeeDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {getLocaleName, SupportedLocales} from "../../../locales.ts";

export const AttendeeDetails = ({attendee}: { attendee: Attendee }) => {
return (
<div className={classes.orderDetails} variant={'lightGray'}>
<div className={classes.orderDetails}>
<div className={classes.block}>
<div className={classes.title}>
{t`Name`}
Expand All @@ -29,7 +29,7 @@ export const AttendeeDetails = ({attendee}: { attendee: Attendee }) => {
{t`Status`}
</div>
<div className={classes.amount}>
{attendee.status}
{attendee.status === 'ACTIVE' ? <span style={{color: '#0d7553'}}>{t`Active`}</span> : <span style={{color: '#EF4444'}}>{t`Canceled`}</span>}
</div>
</div>
<div className={classes.block}>
Expand Down
Loading

0 comments on commit 8de344e

Please sign in to comment.