diff --git a/config/packages/catalyst_auth.yaml b/config/packages/catalyst_auth.yaml index 324d0812..596ce6de 100644 --- a/config/packages/catalyst_auth.yaml +++ b/config/packages/catalyst_auth.yaml @@ -384,6 +384,20 @@ catalyst_auth: - id: partner.delete label: Delete + - id: motolite_event + label: Motolite Events + acls: + - id: motolite_event.menu + label: Menu + - id: motolite_event.list + label: List + - id: motolite_event.add + label: Add + - id: motolite_event.update + label: Update + - id: motolite_event.delete + label: Delete + - id: review label: Reviews acls: diff --git a/config/packages/catalyst_menu.yaml b/config/packages/catalyst_menu.yaml index 328321b8..5d0cb29f 100644 --- a/config/packages/catalyst_menu.yaml +++ b/config/packages/catalyst_menu.yaml @@ -235,11 +235,21 @@ catalyst_menu: label: '[menu.partner.reviews]' parent: partner + - id: motolite_event + acl: motolite_event.menu + label: '[menu.motolite_event]' + icon: flaticon-event-calendar-symbol + order: 13 + - id: motolite_event_list + acl: motolite_event.list + label: '[menu.motolite_event.events]' + parent: motolite_event + - id: analytics acl: analytics.menu label: '[menu.analytics]' icon: flaticon-graphic - order: 13 + order: 14 - id: analytics_forecast_form acl: analytics.forecast label: '[menu.analytics.forecasting]' @@ -249,7 +259,7 @@ catalyst_menu: acl: database.menu label: '[menu.database]' icon: fa fa-database - order: 14 + order: 15 - id: ticket_type_list acl: ticket_type.menu label: '[menu.database.tickettypes]' diff --git a/config/routes/motolite_event.yaml b/config/routes/motolite_event.yaml new file mode 100644 index 00000000..865a6162 --- /dev/null +++ b/config/routes/motolite_event.yaml @@ -0,0 +1,38 @@ +motolite_event_list: + path: /motolite_events + controller: App\Controller\MotoliteEventController::index + +motolite_event_rows: + path: /motolite_events/rows + controller: App\Controller\MotoliteEventController::rows + methods: [POST] + +motolite_event_create: + path: /motolite_events/create + controller: App\Controller\MotoliteEventController::addForm + methods: [GET] + +motolite_event_create_submit: + path: /motolite_events/create + controller: App\Controller\MotoliteEventController::addSubmit + methods: [POST] + +motolite_event_upload_image: + path: /motolite_events/upload + controller: App\Controller\MotoliteEventController::uploadImage + methods: [POST] + +motolite_event_update: + path: /motolite_events/{id} + controller: App\Controller\MotoliteEventController::updateForm + methods: [GET] + +motolite_event_update_submit: + path: /motolite_events/{id} + controller: App\Controller\MotoliteEventController::updateSubmit + methods: [POST] + +motolite_event_delete: + path: /motolite_events/{id} + controller: App\Controller\MotoliteEventController::destroy + methods: [DELETE] diff --git a/public/assets/images/image.gif b/public/assets/images/image.gif new file mode 100755 index 00000000..c32e7886 Binary files /dev/null and b/public/assets/images/image.gif differ diff --git a/src/Controller/CustomerAppAPI/MotoliteEventController.php b/src/Controller/CustomerAppAPI/MotoliteEventController.php index 93f924f2..9565f656 100644 --- a/src/Controller/CustomerAppAPI/MotoliteEventController.php +++ b/src/Controller/CustomerAppAPI/MotoliteEventController.php @@ -37,6 +37,7 @@ class MotoliteEventController extends ApiController 'id' => $result->getID(), 'name' => $result->getName(), 'event_type' => MotoliteEventType::getName($result->getEventType()), + 'event_time' => $result->getEventTime()->format("d M Y g:i A"), 'url' => $result->getUrl(), 'image_file' => $result->getImageFile(), ]; diff --git a/src/Controller/MotoliteEventController.php b/src/Controller/MotoliteEventController.php new file mode 100644 index 00000000..3807f49e --- /dev/null +++ b/src/Controller/MotoliteEventController.php @@ -0,0 +1,304 @@ +denyAccessUnlessGranted('motolite_event.list', null, 'No access.'); + + return $this->render('motolite-event/list.html.twig'); + } + + public function rows(Request $req) + { + $this->denyAccessUnlessGranted('motolite_event.list', null, 'No access.'); + + // get query builder + $qb = $this->getDoctrine() + ->getRepository(MotoliteEvent::class) + ->createQueryBuilder('q'); + + // get datatable params + $datatable = $req->request->get('datatable'); + + // count total records + $tquery = $qb->select('COUNT(q)'); + $this->setQueryFilters($datatable, $tquery); + $total = $tquery->getQuery() + ->getSingleScalarResult(); + + // get current page number + $page = $datatable['pagination']['page'] ?? 1; + + $perpage = $datatable['pagination']['perpage']; + $offset = ($page - 1) * $perpage; + + // add metadata + $meta = [ + 'page' => $page, + 'perpage' => $perpage, + 'pages' => ceil($total / $perpage), + 'total' => $total, + 'sort' => 'asc', + 'field' => 'id' + ]; + + // build query + $query = $qb->select('q'); + $this->setQueryFilters($datatable, $query); + + // check if sorting is present, otherwise use default + if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { + $order = $datatable['sort']['sort'] ?? 'asc'; + $query->orderBy('q.' . $datatable['sort']['field'], $order); + } else { + $query->orderBy('q.event_time', 'asc'); + } + + // get rows for this page + $obj_rows = $query->setFirstResult($offset) + ->setMaxResults($perpage) + ->getQuery() + ->getResult(); + + // process rows + $rows = []; + foreach ($obj_rows as $orow) { + // add row data + $row['id'] = $orow->getID(); + $row['name'] = $orow->getName(); + $row['event_type'] = MotoliteEventType::getName($orow->getEventType()); + $row['event_time'] = $orow->getEventTime()->format("d M Y g:i A"); + $row['url'] = 'Link'; + $row['image_file'] = $orow->getImageFile(); + + // add row metadata + $row['meta'] = [ + 'update_url' => '', + 'delete_url' => '' + ]; + + // add crud urls + if ($this->isGranted('motolite_event.update')) + $row['meta']['update_url'] = $this->generateUrl('motolite_event_update', ['id' => $row['id']]); + if ($this->isGranted('motolite_event.delete')) + $row['meta']['delete_url'] = $this->generateUrl('motolite_event_delete', ['id' => $row['id']]); + + $rows[] = $row; + } + + // response + return $this->json([ + 'meta' => $meta, + 'data' => $rows + ]); + } + + /** + * @Menu(selected="motolite_event_list") + */ + public function addForm() + { + $this->denyAccessUnlessGranted('motolite_event.add', null, 'No access.'); + + $params = []; + $params['obj'] = new MotoliteEvent(); + $params['mode'] = 'create'; + + $params['event_types'] = MotoliteEventType::getCollection(); + + $em = $this->getDoctrine()->getManager(); + + // response + return $this->render('motolite-event/form.html.twig', $params); + } + + public function addSubmit(Request $req, EncoderFactoryInterface $ef, ValidatorInterface $validator) + { + $this->denyAccessUnlessGranted('motolite_event.add', null, 'No access.'); + + // create new object + $em = $this->getDoctrine()->getManager(); + $obj = new MotoliteEvent(); + + $this->setObject($obj, $em, $req); + + // validate + $errors = $validator->validate($obj); + + // initialize error list + $error_array = []; + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + // validated! save the entity + $em->persist($obj); + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + /** + * @Menu(selected="motolite_event_list") + */ + public function updateForm($id) + { + $this->denyAccessUnlessGranted('motolite_event.update', null, 'No access.'); + + // get row data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(MotoliteEvent::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + $params = []; + $params['obj'] = $obj; + $params['mode'] = 'update'; + + $params['event_types'] = MotoliteEventType::getCollection(); + + // response + return $this->render('motolite-event/form.html.twig', $params); + } + + public function updateSubmit(Request $req, EncoderFactoryInterface $ef, ValidatorInterface $validator, $id) + { + $this->denyAccessUnlessGranted('motolite_event.update', null, 'No access.'); + + // get object data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(MotoliteEvent::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + $this->setObject($obj, $em, $req); + + // validate + $errors = $validator->validate($obj); + + // initialize error list + $error_array = []; + + // add errors to list + foreach ($errors as $error) { + $error_array[$error->getPropertyPath()] = $error->getMessage(); + } + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + // validated! save the entity + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + public function destroy($id) + { + $this->denyAccessUnlessGranted('motolite_event.delete', null, 'No access.'); + + // get object data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(MotoliteEvent::class)->find($id); + + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + // delete this object + $em->remove($obj); + $em->flush(); + + // response + $response = new Response(); + $response->setStatusCode(Response::HTTP_OK); + $response->send(); + } + + protected function setQueryFilters($datatable, QueryBuilder $query) + { + if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { + $query->where('q.name LIKE :filter') + ->orWhere('q.url LIKE :filter') + ->orWhere('q.event_type LIKE :filter') + ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); + } + } + + protected function setObject(MotoliteEvent $obj, EntityManager $em, Request $req) + { + // times + $format = "Y-m-d\TH:i"; + $event_time = DateTime::createFromFormat($format, $req->request->get('event_time')); + + $event_type = $req->request->get('event_type'); + + // set and save values + $obj->setName($req->request->get('name')) + ->setEventType($event_type) + ->setEventTime($event_time) + ->setUrl($req->request->get('url')) + ->setImageFile($req->request->get('image_file')); + } + + public function uploadImage(Request $req, FileUploader $uploader) + { + // retrieve temporary info for file + $file = $req->files->get('image_file'); + + // upload the file + $filename = $uploader->upload($file); + + // return response + return $this->json([ + 'success' => true, + 'filename' => $filename + ]); + } +} diff --git a/src/Entity/MotoliteEvent.php b/src/Entity/MotoliteEvent.php index 4349b44b..ccdb367b 100644 --- a/src/Entity/MotoliteEvent.php +++ b/src/Entity/MotoliteEvent.php @@ -4,7 +4,6 @@ namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; -use Symfony\Component\HttpFoundation\File\File; use DateTime; @@ -48,13 +47,12 @@ class MotoliteEvent /** * @ORM\Column(type="string") * @Assert\NotBlank() - * @Assert\File(mimeTypes={"image/png", "image/jpeg", "image/gif", "image/webp"}) */ protected $image_file; public function __construct() { - $this->event_time = new DateTime(); + $this->event_time = new DateTime('today noon'); } public function setName($name) @@ -112,7 +110,7 @@ class MotoliteEvent return $this->event_time; } - public function setImageFile(File $image_file = null) + public function setImageFile($image_file = null) { $this->image_file = $image_file; return $this; diff --git a/templates/motolite-event/form.html.twig b/templates/motolite-event/form.html.twig new file mode 100644 index 00000000..8abdb362 --- /dev/null +++ b/templates/motolite-event/form.html.twig @@ -0,0 +1,223 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Motolite Events

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update' %} + Edit Event + {{ obj.getName() }} + {% else %} + New Event + {% endif %} +

+
+
+
+
+
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+
+

+ Drop files here or click to upload. +

+ + Upload only valid PNG, GIF, or JPEG images + +
+
+ {% if mode == 'update' and obj.getImageFile %} + Leave blank for unchanged + {% endif %} + +
+ {% if mode == 'update' %} +
+ +
+
+ {% endif %} +
+
+
+
+
+
+
+
+ + + Back +
+
+
+
+
+
+
+
+
+{% endblock %} + + +{% block scripts %} + +{% endblock %} diff --git a/templates/motolite-event/list.html.twig b/templates/motolite-event/list.html.twig new file mode 100644 index 00000000..550c4221 --- /dev/null +++ b/templates/motolite-event/list.html.twig @@ -0,0 +1,155 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

+ Motolite Events +

+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 42a5aa54..01df542d 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -145,6 +145,9 @@ menu.partner: 'Partners' menu.partner.partners: 'Partners' menu.partner.reviews: 'Reviews' +menu.motolite_event: 'Motolite Events' +menu.motolite_event.events: 'Events' + menu.analytics: 'Analytics' menu.analytics.forecasting: 'Forecasting'