first draft of case addresses and itineraries

This commit is contained in:
Ryan Prather 2024-12-31 23:01:37 +00:00
parent 1b5ca4bd34
commit 6c340b4229
14 changed files with 1272 additions and 8 deletions

View File

@ -2,15 +2,19 @@
namespace App\Controller;
use App\Entity\CaseLocation;
use App\Entity\Location;
use App\Entity\MemberCase;
use App\Entity\Messages;
use App\Entity\ReferralSource;
use App\Entity\User;
use App\Entity\UserCase;
use App\Factory\MessageFactory;
use App\Form\LocationFormType;
use App\Form\MemberCaseFormType;
use App\Form\UserCaseFormType;
use App\Libs\Breadcrumb;
use App\Libs\Libs;
use App\Libs\NavList;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -39,10 +43,11 @@ class CaseController extends AbstractController
}
#[Route('/my-cases', name: 'app_my_cases')]
public function myCases(#[CurrentUser()] User $user): Response
public function myCases(#[CurrentUser()] User $user, Request $request): Response
{
$this->navLinks['my_cases'] = NavList::PRESENT_LINK;
$this->navLinks['case_list'] = 'nav-link text-dark';
$ucs = $this->entityManager->getRepository(UserCase::class)->findBy(['user' => $user]);
$this->msgs = $this->entityManager->getRepository(Messages::class)->getUnreadMessages($user);
$this->notificationCount = $this->entityManager->getRepository(Messages::class)->getUnreadMessageCount($user);
@ -59,7 +64,11 @@ class CaseController extends AbstractController
$this->navLinks,
[
'breadcrumbs' => [
new Breadcrumb($this->generateUrl('app_my_cases'), 'List Cases')
(
strpos($request->server->get('HTTP_REFERER'), 'list-cases') !== false
? new Breadcrumb($this->generateUrl('app_list_cases'), 'List Cases')
: new Breadcrumb($this->generateUrl('app_my_cases'), 'My Cases')
),
],
'notifications' => $this->msgs,
'cases' => $cases,
@ -276,6 +285,176 @@ class CaseController extends AbstractController
return new Response();
}
#[Route('/addresses/list', name: 'app_list_case_addresses')]
public function listCaseAddresses(Request $request, #[CurrentUser()] User $user): Response
{
$this->navLinks['case_itinerary'] = NavList::PRESENT_LINK;
$this->navLinks['case_list'] = 'nav-link text-dark';
$addresses = $this->entityManager->getRepository(Location::class)->getUserLocations($user);
$this->msgs = $this->entityManager->getRepository(Messages::class)->getUnreadMessages($user);
$this->notificationCount = $this->entityManager->getRepository(Messages::class)->getUnreadMessageCount($user);
$ucs = $this->entityManager->getRepository(UserCase::class)->findBy(['user' => $user]);
$cases = [];
foreach ($ucs as $uc) {
/** @var UserCase $uc */
$cases[] = $uc->getMemberCase();
}
return $this->render(
'internal/cases/addresses/list-case-addresses.html.twig',
array_merge(
$this->navLinks,
[
'title' => 'List Case Addresses',
'breadcrumbs' => [
new Breadcrumb($this->generateUrl('app_my_cases'), 'My Cases'),
new Breadcrumb($this->generateUrl('app_list_case_addresses'), 'List Case Addresses')
],
'notifications' => $this->msgs,
'notificationCount' => $this->notificationCount,
'addresses' => $addresses,
'cases' => $cases,
]
)
);
}
#[Route('/addresses/add', name: 'app_case_add_address')]
public function addCaseAddress(Request $request, #[CurrentUser()] User $user): Response
{
$this->navLinks['case_itinerary'] = NavList::PRESENT_LINK;
$this->navLinks['case_list'] = 'nav-link text-dark';
$this->msgs = $this->entityManager->getRepository(Messages::class)->getUnreadMessages($user);
$this->notificationCount = $this->entityManager->getRepository(Messages::class)->getUnreadMessageCount($user);
$ucs = $this->entityManager->getRepository(UserCase::class)->findBy(['user' => $user]);
$cases = [];
foreach ($ucs as $uc) {
/** @var UserCase $uc */
$cases[] = $uc->getMemberCase();
}
$address = new Location();
$form = $this->createForm(LocationFormType::class, $address);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var Location $address */
$address = $form->getData();
foreach ($request->get('cases') as $caseId) {
$case = $this->entityManager->getRepository(MemberCase::class)->find($caseId);
$cl = new CaseLocation();
$cl->setMemberCase($case)
->setLocation($address);
$this->entityManager->persist($cl);
}
list($lat, $lon) = Libs::getLatLonFromGeoapify((string) $address);
$address->setLat($lat)
->setLon($lon);
//dd($address);
$this->entityManager->persist($address);
$this->entityManager->flush();
$this->addFlash('success', 'Address added successfully');
return $this->redirectToRoute('app_list_case_addresses');
}
return $this->render(
'internal/cases/addresses/add-case-address.html.twig',
array_merge(
$this->navLinks,
[
'title' => 'Add Case Address',
'breadcrumbs' => [
new Breadcrumb($this->generateUrl('app_list_case_addresses'), 'List Addresses'),
new Breadcrumb($this->generateUrl('app_case_add_address'), 'Add Case Address')
],
'notifications' => $this->msgs,
'notificationCount' => $this->notificationCount,
'form' => $form,
'cases' => $cases,
]
)
);
}
#[Route('/addresses/edit/{id}', name: 'app_case_edit_address')]
public function editCaseAddress(Request $request, string $id): Response
{
$this->navLinks['case_itinerary'] = NavList::PRESENT_LINK;
$this->navLinks['case_list'] = 'nav-link text-dark';
$this->msgs = $this->entityManager->getRepository(Messages::class)->getUnreadMessages($this->getUser());
$this->notificationCount = $this->entityManager->getRepository(Messages::class)->getUnreadMessageCount($this->getUser());
$ucs = $this->entityManager->getRepository(UserCase::class)->findBy(['user' => $this->getUser()]);
$lcs = $this->entityManager->getRepository(CaseLocation::class)->findBy(['location' => $id]);
$inCases = [];
foreach ($lcs as $lc) {
$inCases[] = $lc->getMemberCase()->getId()->toString();
}
$cases = [];
foreach ($ucs as $uc) {
/** @var UserCase $uc */
$cases[] = $uc->getMemberCase();
}
$location = $this->entityManager->getRepository(Location::class)->find($id);
$form = $this->createForm(LocationFormType::class, $location);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
list($lat, $lon) = Libs::getLatLonFromGeoapify((string) $location);
$location->setLat($lat)
->setLon($lon);
$this->entityManager->flush();
$this->addFlash('success', 'Address updated successfully');
return $this->redirectToRoute('app_list_case_addresses');
}
return $this->render(
'internal/cases/addresses/edit-case-address.html.twig',
array_merge(
$this->navLinks,
[
'title' => 'Edit Case Address',
'breadcrumbs' => [
new Breadcrumb($this->generateUrl('app_list_case_addresses'), 'List Case Addresses'),
new Breadcrumb($this->generateUrl('app_case_edit_address', ['id' => $id]), 'Edit Case Address')
],
'location' => $location,
'cases' => $cases,
'inCases' => $inCases,
'form' => $form,
'notifications' => $this->msgs,
'notificationCount' => $this->notificationCount,
]
)
);
}
#[Route('/api/filter-address-by-case/{caseId}', name: 'ajax_filter_address_by_case')]
public function filterAddressByCase(string $caseId, Request $request): Response
{
$case = $this->entityManager->getRepository(MemberCase::class)->find($caseId);
$addresses = $this->entityManager->getRepository(Location::class)->getLocationsByCase($case);
return $this->json($addresses);
}
#[Route('/api/case/{caseId}/user/{userId}', name: 'ajax_case_user_level_check')]
public function checkUserCaseLevel(string $caseId, string $userId) : Response
{

View File

@ -0,0 +1,145 @@
<?php
namespace App\Controller;
use App\Entity\CaseItinerary;
use App\Entity\CaseLocation;
use App\Entity\Location;
use App\Entity\MemberCase;
use App\Entity\Messages;
use App\Entity\User;
use App\Libs\Breadcrumb;
use App\Libs\Libs;
use App\Libs\NavList;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\UX\Map\InfoWindow;
use Symfony\UX\Map\Map;
use Symfony\UX\Map\Marker;
use Symfony\UX\Map\Point;
use Symfony\UX\Map\Polyline;
class ItineraryController extends AbstractController
{
private array $msgs = [];
private int $notificationCount = 0;
public function __construct(
private EntityManagerInterface $entityManager,
private array $navLinks = []
) {
$this->navLinks = NavList::LIST;
}
#[Route('/itinerary/map', name: 'app_map_itinerary')]
public function mapItinerary(Request $request, #[CurrentUser()] ?User $user): Response
{
$this->msgs = $this->entityManager->getRepository(Messages::class)->getUnreadMessages($user);
$this->notificationCount = $this->entityManager->getRepository(Messages::class)->getUnreadMessageCount($user);
$itineraries = $this->entityManager->getRepository(CaseItinerary::class)->findBy([
'memberCase' => $request->getPayload()->get('caseId'),
'date' => new DateTime($request->getPayload()->get('caseDate'))
]);
$map = new Map('default');
$map->center(new Point(39.768502, -86.157918))
->zoom(9)
;
$total_distance = 0;
$total_duration = 0;
foreach ($itineraries as $itinerary) {
/** @var CaseItinerary $itinerary */
$map->addPolyline(new Polyline(
points: $itinerary->getGPSPolyLines(),
infoWindow: new InfoWindow(
content: $itinerary->getMemberCase()->getCaseName()
)
));
$total_distance += $itinerary->getDistance();
$total_duration += $itinerary->getDuration()->s;
}
return $this->render(
'internal/cases/itinerary/map.html.twig',
array_merge(
$this->navLinks,
[
'breadcrumbs' => [
new Breadcrumb($this->generateUrl('app_my_cases'), 'My Cases'),
],
'notifications' => $this->msgs,
'notificationCount' => $this->notificationCount,
'map' => $map,
'total_distance' => $total_distance,
'total_duration' => $total_duration,
]
)
);
}
#[Route('/api/get-case-locations/{caseId}', name: 'get_case_locations')]
public function createItinerary(string $caseId): Response
{
$case = $this->entityManager->getRepository(MemberCase::class)->find($caseId);
$cls = $this->entityManager->getRepository(CaseLocation::class)->getCaseLocations($case);
$locations = [];
foreach ($cls as $cl) {
/** @var CaseLocation $cl */
$locations[] = $cl->getLocation();
}
return $this->json($locations);
}
#[Route('/api/add-location-to-itinerary', name: 'add_location_to_itinerary')]
public function addLocationToItinerary(Request $request, Session $session): Response
{
$case = $this->entityManager->getRepository(MemberCase::class)->find($request->getPayload()->get('caseId'));
$origin = $this->entityManager->getRepository(Location::class)->find($request->getPayload()->get('origin'));
$destination = $this->entityManager->getRepository(Location::class)->find($request->getPayload()->get('destination'));
$departure = $request->getPayload()->get('departure');
$arrival = $request->getPayload()->get('arrival');
$caseMileage = (bool) $request->getPayload()->get('caseMileage');
$date = new DateTime($request->getPayload()->get('date'));
$route = Libs::getRouteDistance($origin, $destination);
if (!$route) {
return $this->json(['success' => false, 'message' => 'No route found']);
}
$ci = new CaseItinerary();
$ci->setMemberCase($case)
->setDate($date)
->setCaseMileage($caseMileage)
->setOriginLocation($origin)
->setDestLocation($destination)
->setDeparture(new \DateTimeImmutable($departure))
->setArrival(new \DateTimeImmutable($arrival))
->setDistance($route->getDistance())
->setDuration($route->getDuration())
->setGpsRoute($route->getGeometry())
;
$this->entityManager->persist($ci);
$this->entityManager->flush();
$session->getFlashBag()->add(
'success',
'Location added to itinerary'
);
return $this->json(['success' => true, 'message' => 'Location added to itinerary']);
}
}

View File

@ -0,0 +1,194 @@
<?php
namespace App\Entity;
use App\Repository\CaseItineraryRepository;
use DateInterval;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Symfony\UX\Map\Point;
#[ORM\Entity(repositoryClass: CaseItineraryRepository::class)]
class CaseItinerary
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
private ?Uuid $id = null;
#[ORM\Column(type: Types::TIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $departure = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?Location $originLocation = null;
#[ORM\Column(type: Types::TIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $arrival = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?Location $destLocation = null;
#[ORM\Column]
private ?bool $caseMileage = null;
#[ORM\Column]
private ?DateInterval $duration = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?MemberCase $memberCase = null;
#[ORM\Column(type: Types::DATE_MUTABLE)]
private ?\DateTimeInterface $date = null;
#[ORM\Column(nullable: true)]
private ?float $distance = null;
#[ORM\Column(nullable: true)]
private ?array $gpsRoute = null;
public function getId(): ?Uuid
{
return $this->id;
}
public function getDeparture(): ?\DateTimeInterface
{
return $this->departure;
}
public function setDeparture(?\DateTimeInterface $departure): static
{
$this->departure = $departure;
return $this;
}
public function getOriginLocation(): ?Location
{
return $this->originLocation;
}
public function setOriginLocation(?Location $originLocation): static
{
$this->originLocation = $originLocation;
return $this;
}
public function getArrival(): ?\DateTimeInterface
{
return $this->arrival;
}
public function setArrival(?\DateTimeInterface $arrival): static
{
$this->arrival = $arrival;
return $this;
}
public function getDestLocation(): ?Location
{
return $this->destLocation;
}
public function setDestLocation(?Location $destLocation): static
{
$this->destLocation = $destLocation;
return $this;
}
public function isCaseMileage(): ?bool
{
return $this->caseMileage;
}
public function setCaseMileage(bool $caseMileage): static
{
$this->caseMileage = $caseMileage;
return $this;
}
public function getDuration(): ?DateInterval
{
return $this->duration;
}
public function setDuration(?DateInterval $duration): static
{
//$this->calcDuration();
$this->duration = $duration;
return $this;
}
public function calcDuration()
{
$this->duration = $this->departure - $this->arrival;
}
public function getMemberCase(): ?MemberCase
{
return $this->memberCase;
}
public function setMemberCase(?MemberCase $memberCase): static
{
$this->memberCase = $memberCase;
return $this;
}
public function getDate(): ?\DateTimeInterface
{
return $this->date;
}
public function setDate(\DateTimeInterface $date): static
{
$this->date = $date;
return $this;
}
public function getDistance(): ?float
{
return $this->distance;
}
public function setDistance(?float $distance): static
{
$this->distance = $distance;
return $this;
}
public function getGpsRoute(): ?array
{
return $this->gpsRoute;
}
public function setGpsRoute(?array $gpsRoute): static
{
$this->gpsRoute = $gpsRoute;
return $this;
}
public function getGPSPolyLines(): array
{
$points = [];
foreach ($this->gpsRoute as $route) {
$points[] = new Point($route['lat'], $route['lon']);
}
return $points;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Entity;
use App\Repository\CaseLocationRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: CaseLocationRepository::class)]
class CaseLocation
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
private ?Uuid $id = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?MemberCase $memberCase = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false)]
private ?Location $location = null;
public function getId(): ?int
{
return $this->id;
}
public function getMemberCase(): ?MemberCase
{
return $this->memberCase;
}
public function setMemberCase(?MemberCase $memberCase): static
{
$this->memberCase = $memberCase;
return $this;
}
public function getLocation(): ?Location
{
return $this->location;
}
public function setLocation(?Location $location): static
{
$this->location = $location;
return $this;
}
}

140
src/Entity/Location.php Normal file
View File

@ -0,0 +1,140 @@
<?php
namespace App\Entity;
use App\Enums\State;
use App\Repository\LocationRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
#[ORM\Entity(repositoryClass: LocationRepository::class)]
class Location
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
private ?Uuid $id = null;
#[ORM\Column(length: 45)]
private ?string $name = null;
#[ORM\Column(length: 255)]
private ?string $address = null;
#[ORM\Column(length: 100, nullable: true)]
private ?string $city = null;
#[ORM\Column(length: 10, nullable: true)]
private ?State $state = null;
#[ORM\Column(nullable: true)]
private ?int $zip = null;
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 6, nullable: true)]
private ?string $lat = null;
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 6, nullable: true)]
private ?string $lon = null;
public function getId(): ?Uuid
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getAddress(): ?string
{
return $this->address;
}
public function setAddress(string $address): static
{
$this->address = $address;
return $this;
}
public function getCity(): ?string
{
return $this->city;
}
public function setCity(?string $city): static
{
$this->city = $city;
return $this;
}
public function getState(): ?State
{
return $this->state;
}
public function setState(?State $state): static
{
$this->state = $state;
return $this;
}
public function getZip(): ?int
{
return $this->zip;
}
public function setZip(?int $zip): static
{
$this->zip = $zip;
return $this;
}
public function getFormattedAddress(): string
{
return "{$this->address}<br/>{$this->city}, {$this->state->value} {$this->zip}";
}
public function getLat(): ?string
{
return $this->lat;
}
public function setLat(?string $lat): static
{
$this->lat = $lat;
return $this;
}
public function getLon(): ?string
{
return $this->lon;
}
public function setLon(?string $lon): static
{
$this->lon = $lon;
return $this;
}
public function __toString(): string
{
return "{$this->address} {$this->city}, {$this->state->value} {$this->zip}";
}
}

