Skip to content

Commit c1a0377

Browse files
authored
Merge pull request #7 from justbetter/feature/import-export
Import & Export
2 parents bca7d1a + c35795a commit c1a0377

29 files changed

+671
-34
lines changed

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"require": {
55
"statamic/cms": "^5.67.0",
66
"laravel/framework": "^12.0",
7-
"php": "^8.3"
7+
"php": "^8.3",
8+
"spatie/simple-excel": "^3.8"
89
},
910
"require-dev": {
1011
"larastan/larastan": "^3.4",
@@ -40,7 +41,7 @@
4041
"analyse": "phpstan --memory-limit=1G",
4142
"style": "pint --test",
4243
"style:fix": "pint",
43-
"coverage": "XDEBUG_MODE=coverage php vendor/bin/pest --coverage --min=93",
44+
"coverage": "XDEBUG_MODE=coverage php vendor/bin/pest --coverage --min=96",
4445
"quality": [
4546
"@test",
4647
"@analyse",
@@ -53,4 +54,4 @@
5354
"pestphp/pest-plugin": true
5455
}
5556
}
56-
}
57+
}

config/statamic-detour.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@
1515
'path' => base_path('content/detours'),
1616

1717
'mode' => env('STATAMIC_DETOUR_MODE', 'basic'), // basic | performance
18+
19+
'actions' => [
20+
'disk' => 'local',
21+
],
22+
23+
'queue' => 'default',
1824
];

