This commit is contained in:
Martin Folkerts 2023-11-22 20:01:12 +01:00
parent b92677cd75
commit 486476d326
32 changed files with 1219 additions and 219 deletions

View file

@ -0,0 +1,27 @@
<?php
namespace App;
class Helper
{
public static function getDays(): \Illuminate\Support\Collection
{
$startOfWeek = now()->startOfWeek();
$daysOfWeek = collect([]);
for ($i = 0; $i < 7; $i++) {
$daysOfWeek->add($startOfWeek->copy()->addDays($i)->format('l'));
}
return $daysOfWeek;
}
public static function getMailFrequencies(): \Illuminate\Support\Collection
{
return collect([
'Weekly',
'Monthly',
]);
}
}

View file

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Models\Project;
use Illuminate\Http\Request;
class ProjectController extends Controller
@ -10,5 +11,10 @@ public function index()
{
return view('projects.index');
}
public function show(Project $project)
{
return view('projects.show', compact('project'));
}
//
}

View file

@ -0,0 +1,21 @@
<?php
namespace App\Livewire\Projects;
use App\Models\Project;
use Livewire\Component;
class DownloadManager extends Component
{
public $project;
public function mount(Project $project) {
$this->path = $project->download_path;
}
public function render()
{
return view('livewire.projects.download-manager', [
'downloads' => $this->project
]);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Livewire\Projects;
use App\Models\Project;
use Livewire\Component;
class MailingManager extends Component
{
public $project;
public function mount(Project $project)
{
$this->project = $project;
}
public function render()
{
return view('livewire.projects.mailing-manager', [
'mailings' => $this->project->mailings()->orderBy('created_at', 'desc')->get(),
]);
}
}

View file

@ -4,10 +4,10 @@
use App\Models\Project;
use App\Models\ProjectBoundingBox;
use App\Models\Report;
use App\Models\ProjectEmailRecipient;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Livewire\Component;
use Symfony\Component\Process\Process;
class ProjectManager extends Component
{
@ -20,6 +20,8 @@ class ProjectManager extends Component
public $showProjectModal = false;
public $showMailSettingsModal = false;
public $projectIdBeingDeleted;
/**
@ -29,64 +31,59 @@ class ProjectManager extends Component
*/
public function mount()
{
$this->addBoundingBox();
$this->resetFormData();
}
public function editProject(Project $project) {
public function editProject(Project $project)
{
$this->showProjectModal = true;
$this->loadFormData($project);
}
public function editMailSettings(Project $project)
{
$this->showMailSettingsModal = true;
$this->loadFormData($project);
}
private function loadFormData(Project $project)
{
$this->formData = $project->toArray();
$this->formData['boundingBoxes'] = $project->boundingBoxes->toArray();
$this->formData['mail_recipients'] = $project->emailRecipients->toArray() ?: [
[
'name' => '',
'email' => '',
]
];
}
/**
* Create a new API token.
*
* @return void
*/
public function createProject()
public function openCreateProjectModal()
{
$this->resetFormData();
$this->showProjectModal = true;
}
public function saveProject()
{
$this->resetErrorBag();
Validator::make([
'name' => $this->formData['name'],
'boundingBoxes' => $this->formData['boundingBoxes'],
], [
'name' => ['required', 'string', 'max:255'],
'boundingBoxes' => ['required', 'array', 'min:1'],
'boundingBoxes.*.name' => ['required', 'string', 'max:255'],
'boundingBoxes.*.top_left_latitude' => ['required', 'string'],
'boundingBoxes.*.top_left_longitude' => ['required', 'string'],
'boundingBoxes.*.bottom_right_latitude' => ['required', 'string'],
'boundingBoxes.*.bottom_right_longitude' => ['required', 'string'],
])->validateWithBag('createProject');
// Define the unique identifier for the Project (for example, 'id')
$uniqueIdentifier = ['id' =>$this->formData['id'] ?? null];
// Using updateOrCreate to either update an existing project or create a new one
$project = Project::updateOrCreate($uniqueIdentifier, $this->formData);
// Preparing the bounding boxes data for upsert
// Ensure that each bounding box data array includes a 'project_id' field
$boundingBoxesData = array_map(function ($boundingBox) use ($project) {
$boundingBox['project_id'] = $project->id;
unset($boundingBox['created_at']);
unset($boundingBox['updated_at']);
if (!array_key_exists('id', $boundingBox)) {
$boundingBox['id'] = null;
}
return $boundingBox;
}, $this->formData['boundingBoxes'] ?? []);
// Using upsert to update or create bounding boxes
// You need to specify the unique fields and the fields to be updated
ProjectBoundingBox::upsert($boundingBoxesData, ['id', 'project_id'], ['name', 'top_left_latitude', 'top_left_longitude', 'bottom_right_latitude', 'bottom_right_longitude']);
$this->validateForm();
Project::saveWithFormData($this->formData);
$this->resetFormData();
$this->showProjectModal = false;
$this->dispatch('saved');
}
public function saveMailSettings()
{
$this->resetErrorBag();
$this->validateEmailSettingsForm();
Project::saveWithFormData($this->formData);
$this->resetFormData();
$this->showMailSettingsModal = false;
$this->dispatch('saved');
}
public function addBoundingBox()
{
$this->formData['boundingBoxes'][] =
@ -99,9 +96,19 @@ public function addBoundingBox()
];
}
public function addEmailRecipient()
{
$this->formData['mail_recipients'][] =
[
'name' => '',
'email' => '',
];
}
public function deleteBoundingBox($index)
{
if(array_key_exists('id', $this->formData['boundingBoxes'][$index])) {
if (array_key_exists('id', $this->formData['boundingBoxes'][$index])) {
ProjectBoundingBox::whereId($this->formData['boundingBoxes'][$index]['id'])->delete();
}
unset($this->formData['boundingBoxes'][$index]);
@ -109,6 +116,16 @@ public function deleteBoundingBox($index)
$this->dispatch('saved');
}
public function deleteEmailRecipient($index)
{
if (array_key_exists('id', $this->formData['mail_recipients'][$index])) {
ProjectEmailRecipient::whereId($this->formData['mail_recipients'][$index]['id'])->delete();
}
unset($this->formData['mail_recipients'][$index]);
$this->formData['mail_recipients'] = array_values($this->formData['mail_recipients']);
$this->dispatch('saved');
}
/**
* Confirm that the given Project should be deleted.
@ -154,9 +171,57 @@ public function render()
private function resetFormData()
{
$this->formData = [
'name' => '',
'boundingBoxes' => [],
'name' => '',
'boundingBoxes' => [],
'mail_subject' => '',
'mail_template' => '',
'mail_frequency' => '',
'mail_day' => '',
'mail_recipients' => [],
];
$this->addBoundingBox();
$this->addEmailRecipient();
}
private function validateForm()
{
$projectIdentifier = $this->formData['id'] ?? null;
Validator::make([
'name' => $this->formData['name'],
'boundingBoxes' => $this->formData['boundingBoxes'],
], [
'name' => [
'required',
Rule::unique('projects')->ignore($projectIdentifier),
'string',
'max:255'
],
'boundingBoxes' => ['required', 'array', 'min:1'],
'boundingBoxes.*.name' => ['required', 'string', 'max:255'],
'boundingBoxes.*.top_left_latitude' => ['required', 'string'],
'boundingBoxes.*.top_left_longitude' => ['required', 'string'],
'boundingBoxes.*.bottom_right_latitude' => ['required', 'string'],
'boundingBoxes.*.bottom_right_longitude' => ['required', 'string'],
])->validateWithBag('saveProject');
}
private function validateEmailSettingsForm()
{
Validator::make([
'mail_template' => $this->formData['mail_template'],
'mail_subject' => $this->formData['mail_subject'],
'mail_frequency' => $this->formData['mail_frequency'],
'mail_day' => $this->formData['mail_day'],
'mail_recipients' => $this->formData['mail_recipients'],
], [
'mail_template' => ['required', 'string',],
'mail_subject' => ['required', 'string',],
'mail_frequency' => ['required', 'string',],
'mail_day' => ['required', 'string',],
'mail_recipients' => ['required', 'array', 'min:1'],
'mail_recipients.*.name' => ['required', 'string', 'max:255'],
'mail_recipients.*.email' => ['required', 'email'],
])->validateWithBag('saveEmailSettingsForm');
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Livewire\Projects;
use Livewire\Component;
class ReportManager extends Component
{
public function render()
{
return view('livewire.projects.report-manager');
}
}

View file

@ -8,16 +8,75 @@
class Project extends Model
{
use HasFactory;
protected $fillable = [
'name',
'mail_template',
'mail_subject',
'mail_frequency',
'mail_day',
];
public static function saveWithFormData(mixed $formData)
{
$uniqueIdentifier = ['id' => $formData['id'] ?? null];
$project = Project::updateOrCreate($uniqueIdentifier, $formData);
$project->upsertBoundingBox($formData);
$project->upsertMailRecipients($formData);
}
private function upsertBoundingBox($formData)
{
$boundingBoxesData = array_map(function ($boundingBox) {
$boundingBox['project_id'] = $this->id;
unset($boundingBox['created_at']);
unset($boundingBox['updated_at']);
$boundingBox['id'] ??= null;
return $boundingBox;
}, $formData['boundingBoxes'] ?? []);
ProjectBoundingBox::upsert(
$boundingBoxesData,
['id', 'project_id'],
[
'name',
'top_left_latitude',
'top_left_longitude',
'bottom_right_latitude',
'bottom_right_longitude'
]
);
}
private function upsertMailRecipients($formData)
{
$mailRecipientsData = array_map(function ($mailRecipient) {
$mailRecipient['project_id'] = $this->id;
unset($mailRecipient['created_at']);
unset($mailRecipient['updated_at']);
$mailRecipient['id'] ??= null;
return $mailRecipient;
}, $formData['mail_recipients'] ?? []);
ProjectEmailRecipient::upsert(
$mailRecipientsData,
['id', 'project_id'],
['name', 'email',]
);
}
protected static function boot()
{
parent::boot(); // TODO: Change the autogenerated stub
static::deleting(function ($project) {
$project->boundingBoxes()->delete();
$project->emailRecipients()->delete();
$project->mailings()->each(function ($mailing) {
$mailing->attachments()->delete();
$mailing->recipients()->delete();
});
$project->mailings()->delete();
});
}
@ -25,4 +84,19 @@ public function boundingBoxes()
{
return $this->hasMany(ProjectBoundingBox::class);
}
public function emailRecipients()
{
return $this->hasMany(ProjectEmailRecipient::class);
}
public function mailings()
{
return $this->hasMany(ProjectMailing::class);
}
public function downloads()
{
return $this->hasMany(ProjectDownload::class);
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ProjectDownload extends Model
{
use HasFactory;
}

View file

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ProjectEmailRecipient extends Model
{
use HasFactory;
protected $fillable = [
'name',
'email',
];
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ProjectMailing extends Model
{
use HasFactory;
protected $fillable = [
'subject',
'message',
];
public function project()
{
return $this->belongsTo(Project::class);
}
public function recipients()
{
return $this->hasMany(ProjectMailingRecipient::class);
}
public function attachments()
{
return $this->hasMany(ProjectMailingAttachment::class);
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ProjectMailingAttachment extends Model
{
use HasFactory;
protected $fillable = [
'name',
'path',
];
}

View file

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ProjectMailingRecipient extends Model
{
use HasFactory;
}

View file

@ -16,8 +16,9 @@ public function up(): void
$table->string('name')->unique();
$table->text('mail_template')->nullable();
$table->string('mail_subject')->nullable();
$table->string('mail_frequentie')->default('weekly');
$table->string('mail_day')->default('friday');
$table->string('mail_frequency')->default('Weekly');
$table->string('download_path')->unique();
$table->string('mail_day')->default('Friday');
$table->timestamps();
});
}

View file

@ -0,0 +1,31 @@
<?php
use App\Models\Project;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('project_email_recipients', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Project::class);
$table->string('email');
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('project_email_recipients');
}
};

View file

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('project_mailings', function (Blueprint $table) {
$table->id();
$table->foreignId('project_id');
$table->string('subject');
$table->text('message');
// $table->text('recipients');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('project_mailings');
}
};

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('project_mailing_recipients', function (Blueprint $table) {
$table->id();
$table->foreignId('project_mailing_id');
$table->string('email');
$table->string('name')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('project_mailing_recipients');
}
};

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('project_mailing_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('project_mailing_id');
$table->string('name');
$table->string('path');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('project_mailing_attachments');
}
};

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('project_downloads', function (Blueprint $table) {
$table->id();
$table->foreignId('project_id');
$table->string('name');
$table->string('path');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('project_downloads');
}
};

View file

@ -14,11 +14,244 @@ public function run(): void
{
// \App\Models\User::factory(10)->create();
\App\Models\User::factory()->create([
'name' => 'Martin Folkerts',
'email' => 'martin@sobit.nl',
'password' => '$2y$10$hZZaAaiv1KXmCqq5vZ6PEeRWzvwGbaHKcfqeEMlTn.y22EEPVtofi',
\App\Models\User::factory()->create([
'name' => 'Martin Folkerts',
'email' => 'martin@sobit.nl',
'password' => '$2y$10$hZZaAaiv1KXmCqq5vZ6PEeRWzvwGbaHKcfqeEMlTn.y22EEPVtofi',
]);
]);
$this->createChembaProject();
$this->createXinavaneProject();
$this->createKakiraProject();
}
private function createChembaProject()
{
$chembaProject = \App\Models\Project::create([
'name' => 'Chemba',
'mail_template' => 'Mail template for Chemba',
'mail_subject' => 'SmartCane update Chemba for {date}',
'mail_frequency' => 'Weekly',
'mail_day' => 'Friday',
'download_path' => 'chemba',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
]);
$chembaProject->boundingBoxes()->createMany([
[
'name' => 'Chemba West',
'top_left_latitude' => 34.9460,
'top_left_longitude' => -17.3516,
'bottom_right_latitude' => 34.9380,
'bottom_right_longitude' => -17.2917,
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
],
[
'name' => 'Chemba East',
'top_left_latitude' => 34.8830,
'top_left_longitude' => -17.3516,
'bottom_right_latitude' => 34.9380,
'bottom_right_longitude' => -17.2917,
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
],
]);
$chembaProject->emailRecipients()->createMany([
[
'name' => 'Martin Folkerts',
'email' => 'martin@sobit.nl',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
], [
'name' => 'Timon Weitkamp',
'email' => 'timon@smartfarmingtech.com',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
],
]);
$chembaProject->mailings()->createMany([
[
'subject' => 'Chemba 2021-01-01',
'message' => 'Chemba 2021-01-01',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
], [
'subject' => 'Chemba 2021-01-08',
'message' => 'Chemba 2021-01-08',
'created_at' => '2021-01-08 00:00:00',
'updated_at' => '2021-01-08 00:00:00',
],
]);
foreach ($chembaProject->mailings as $mailing) {
$mailing->recipients()->createMany([
[
'name' => 'Martin Folkerts',
'email' => 'martin@sobit.nl',
'created_at' => $mailing->created_at,
'updated_at' => $mailing->updated_at,
], [
'name' => 'Timon Weitkamp',
'email' => 'timon@smartfarmingtech.com',
'created_at' => $mailing->created_at,
'updated_at' => $mailing->updated_at,
],
]);
}
}
private function createXinavaneProject()
{
$project = \App\Models\Project::create([
'name' => 'Xinavane',
'mail_template' => 'Mail template for Xinavane',
'mail_subject' => 'SmartCane update Xinavane for {date}',
'mail_frequency' => 'Weekly',
'mail_day' => 'Friday',
'download_path' => 'xinavane',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
]);
$project->boundingBoxes()->createMany([
[
'name' => 'Xinavane West',
'top_left_latitude' => 32.6213,
'top_left_longitude' => -25.0647,
'bottom_right_latitude' => 32.6284,
'bottom_right_longitude' => -25.0570,
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
],
[
'name' => 'Xinavane East',
'top_left_latitude' => 32.6790,
'top_left_longitude' => -25.0333,
'bottom_right_latitude' => 32.7453,
'bottom_right_longitude' => -25.0235,
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
],
]);
$project->emailRecipients()->createMany([
[
'name' => 'Martin Folkerts',
'email' => 'martin@sobit.nl',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
], [
'name' => 'Timon Weitkamp',
'email' => 'timon@smartfarmingtech.com',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
],
]);
$project->mailings()->createMany([
[
'subject' => 'Xinavane 2021-01-01',
'message' => 'Xinavane 2021-01-01',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
], [
'subject' => 'Xinavane 2021-01-08',
'message' => 'Xinavane 2021-01-08',
'created_at' => '2021-01-08 00:00:00',
'updated_at' => '2021-01-08 00:00:00',
],
]);
foreach ($project->mailings as $mailing) {
$mailing->recipients()->createMany([
[
'name' => 'Martin Folkerts',
'email' => 'martin@sobit.nl',
'created_at' => $mailing->created_at,
'updated_at' => $mailing->updated_at,
], [
'name' => 'Timon Weitkamp',
'email' => 'timon@smartfarmingtech.com',
'created_at' => $mailing->created_at,
'updated_at' => $mailing->updated_at,
],
]);
}
}
private function createKakiraProject()
{
$project = \App\Models\Project::create([
'name' => 'Kakira',
'mail_template' => 'Mail template for Kakira',
'mail_subject' => 'SmartCane update Kakira for {date}',
'mail_frequency' => 'Weekly',
'mail_day' => 'Friday',
'download_path' => 'kakira',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
]);
$project->boundingBoxes()->createMany([
[
'name' => 'kakira_demo',
'top_left_latitude' => 33.289993827426535,
'top_left_longitude' => 0.491981861534345,
'bottom_right_latitude' => 33.32572075441914,
'bottom_right_longitude' => 0.5144195937114393,
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
],
]);
$project->emailRecipients()->createMany([
[
'name' => 'Martin Folkerts',
'email' => 'martin@sobit.nl',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
], [
'name' => 'Timon Weitkamp',
'email' => 'timon@smartfarmingtech.com',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
],
]);
$project->mailings()->createMany([
[
'subject' => 'Kakira 2021-01-01',
'message' => 'Kakira 2021-01-01',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
], [
'subject' => 'Kakira 2021-01-08',
'message' => 'Kakira 2021-01-08',
'created_at' => '2021-01-08 00:00:00',
'updated_at' => '2021-01-08 00:00:00',
],
]);
foreach ($project->mailings as $mailing) {
$mailing->recipients()->createMany([
[
'name' => 'Martin Folkerts',
'email' => 'martin@sobit.nl',
'created_at' => $mailing->created_at,
'updated_at' => $mailing->updated_at,
], [
'name' => 'Timon Weitkamp',
'email' => 'timon@smartfarmingtech.com',
'created_at' => $mailing->created_at,
'updated_at' => $mailing->updated_at,
],
]);
}
}
}

View file

@ -0,0 +1,25 @@
@props(['submit'])
<div {{ $attributes->merge(['class' => 'md:grid md:grid-cols-1 md:gap-6']) }}>
<div class="mt-5 px-6">
<h2>{{ $title }}</h2>
<span name="description">{{ $description }}</span>
</div>
<div class="mt-5 md:mt-0 md:col-span-1">
<form wire:submit="{{ $submit }}">
<div class="px-4 py-5 bg-white sm:p-6 shadow {{ isset($actions) ? 'sm:rounded-tl-md sm:rounded-tr-md' : 'sm:rounded-md' }}">
<div class="grid grid-cols-6 gap-6">
{{ $form }}
</div>
</div>
@if (isset($actions))
<div class="flex items-center justify-end px-4 py-3 bg-gray-50 text-right sm:px-6 shadow sm:rounded-bl-md sm:rounded-br-md">
{{ $actions }}
</div>
@endif
</form>
</div>
</div>

View file

@ -9,6 +9,8 @@
'lg' => 'sm:max-w-lg',
'xl' => 'sm:max-w-xl',
'2xl' => 'sm:max-w-2xl',
'4xl' => 'sm:max-w-4xl',
'6xl' => 'sm:max-w-6xl',
][$maxWidth ?? '2xl'];
@endphp

View file

@ -0,0 +1,19 @@
<x-confirmation-modal wire:model.live="confirmingReportDeletion" {{ $attributes }}>
<x-slot name="title">
{{ __('Delete Project') }}
</x-slot>
<x-slot name="content">
{{ __('Are you sure you would like to delete this Project?') }}
</x-slot>
<x-slot name="footer">
<x-secondary-button wire:click="$toggle('confirmingReportDeletion')" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-secondary-button>
<x-danger-button class="ml-3" wire:click="deleteProject" wire:loading.attr="disabled">
{{ __('Delete') }}
</x-danger-button>
</x-slot>
</x-confirmation-modal>

View file

@ -0,0 +1,45 @@
@props([
/** @var \App\Livewire\Projects\ProjectManager */
'projectManager'
])
<div {{ $attributes->class(['mt-10 sm:mt-0']) }}>
<x-action-section>
<x-slot name="title">
{{ __('Manage projects') }}
</x-slot>
<x-slot name="description">
{{ __('You may delete any of your projects if they are no longer needed.') }}
</x-slot>
<x-slot name="content">
<div class="space-y-6">
<x-secondary-button wire:click="openCreateProjectModal">
{{ __('Create Project') }}
</x-secondary-button>
@foreach ($projectManager->projects->sortBy('name') as $project)
<div class="flex items-center justify-between">
<div class="break-all">
<a href="{!! route('project.show', $project->id) !!}">{{ $project->name }}</a>
</div>
<div class="flex items-center ml-2">
<button class="cursor-pointer ml-6 text-sm text-gray-500"
wire:click="editMailSettings({{ $project->id }})">
{{ __('Mail') }}
</button>
<button class="cursor-pointer ml-6 text-sm text-gray-500"
wire:click="editProject({{ $project->id }})">
{{ __('Edit') }}
</button>
<button class="cursor-pointer ml-6 text-sm text-red-500"
wire:click="confirmReportDeletion({{ $project->id }})">
{{ __('Delete') }}
</button>
</div>
</div>
@endforeach
</div>
</x-slot>
</x-action-section>
</div>

View file

@ -0,0 +1,104 @@
@props([
/** @var \App\Livewire\Projects\ProjectManager */
'projectManager'
])
<x-modal max-width="4xl" wire:model.live="showMailSettingsModal" {{ $attributes }}>
<x-form-modal submit="saveMailSettings">
<x-slot name="title">
{{ __('Project') }} : {{ $projectManager->formData['name'] ?? '' }}
</x-slot>
<x-slot name="description">
{{ __('Email settings for project.') }}
</x-slot>
<x-slot name="form">
<div class="col-span-6 sm:col-span-4">
<x-label for="email_subject" value="{{ __('Subject') }}"/>
<x-input id="email_subject" type="text" class="mt-1 block w-full" wire:model="formData.mail_subject" autofocus/>
<x-input-error for="mail_subject" class="mt-2"/>
</div>
<div class="col-span-6 sm:col-span-4">
<x-label for="email_template" value="{{ __('Template') }}"/>
<textarea id="email_template" type="text" class="mt-1 block w-full h-[200px] 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" wire:model="formData.mail_template" autofocus></textarea>
<x-input-error for="mail_template" class="mt-2"/>
</div>
<div class="col-span-6 sm:col-span-4">
<x-label for="email_frequency" value="{{ __('Frequency') }}"/>
<select id="email_frequency" class="mt-1 block w-full" wire:model="formData.mail_frequency">
@foreach(App\Helper::getMailFrequencies() as $frequency)
<option value="{{ $frequency }}">{{ $frequency }}</option>
@endforeach
</select>
<x-input-error for="mail_frequency" class="mt-2"/>
</div>
<div class="col-span-6 sm:col-span-4">
<x-label for="mail_day" value="{{ __('Day') }}"/>
<select id="mail_day" wire:model="formData.mail_day" class="mt-1 block w-full">
@foreach(App\Helper::getDays() as $day)
<option value="{{ $day }}">{{ $day }}</option>
@endforeach
</select>
<x-input-error for="mail_day" class="mt-2"/>
</div>
<div class="col-span-6 sm:col-span-4">
{{ __('Recipients') }}
</div>
@foreach($projectManager->formData['mail_recipients'] as $key => $email_recipient)
<div class="col-span-6 sm:colspan-4 grid grid-cols-6 gap-1">
<div class="col-span-2">
<x-label for="mail_recipient_{{ $key }}_name" value="{{ __('Name') }}"/>
<x-input id="mail_recipient_{{ $key }}_name" type="text" class="mt-1 block w-full"
wire:model="formData.mail_recipients.{{ $key }}.name" autofocus/>
<x-input-error for="mail_recipients.{{ $key }}.name" class="mt-2"/>
</div>
<div class="col-span-3">
<x-label for="bounding_box_{{ $key }}_top_left_latitude"
value="{{ __('Email') }}"/>
<x-input id="bounding_box_{{ $key }}_top_left_latitude" type="text"
class="mt-1 block w-full"
wire:model="formData.mail_recipients.{{ $key }}.email"
autofocus/>
<x-input-error for="mail_recipients.{{ $key }}.email" class="mt-2"/>
</div>
<div class="col-span-1 flex">
@if( count($projectManager->formData['mail_recipients']) > 1)
<button
class="cursor-pointer ml-6 text-sm text-red-500 py-3 self-end"
type="button"
wire:click="deleteEmailRecipient({{ $key }})"
wire:confirm="{{ __('Are you sure you want to delete this Recipient?') }}"
>
Delete
</button>
@endif
</div>
</div>
@endforeach
</x-slot>
<x-slot name="actions">
<x-action-message class="mr-3" on="saved">
{{ __('Saved.') }}
</x-action-message>
<x-secondary-button class="mr-3"
type="button"
wire:click="addEmailRecipient"
>
{{ __('Add recipient') }}
</x-secondary-button>
<x-secondary-button class="mr-3"
type="button"
x-on:click="$wire.showMailSettingsModal = false"
>
{{ __('Cancel') }}
</x-secondary-button>
<x-button>
{{ __('Save') }}
</x-button>
</x-slot>
</x-form-modal>
</x-modal>

View file

@ -0,0 +1,106 @@
@props([
'formData',
/** @var \App\Livewire\Projects\ProjectManager */
'projectManager'
])
<x-modal wire:model.live="showProjectModal" {{ $attributes }}>
<x-form-modal submit="saveProject">
<x-slot name="title">
{{ __('Project') }}
</x-slot>
<x-slot name="description">
{{ __('Report generator for generating reports') }}
</x-slot>
<x-slot name="form">
<!-- Token Name -->
<div class="col-span-6 sm:col-span-4">
<x-label for="name" value="{{ __('Name') }}"/>
<x-input id="name" type="text" class="mt-1 block w-full" wire:model="formData.name" autofocus/>
<x-input-error for="name" class="mt-2"/>
</div>
@foreach($projectManager->formData['boundingBoxes'] as $key => $boundingBox)
{{-- <div wire:key="bounding_box_{{ $key }}">--}}
<div class="col-span-6 sm:col-span-4">
{{ __('Bounding box') }}
@if( count($projectManager->formData['boundingBoxes']) > 1)
<button
class="cursor-pointer ml-6 text-sm text-red-500"
type="button"
wire:click="deleteBoundingBox({{ $key }})"
wire:confirm="{{ __('Are you sure you want to delete this BoundingBox?') }}"
>
Delete
</button>
@endif
</div>
<div class="col-span-6 sm:col-span-4">
<x-label for="bounding_box_{{ $key }}_name" value="{{ __('Name') }}"/>
<x-input id="bounding_box_{{ $key }}_name" type="text" class="mt-1 block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.name" autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.name" class="mt-2"/>
</div>
<div class="col-span-6 grid grid-cols-6 gap-1">
<div class="col-span-3">
<x-label for="bounding_box_{{ $key }}_top_left_latitude"
value="{{ __('Top left Latitude') }}"/>
<x-input id="bounding_box_{{ $key }}_top_left_latitude" type="text"
class="block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.top_left_latitude"
autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.top_left_latitude" class="mt-2"/>
</div>
<div class="col-span-3">
<x-label for="bounding_box_{{ $key }}_top_left_longitude" value="{{ __('Longitude') }}"/>
<x-input id="name" type="text" class="block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.top_left_longitude"
autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.top_left_longitude" class="mt-2"/>
</div>
<div class="col-span-3">
<x-label for="bounding_box_{{ $key }}_bottom_right_latitude"
value="{{ __('Bottom right Latitude') }}"/>
<x-input id="bounding_box_{{ $key }}_bottom_right_latitude" type="text"
class="block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.bottom_right_latitude"
autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.bottom_right_latitude" class="mt-2"/>
</div>
<div class="col-span-3">
<x-label for="boundingBox_{{ $key }}_bottom_right_longitude" value="{{ __('Longitude') }}"/>
<x-input id="boundingBox_{{ $key }}_bottom_right_longitude" type="text"
class="block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.bottom_right_longitude"
autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.bottom_right_longitude" class="mt-2"/>
</div>
</div>
{{-- </div>--}}
@endforeach
</x-slot>
<x-slot name="actions">
<x-action-message class="mr-3" on="saved">
{{ __('Saved.') }}
</x-action-message>
<x-secondary-button class="mr-3"
type="button"
wire:click="addBoundingBox"
>
{{ __('Add Bounding box') }}
</x-secondary-button>
<x-secondary-button class="mr-3"
type="button"
x-on:click="$wire.showProjectModal = false"
>
{{ __('Cancel') }}
</x-secondary-button>
<x-button>
{{ __('Save') }}
</x-button>
</x-slot>
</x-form-modal>
</x-modal>

View file

@ -0,0 +1,23 @@
<div {{ $attributes->merge(['class' => 'md:grid md:grid-cols-1 md:gap-6']) }}>
<div class="mt-5 px-6">
<h2>{{ $title }}</h2>
<span name="description">{{ $description }}</span>
</div>
<div class="mt-5 md:mt-0 md:col-span-2">
<div class="px-4 py-5 bg-white sm:p-6 shadow {{ isset($actions) ? 'sm:rounded-tl-md sm:rounded-tr-md' : 'sm:rounded-md' }}">
<div class="grid grid-cols-1" >
{{ $form }}
</div>
</div>
@if (isset($actions))
<div class="flex items-center justify-end px-4 py-3 bg-gray-50 text-right sm:px-6 shadow sm:rounded-bl-md sm:rounded-br-md">
{{ $actions }}
</div>
@endif
</form>
</div>
</div>

View file

@ -1,165 +1,7 @@
<div>
<x-modal wire:model.live="showProjectModal">
<x-form-section submit="createProject">
<x-slot name="title">
{{ __('Project') }}
</x-slot>
<x-slot name="description">
{{ __('Report generator for generating reports') }}
</x-slot>
<x-slot name="form">
<!-- Token Name -->
<div class="col-span-6 sm:col-span-4">
<x-label for="name" value="{{ __('Name') }}"/>
<x-input id="name" type="text" class="mt-1 block w-full" wire:model="formData.name" autofocus/>
<x-input-error for="name" class="mt-2"/>
</div>
@foreach($formData['boundingBoxes'] as $key => $boundingBox)
{{-- <div wire:key="bounding_box_{{ $key }}">--}}
<div class="col-span-6 sm:col-span-4">
{{ __('Bounding box') }}
@if( count($this->formData['boundingBoxes']) > 1)
<button
class="cursor-pointer ml-6 text-sm text-red-500"
type="button"
wire:click="deleteBoundingBox({{ $key }})"
wire:confirm="{{ __('Are you sure you want to delete this BoundingBox?') }}"
>
Delete
</button>
@endif
</div>
<div class="col-span-6 sm:col-span-4">
<x-label for="bounding_box_{{ $key }}_name" value="{{ __('Name') }}"/>
<x-input id="bounding_box_{{ $key }}_name" type="text" class="mt-1 block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.name" autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.name" class="mt-2"/>
</div>
<div class="col-span-6 sm:colspan-4 grid grid-cols-6">
<div class="col-span-6 sm:col-span-3 mr-1">
<x-label for="bounding_box_{{ $key }}_top_left_latitude"
value="{{ __('Top left Latitude') }}"/>
<x-input id="bounding_box_{{ $key }}_top_left_latitude" type="text"
class="mt-1 block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.top_left_latitude"
autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.top_left_latitude" class="mt-2"/>
</div>
<div class="col-span-6 sm:col-span-3">
<x-label for="bounding_box_{{ $key }}_top_left_longitude" value="{{ __('Longitude') }}"/>
<x-input id="name" type="text" class="mt-1 block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.top_left_longitude"
autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.top_left_longitude" class="mt-2"/>
</div>
<div class="col-span-6 sm:col-span-3 mr-1">
<x-label for="bounding_box_{{ $key }}_bottom_right_latitude"
value="{{ __('Bottom right Latitude') }}"/>
<x-input id="bounding_box_{{ $key }}_bottom_right_latitude" type="text"
class="mt-1 block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.bottom_right_latitude"
autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.bottom_right_latitude" class="mt-2"/>
</div>
<div class="col-span-6 sm:col-span-3">
<x-label for="boundingBox_{{ $key }}_bottom_right_longitude" value="{{ __('Longitude') }}"/>
<x-input id="boundingBox_{{ $key }}_bottom_right_longitude" type="text"
class="mt-1 block w-full"
wire:model="formData.boundingBoxes.{{ $key }}.bottom_right_longitude"
autofocus/>
<x-input-error for="boundingBoxes.{{ $key }}.bottom_right_longitude" class="mt-2"/>
</div>
</div>
{{-- </div>--}}
@endforeach
</x-slot>
<x-slot name="actions">
<x-action-message class="mr-3" on="saved">
{{ __('Saved.') }}
</x-action-message>
<x-secondary-button class="mr-3"
type="button"
wire:click="addBoundingBox"
>
{{ __('Add Bounding box') }}
</x-secondary-button>
<x-secondary-button class="mr-3"
type="button"
x-on:click="$wire.showProjectModal = false"
>
{{ __('Cancel') }}
</x-secondary-button>
<x-button>
{{ __('Save') }}
</x-button>
</x-slot>
</x-form-section>
</x-modal>
<!-- Manage API Tokens -->
<div class="mt-10 sm:mt-0">
<x-action-section>
<x-slot name="title">
{{ __('Manage projects') }}
</x-slot>
<x-slot name="description">
{{ __('You may delete any of your projects if they are no longer needed.') }}
</x-slot>
<!-- API Token List -->
<x-slot name="content">
<div class="space-y-6">
<x-secondary-button x-on:click="$wire.showProjectModal = true">
{{ __('Create Project') }}
</x-secondary-button>
@foreach ($this->projects->sortBy('name') as $project)
<div class="flex items-center justify-between">
<div class="break-all">
{{ $project->name }}
</div>
<div class="flex items-center ml-2">
<button class="cursor-pointer ml-6 text-sm text-gray-500"
wire:click="editProject({{ $project->id }})">
{{ __('Edit') }}
</button>
<button class="cursor-pointer ml-6 text-sm text-red-500"
wire:click="confirmReportDeletion({{ $project->id }})">
{{ __('Delete') }}
</button>
</div>
</div>
@endforeach
</div>
</x-slot>
</x-action-section>
</div>
<!-- Delete Token Report Modal -->
<x-confirmation-modal wire:model.live="confirmingReportDeletion">
<x-slot name="title">
{{ __('Delete Project') }}
</x-slot>
<x-slot name="content">
{{ __('Are you sure you would like to delete this Project?') }}
</x-slot>
<x-slot name="footer">
<x-secondary-button wire:click="$toggle('confirmingReportDeletion')" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-secondary-button>
<x-danger-button class="ml-3" wire:click="deleteProject" wire:loading.attr="disabled">
{{ __('Delete') }}
</x-danger-button>
</x-slot>
</x-confirmation-modal>
<x-project-manager-mail-settings-modal :projectManager="$this"/>
<x-project-manager-properties-modal :projectManager="$this"/>
<x-project-manager-grid :projectManager="$this"/>
<x-project-manager-delete-confirmation-modal/>
</div>

View file

@ -0,0 +1,16 @@
<x-tab-section>
<x-slot name="title">Download details</x-slot>
<x-slot name="description">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsa, reprehenderit.</x-slot>
<x-slot name="form">
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<div class="mt-10 sm:mt-0">
@livewire('download.download-grid')
</div>
<x-section-border />
<div class="mt-10 sm:mt-0">
@livewire('download.download-form')
</div>
</div>
</x-slot>
</x-tab-section>

View file

@ -0,0 +1,40 @@
<x-tab-section>
<x-slot name="title">Mailing Details</x-slot>
<x-slot name="description">...</x-slot>
<x-slot name="form">
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<table class="min-w-full divide-y divide-gray-300">
<thead>
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">Id
</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Created</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Subject</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">#</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Attachment</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0">
<span class="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
@foreach($project->mailings as $mail)
<tr>
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0">{{ $mail->id }}</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{{ $mail->created_at }}</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{{ $mail->subject }}</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{{ $mail->recipients()->count() }}</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{{ $mail->attachments()->get('name')->implode(', ') }}</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
<button type="button" class="text-indigo-600 hover:text-indigo-900">Show</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</tbody>
</table>
</x-slot>
</x-tab-section>

View file

@ -0,0 +1,9 @@
<x-tab-section>
<x-slot name="title">Report Details</x-slot>
<x-slot name="description">..</x-slot>
<x-slot name="form">
</x-slot>
</x-tab-section>

View file

@ -0,0 +1,68 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Project') }} {{ $project->name }}
</h2>
</x-slot>
<div>
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8" x-data="{openTab: 1}">
<div class="mt-10 sm:mt-0">
<div class="flex w-full space-x-6 mb-5 border-b border-secondary max-h-[50px]" selid="tabs">
<div :class="{'border-b-2 border-indigo-500 -mb-px text-indigo-500' : openTab === 1}" selid="tab-question">
<button
style="color:inherit"
x-on:click="openTab = 1"
>
<span>{{ __('Mailing') }}</span>
</button>
</div>
<div class="" :class="{'border-b-2 border-indigo-500 -mb-px text-indigo-500' : openTab === 2}"
selid="tab-settings">
<button
style="color:inherit"
x-on:click="openTab = 2;"
>
<span>{{ __('Downloads') }}</span>
</button>
</div>
<div class="" :class="{'border-b-2 border-indigo-500 -mb-px text-indigo-500' : openTab === 3}"
selid="tab-statistics">
<button
style="color:inherit"
x-on:click="openTab = 3;"
>
<span>{{ __('Reports') }}</span>
</button>
</div>
</div>
<div class="flex flex-col flex-1 pb-5 space-y-4 relative" x-show="openTab === 2"
x-transition:enter="transition duration-200"
x-transition:enter-start="opacity-0 delay-200"
x-transition:enter-end="opacity-100"
>
@livewire('projects.download-manager', ['project' => $project]);
</div>
<div class="flex flex-col flex-1 pb-5 space-y-4 relative" x-show="openTab === 1"
x-transition:enter="transition duration-200"
x-transition:enter-start="opacity-0 delay-200"
x-transition:enter-end="opacity-100"
>
@livewire('projects.mailing-manager', ['project' => $project]);
</div>
<div class="flex flex-col flex-1 pb-5 space-y-4 relative" x-show="openTab === 3"
x-transition:enter="transition duration-200"
x-transition:enter-start="opacity-0 delay-200"
x-transition:enter-end="opacity-100"
>
@livewire('projects.report-manager', ['project' => $project]);
</div>
</div>
</div>
</div>
</x-app-layout>

View file

@ -28,5 +28,6 @@
Route::get('/download', [\App\Http\Controllers\DownloadController::class, 'show'])->name('download');
Route::get('/report', [\App\Http\Controllers\ReportController::class, 'index'])->name('report');
Route::get('/project', [\App\Http\Controllers\ProjectController::class, 'index'])->name('project');
Route::get('/projects/{project}', [\App\Http\Controllers\ProjectController::class, 'show'])->name('project.show');
Route::get('/projects', [\App\Http\Controllers\ProjectController::class, 'index'])->name('project');
});