View File

@ -2,6 +2,8 @@
namespace App\Libs;
use App\Entity\Location;
class Libs
{
public static function getLatLonFromGeoapify($address): ?array
@ -24,7 +26,7 @@ class Libs
return null;
}
public static function getRoute($lat1, $lon1, $lat2, $lon2): ?array
public static function getRoute($lat1, $lon1, $lat2, $lon2): ?Route
{
$params = [
'waypoints' => "{$lat1},{$lon1}|{$lat2},{$lon2}",
@ -34,7 +36,6 @@ class Libs
'apiKey' => $_ENV['GEOAPIFY_API_KEY']
];
$url = "https://api.geoapify.com/v1/routing?".http_build_query($params);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
@ -43,10 +44,18 @@ class Libs
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$result = curl_exec($ch);
curl_close($ch);
$result = json_decode($result, true);
dd($result);
if (isset($result['features'][0]['geometry']['coordinates'])) {
$route = $result['features'][0]['geometry']['coordinates'];
$route = new Route(json_decode($result));
if (is_a($route, Route::class)) {
return $route;
}
return null;
}
public static function getRouteDistance(Location $origin, Location $destination): ?Route
{
$route = self::getRoute($origin->getLat(), $origin->getLon(), $destination->getLat(), $destination->getLon());
if ($route) {
return $route;
}
return null;

47
src/Libs/Route.php Normal file
View File

@ -0,0 +1,47 @@
<?php
namespace App\Libs;
use DateInterval;
use DateTime;
class Route
{
private float $distance;
private float $duration;
private array $geometry;
public function __construct(
object $route
) {
$this->distance = $route->results[0]->distance;
$this->duration = $route->results[0]->time;
$this->geometry = $route->results[0]->geometry[0];
}
public function getDuration(): ?DateInterval
{
$startDate = new DateTime("@0");
$endDate = new DateTime("@{$this->duration}");
$dur = $startDate->diff($endDate);
return $dur;
}
public function getDurationString(): string
{
return $this->getDuration()->format('%H:%I:%S');
}
public function getDistance(): float
{
return $this->distance;
}
public function getGeometry(): array
{
return $this->geometry;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\CaseItinerary;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<CaseItinerary>
*/
class CaseItineraryRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, CaseItinerary::class);
}
// /**
// * @return CaseLocation[] Returns an array of CaseLocation objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('c.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?CaseLocation
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Repository;
use App\Entity\CaseLocation;
use App\Entity\Location;
use App\Entity\MemberCase;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<CaseLocation>
*/
class CaseLocationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, CaseLocation::class);
}
public function getCaseLocations(MemberCase $case): array
{
return $this->createQueryBuilder('cl')
->leftJoin(Location::class, 'l', 'WITH', 'l.id = cl.location')
->andWhere('cl.memberCase = :case')
->setParameter('case', $case->getId()->toBinary())
->orderBy('l.name', 'ASC')
->getQuery()
->getResult()
;
}
// /**
// * @return CaseLocation[] Returns an array of CaseLocation objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('c.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?CaseLocation
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Repository;
use App\Entity\CaseLocation;
use App\Entity\Location;
use App\Entity\MemberCase;
use App\Entity\User;
use App\Entity\UserCase;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Locations>
*/
class LocationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Location::class);
}
public function getUserLocations(User $user): array
{
$query = $this->createQueryBuilder('location')
->leftJoin(CaseLocation::class, 'cl', 'WITH', 'cl.location = location.id')
->leftJoin(UserCase::class, 'uc', 'WITH', 'uc.memberCase = cl.memberCase')
->andWhere('uc.user = :user')
->setParameter('user', $user->getId()->toBinary())
->orderBy('location.name', 'ASC')
->getQuery()
->getResult()
;
return $query;
}
public function getLocationsByCase(MemberCase $case): array
{
return $this->createQueryBuilder('location')
->leftJoin(CaseLocation::class, 'cl', 'WITH', 'cl.location = location.id')
->andWhere('cl.memberCase = :case')
->setParameter('case', $case->getId()->toBinary())
->orderBy('location.name', 'ASC')
->getQuery()
->getResult()
;
}
// /**
// * @return Locations[] Returns an array of Locations objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('l')
// ->andWhere('l.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('l.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Locations
// {
// return $this->createQueryBuilder('l')
// ->andWhere('l.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@ -0,0 +1,76 @@
{% extends 'base.html.twig' %}
{% block body %}
{{ block('nav', 'internal/libs/nav.html.twig') }}
<main class="main-content position-relative max-height-vh-100 h-100 border-radius-lg ">
{{ block('topnav', 'internal/libs/top-nav.html.twig') }}
<section>
<div class="card card-plain">
<div class="card-header">
<h4 class="font-weight-bolder">Add Address</h4>
<p class="mb-0"></p>
</div>
<div class="card-body">
<div class="container">
{{ form_errors(form) }}
{{ form_start(form) }}
<div class="row">
<div class='col'>
<div class='input-group input-group-outline mb-3'>
<label for='address_form_name' class='form-label'>Name</label>
<input type='text' name='{{ field_name(form.name) }}' id='address_form_nameame' class='form-control' required='required'/>
</div>
<div class='input-group input-group-outline mb-3'>
{% for c in cases %}
<div>
<input type='checkbox' name='cases[]' id='{{ c.id }}' value='{{ c.id }}'/>
<label for='{{ c.id }}' style='margin:5px'>{{ c.caseName }}</label>
</div>
{% endfor %}
</div>
</div>
<div class='col'>
<div class='input-group input-group-outline mb-3'>
<label for='address_form_address' class='form-label'>Address</label>
<input type='text' name='{{ field_name(form.address) }}' id='address_form_address' class='form-control'/>
</div>
<div class='input-group input-group-outline mb-3'>
<label for='address_form_city' class='form-label'>City</label>
<input type='text' name='{{ field_name(form.city) }}' id='address_form_city' class='form-control'/>
</div>
<div class='input-group input-group-outline mb-3'>
<label for='address_form_state'></label>
<select name='{{ field_name(form.state) }}' id='address_form_state' class='form-control'>
<option value=''>-- Select State --</option>
{% for s in enum('App\\Enums\\State').cases() %}
<option value='{{ s.value }}'>{{ s.name }}</option>
{% endfor %}
</select>
</div>
<div class='input-group input-group-outline mb-3'>
<label for='address_form_zip' class='form-label'>Zip</label>
<input type='text' name='{{ field_name(form.zip) }}' id='address_form_zip' class='form-control'/>
</div>
</div>
</div>
<div class='row'>
<div class="text-center">
<button type="submit" class="btn btn-lg bg-gradient-dark btn-lg w-100 mt-4 mb-0">Save Address</button>
</div>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
</section>
</main>
{% endblock %}