resources/js/components/Detours.vue

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</tr>
1414
</thead>
1515
<tbody class="divide-y divide-gray-200">
16-
<tr class="hover:bg-gray-50 text-gray-900" v-for="(item, id) in detours" :key="id">
16+
<tr class="hover:bg-gray-50 text-gray-900" v-for="(item, key) in detours" :key="item.id">
1717
<td class="px-4 py-3 text-sm">{{ item.from }}</td>
1818
<td class="px-4 py-3 text-sm">{{ item.to }}</td>
1919
<td class="px-4 py-3 text-sm">{{ item.type }}</td>
@@ -25,7 +25,7 @@
2525
</td>
2626
<td class="px-4 py-3 text-right">
2727
<button
28-
@click="deleteDetour(id)"
28+
@click="deleteDetour(item.id)"
2929
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-red-500 rounded hover:bg-red-400 focus:outline-none focus:ring-2 focus:ring-red-500">
3030
{{ __('Delete') }}
3131
</button>
@@ -58,16 +58,15 @@ export default ({
5858
},
5959
6060
methods: {
61-
deleteItem(id, type = 'delete') {
62-
if (type == 'delete') {
63-
delete this.detours[id];
64-
}
61+
deleteItem(id) {
62+
const index = this.detours.findIndex(item => item.id === id);
63+
this.detours.splice(index, 1);
6564
6665
this.$forceUpdate();
6766
},
6867
6968
addItem(event) {
70-
this.detours[event.data.id] = event.data;
69+
this.detours.push(event.data);
7170
this.$forceUpdate();
7271
},
7372
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
@extends('statamic::layout')
2+
3+
@section('title', __('Detours - Actions'))
4+
5+
@section('content')
6+
<div class="max-w-3xl mx-auto space-y-6">
7+
<h1>
8+
{{ __('Import & Export Detours') }}
9+
</h1>
10+
11+
<div class="bg-white shadow-sm ring-1 ring-gray-200 rounded-lg p-6">
12+
<h2 class="text-lg font-semibold text-gray-900">{{ __('Upload file') }}</h2>
13+
<p class="mt-1 text-sm text-gray-600">
14+
{{ __('Upload a CSV to be imported.')}}
15+
</p>
16+
17+
@if (session('status'))
18+
<div class="mt-4 rounded-md bg-green-50 px-4 py-3 text-sm text-green-800 ring-1 ring-green-200">
19+
{{ session('status') }}
20+
</div>
21+
@endif
22+
23+
@if ($errors->any())
24+
<div class="mt-4 rounded-md bg-red-50 px-4 py-3 text-sm text-red-800 ring-1 ring-red-200">
25+
<ul class="list-disc pl-5 space-y-1">
26+
@foreach ($errors->all() as $error)
27+
<li>{{ $error }}</li>
28+
@endforeach
29+
</ul>
30+
</div>
31+
@endif
32+
33+
<form method="POST" action="{{ cp_route('justbetter.detours.actions.import') }}" enctype="multipart/form-data" class="mt-6 space-y-4">
34+
@csrf
35+
36+
<div>
37+
<label for="file" class="block text-sm font-medium text-gray-900">{{ __('Choose file') }}</label>
38+
<div class="mt-2">
39+
<input
40+
id="file"
41+
name="file"
42+
type="file"
43+
class="block w-full text-sm text-gray-900 file:mr-4 file:rounded-md file:border-0 file:bg-gray-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-gray-800 hover:file:bg-gray-200 rounded-md border border-gray-300 bg-white px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
44+
required
45+
/>
46+
</div>
47+
</div>
48+
49+
<div class="flex items-center gap-3">
50+
<button
51+
type="submit"
52+
class="btn btn-primary"
53+
>
54+
{{ __('Upload') }}
55+
</button>
56+
</div>
57+
</form>
58+
</div>
59+
60+
<div class="bg-white shadow-sm ring-1 ring-gray-200 rounded-lg p-6">
61+
<h2 class="text-lg font-semibold text-gray-900">{{ __('Export') }}</h2>
62+
<p class="mt-1 text-sm text-gray-600">
63+
{{ __('Generate and download an export file.') }}
64+
</p>
65+
66+
<div class="mt-6 flex flex-col sm:flex-row gap-3">
67+
<a
68+
href="{{ cp_route('justbetter.detours.actions.export') }}"
69+
class="btn btn-primary"
70+
>
71+
{{ __('Download CSV') }}
72+
</a>
73+
</div>
74+
</div>
75+
76+
</div>
77+
@endsection

resources/views/detours/index.blade.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
@extends('statamic::layout')
22

3+
@section('title', 'Detours - Overview')
4+
35
@section('content')
46
<div>
57
<Detours

routes/cp.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22

33
use Illuminate\Support\Facades\Route;
44
use JustBetter\Detour\Http\Controllers\DetourController;
5+
use JustBetter\Detour\Http\Controllers\ImportExportController;
56

67
Route::prefix('detours')->group(function () {
78
Route::get('/', [DetourController::class, 'index'])->name('justbetter.detours.index');
89

910
Route::post('/store', [DetourController::class, 'store'])->name('justbetter.detours.store');
1011

1112
Route::delete('/{detour}', [DetourController::class, 'destroy'])->name('justbetter.detours.destroy');
13+
14+
Route::get('/actions', [ImportExportController::class, 'index'])->name('justbetter.detours.actions.index');
15+
Route::get('/actions/export', [ImportExportController::class, 'export'])->name('justbetter.detours.actions.export');
16+
17+
Route::post('/actions/import', [ImportExportController::class, 'import'])->name('justbetter.detours.actions.import');
1218
});

src/Actions/ExportDetours.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace JustBetter\Detour\Actions;
4+
5+
use Illuminate\Support\Facades\File;
6+
use Illuminate\Support\Facades\Storage;
7+
use JustBetter\Detour\Contracts\ExportsDetours;
8+
use JustBetter\Detour\Contracts\ResolvesRepository;
9+
use JustBetter\Detour\Data\Detour;
10+
use Spatie\SimpleExcel\SimpleExcelWriter;
11+
12+
class ExportDetours implements ExportsDetours
13+
{
14+
public function __construct(
15+
protected ResolvesRepository $resolvesRepository
16+
) {}
17+
18+
public function export(): string
19+
{
20+
$disk = config()->string('justbetter.statamic-detour.actions.disk');
21+
$filename = 'export.csv';
22+
$fullPath = Storage::disk($disk)->path($filename);
23+
File::ensureDirectoryExists(Storage::disk($disk)->path(''));
24+
25+
$repository = $this->resolvesRepository->resolve();
26+
$detours = $repository->get();
27+
28+
$detours = $detours->map(function (Detour $detour): array {
29+
$detour = $detour->toArray();
30+
/** @var array<int, string> $sites */
31+
$sites = $detour['sites'] ?? [];
32+
$detour['sites'] = isset($detour['sites']) ? implode(';', $sites) : null;
33+
34+
return $detour;
35+
});
36+
37+
$headers = $detours->flatMap(fn (array $detour): array => array_keys($detour))->unique()->toArray();
38+
39+
$writer = SimpleExcelWriter::create($fullPath)->addHeader($headers);
40+
41+
$writer->addRows($detours->toArray());
42+
43+
$writer->close();
44+
45+
return $fullPath;
46+
}
47+
48+
public static function bind(): void
49+
{
50+
app()->singleton(ExportsDetours::class, static::class);
51+
}
52+
}

src/Actions/ImportDetour.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace JustBetter\Detour\Actions;
4+
5+
use JustBetter\Detour\Contracts\ImportsDetour;
6+
use JustBetter\Detour\Contracts\ResolvesRepository;
7+
use JustBetter\Detour\Data\Form;
8+
9+
class ImportDetour implements ImportsDetour
10+
{
11+
public function __construct(protected ResolvesRepository $resolvesRepository) {}
12+
13+
public function import(array $data): void
14+
{
15+
/** @var string $sites */
16+
$sites = isset($data['sites']) ? $data['sites'] : '';
17+
$data['sites'] = $sites ? explode(';', $sites) : '';
18+
19+
$data = Form::make($data);
20+
21+
$repository = $this->resolvesRepository->resolve();
22+
if ($data->id) {
23+
$repository->update($data->id, $data);
24+
} else {
25+
$repository->store($data);
26+
}
27+
}
28+
29+
public static function bind(): void
30+
{
31+
app()->singleton(ImportsDetour::class, static::class);
32+
}
33+
}

src/Actions/ImportDetours.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace JustBetter\Detour\Actions;
4+
5+
use Illuminate\Support\Facades\Storage;
6+
use JustBetter\Detour\Contracts\ImportsDetour;
7+
use JustBetter\Detour\Contracts\ImportsDetours;
8+
use Spatie\SimpleExcel\SimpleExcelReader;
9+
10+
class ImportDetours implements ImportsDetours
11+
{
12+
public function import(string $file): void
13+
{
14+
$disk = config()->string('justbetter.statamic-detour.actions.disk');
15+
16+
$file = Storage::disk($disk)->path($file);
17+
$rows = SimpleExcelReader::create($file)->getRows();
18+
$importDetour = app(ImportsDetour::class);
19+
// @phpstan-ignore-next-line argument.type
20+
$rows->each(function (array $row) use ($importDetour) {
21+
$importDetour->import($row);
22+
});
23+
}
24+
25+
public static function bind(): void
26+
{
27+
app()->singleton(ImportsDetours::class, static::class);
28+
}
29+
}

src/Contracts/ExportsDetours.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace JustBetter\Detour\Contracts;
4+
5+
interface ExportsDetours
6+
{
7+
public function export(): string;
8+
}

0 commit comments

Comments
 (0)