View File

@ -0,0 +1,76 @@
{% extends 'base.html.twig' %}
{% block body %}
{{ block('nav', 'internal/libs/nav.html.twig') }}
<main class="main-content position-relative max-height-vh-100 h-100 border-radius-lg ">
{{ block('topnav', 'internal/libs/top-nav.html.twig') }}
<section>
<div class="card card-plain">
<div class="card-header">
<h4 class="font-weight-bolder">Edit Address</h4>
<p class="mb-0">{{ location.name }}</p>
</div>
<div class="card-body">
<div class="container">
{{ form_start(form) }}
{{ form_errors(form) }}
<div class="row">
<div class='col'>
<div class='input-group input-group-outline mb-3 is-filled'>
<label for='address_form_name' class='form-label'>Name</label>
<input type='text' name='{{ field_name(form.name) }}' id='address_form_nameame' value='{{ field_value(form.name) }}' class='form-control' required='required'/>
</div>
<div class='input-group input-group-outline mb-3'>
{% for c in cases %}
<div>
<input type='checkbox' name='cases[]' id='{{ c.id }}' value='{{ c.id }}' {% for id in inCases %} {% if c.id == id %} checked='checked' {% endif %} {% endfor %}/>
<label for='{{ c.id }}' style='margin:5px'>{{ c.caseName }}</label>
</div>
{% endfor %}
</div>
</div>
<div class='col'>
<div class='input-group input-group-outline mb-3 is-filled'>
<label for='address_form_address' class='form-label'>Address</label>
<input type='text' name='{{ field_name(form.address) }}' id='address_form_address' value='{{ field_value(form.address) }}' class='form-control'/>
</div>
<div class='input-group input-group-outline mb-3 is-filled'>
<label for='address_form_city' class='form-label'>City</label>
<input type='text' name='{{ field_name(form.city) }}' id='address_form_city' value='{{ field_value(form.city) }}' class='form-control'/>
</div>
<div class='input-group input-group-outline mb-3'>
<label for='address_form_state'></label>
<select name='{{ field_name(form.state) }}' id='address_form_state' class='form-control'>
<option value=''>-- Select State --</option>
{% for s in enum('App\\Enums\\State').cases() %}
<option value='{{ s.value }}' {% if s.value == location.state.value %} selected='selected' {% endif %}>{{ s.name }}</option>
{% endfor %}
</select>
</div>
<div class='input-group input-group-outline mb-3 is-filled'>
<label for='address_form_zip' class='form-label'>Zip</label>
<input type='text' name='{{ field_name(form.zip) }}' id='address_form_zip' value='{{ field_value(form.zip) }}' class='form-control'/>
</div>
</div>
</div>
<div class='row'>
<div class="text-center">
<button type="submit" class="btn btn-lg bg-gradient-dark btn-lg w-100 mt-4 mb-0">Save Address</button>
</div>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
</section>
</main>
{% endblock %}

View File

@ -0,0 +1,149 @@
{% extends 'base.html.twig' %}
{% block body %}
{{ block('nav', 'internal/libs/nav.html.twig') }}
<main class="main-content position-relative max-height-vh-100 h-100 border-radius-lg ">
{{ block('topnav', 'internal/libs/top-nav.html.twig') }}
<div class="container-fluid py-2">
<div class="row">
<div class="col-12">
<div class="card my-4">
<div class="card-header p-0 position-relative mt-n4 mx-3 z-index-2">
<div class="d-flex justify-content-between bg-gradient-dark shadow-dark border-radius-lg pt-4 pb-3 ps-3 p-2">
<div>
<h6 class="text-white text-capitalize ps-3">Case Address List</h6>
</div>
<div>
<form action='{{ path('app_map_itinerary') }}' method='post' id='map-form' style='display:inline;'>
<input type='hidden' name='caseId' id='caseId'/>
<input type='hidden' name='caseDate' id='caseDate'/>
<button type='button' class='btn bg-gradient-info btn-block mb-3' id='map-itinerary'>Map Itinerary</button>
</form>
<button type='button' class='btn bg-gradient-success btn-block mb-3' id='create-itinerary'>Create Itinerary</button>
<button type="button" class="btn btn-block btn-light mb-3" onclick="window.open('{{ path('app_case_add_address') }}', '_self')">Add Address</button>
</div>
</div>
</div>
<div class="card-body px-0 pb-2">
<div class=''>
Filters:
<select id='case-filter'>
<option value=''>-- Select --</option>
{% for c in cases %}
<option value='{{ c.id }}'>{{ c.caseName }}</option>
{% endfor %}
</select>
<input type='date' id='date-filter'/>
</div>
<div class="table-responsive p-0">
<table class="table align-items-center mb-0">
<thead>
<tr>
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">Name</th>
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 ps-2">Address</th>
<th class='text-center text-uppercase text-secondary text-xxs font-weight-bolder opacity-7'>Lat/Lon</th>
<th class="text-secondary opacity-7"></th>
</tr>
</thead>
<tbody id='addressList'>
{% for l in addresses %}
<tr>
<td>
<div class='d-flex px-2 py-1'>
<div class='d-flex flex-column justify-content-center'>
<h6 class='mb-0 text-small'>
{{ l.name }}
</h6>
</div>
</div>
</td>
<td title=''>
{{ l.getFormattedAddress()|raw }}
</td>
<td class='align-middle text-center text-xs'>
{{ l.lat }}/{{ l.lon }}
</td>
<td class='align-middle'>
<a href='{{ path('app_case_edit_address', {id: l.id}) }}' title='Edit Location'>
<i class="material-symbols-rounded opacity-5">edit</i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class='modal fade' id='exampleModalMessage' tabindex='-1' role='dialog' aria-labelledby='exampleModalMessageTitle'>
<div class='modal-dialog modal-dialog-centered' role='document'>
<div class='modal-content'>
<div class="modal-header">
<h6 class="modal-title font-weight-normal" id="exampleModalLabel">Add Itinerary</h6>
<button type="button" class="btn-close text-dark" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class='modal-body'>
<form>
<div class='input-group input-group-outline my-3'>
<input type='date' id='date' class='form-control'/>
</div>
<div class='input-group input-group-outline my-3'>
<select name="origin" id='origin' class='form-control'>
<option value=''>-- Origin --</option>
</select>
</div>
<div class='input-group input-group-outline my-3'>
<input type='time' name='departure' id='departure'/>
</div>
<div class='input-group input-group-outline my-3'>
<select name='destination' id='destination' class='form-control'>
<option value=''>-- Destination --</option>
</select>
</div>
<div class='input-group input-group-outline my-3'>
<input type='time' name='arrival' id='arrival'/>
</div>
<div class='input-group input-group-outline my-3'>
<input type='checkbox' name='case-mileage' id='case-mileage' value='1'/>
<label for='case-mileage' style='padding:5px;'>Case Mileage</label>
</div>
</form>
</div>
<div class='modal-footer'>
<button type='button' id='close-modal' class='btn bg-gradient-secondary' data-bs-dismiss='modal'>Close</button>
<button type='button' id='add-location-to-itinerary' class='btn bg-gradient-primary'>Add Location</button>
</div>
</div>
</div>
</div>
</main>
{% endblock %}
{% block page_js %}
<script type='module'>
import $ from "{{ asset('vendor/jquery/jquery.index.js') }}";
import * as notify from "{{ asset('vendor/notify.js/notify.js.index.js') }}";
import {filterAddressesByCase} from '{{ asset("js/app/filter.js") }}';
import {createItinerary, addLocationToItinerary, openMap} from '{{ asset("js/app/itinerary.js") }}';
window.$ = $;
$(function () {
document.getElementById('case-filter').addEventListener('change', filterAddressesByCase);
document.getElementById('create-itinerary').addEventListener('click', createItinerary);
document.getElementById('add-location-to-itinerary').addEventListener('click', addLocationToItinerary);
document.getElementById('map-itinerary').addEventListener('click', openMap);
});
</script>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends 'base.html.twig' %}
{% block body %}
{{ block('nav', 'internal/libs/nav.html.twig') }}
<main class="main-content position-relative max-height-vh-100 h-100 border-radius-lg ">
{{ block('topnav', 'internal/libs/top-nav.html.twig') }}
<div class="container-fluid py-2">
<div>
Totals:
{{ total_distance }}
/
{{ total_duration }}
</div>
{{ ux_map(map, {style: 'height: 600px', 'data-controller': 'my-map'}) }}
</div>
</main>
{% endblock %}