Compare commits
10 Commits
54db2e8e2b
...
ffee3b257d
Author | SHA1 | Date | |
---|---|---|---|
ffee3b257d | |||
957e90aff6 | |||
252f5ee6b7 | |||
2a2869e2c6 | |||
6a033d108c | |||
620217e1d5 | |||
a7e93681f3 | |||
9d7da548e3 | |||
2349008131 | |||
908b1e4bec |
@ -14,3 +14,6 @@ DB_PASSWORD=password
|
|||||||
EXTERNAL_WEBSERVER_PORT=80
|
EXTERNAL_WEBSERVER_PORT=80
|
||||||
CURRENT_UID=1000
|
CURRENT_UID=1000
|
||||||
XDG_CONFIG_HOME=/tmp
|
XDG_CONFIG_HOME=/tmp
|
||||||
|
|
||||||
|
VITE_CV_APP_URL=https://cv.kamilcraft.com
|
||||||
|
VITE_PORT=3001
|
||||||
|
27
app/Http/Controllers/Api/MessageController.php
Normal file
27
app/Http/Controllers/Api/MessageController.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\MessageRequest;
|
||||||
|
use App\Models\Message;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
class MessageController extends Controller
|
||||||
|
{
|
||||||
|
public function store(MessageRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$data = $request->toArray();
|
||||||
|
Message::query()->create([
|
||||||
|
'message' => $data['message'],
|
||||||
|
'email' => $data['email'],
|
||||||
|
'sender' => $data['sender'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Dziękuję za wiadomość! Odpowiem możliwie najszybciej.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -49,12 +49,24 @@ class CVController extends Controller
|
|||||||
'mission' => ($mission = $request->get('mission')) === [''] ? [] : $mission,
|
'mission' => ($mission = $request->get('mission')) === [''] ? [] : $mission,
|
||||||
'rodo' => ($rodo =$request->get('rodo')) === '' ? null : $rodo,
|
'rodo' => ($rodo =$request->get('rodo')) === '' ? null : $rodo,
|
||||||
'position' => $request->get('position'),
|
'position' => $request->get('position'),
|
||||||
|
'notes' => $request->get('notes'),
|
||||||
]);
|
]);
|
||||||
return redirect()
|
return redirect()
|
||||||
->route('admin.cv.store')
|
->route('admin.cv.store')
|
||||||
->with('success', 'Utworzono nowe CV dla firmy ' . $request->get('recipient'));
|
->with('success', 'Utworzono nowe CV dla firmy ' . $request->get('recipient'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateSendStatus(CV $cv): RedirectResponse
|
||||||
|
{
|
||||||
|
$cv->update([
|
||||||
|
'sended' => true,
|
||||||
|
'sended_timestamp' => now()
|
||||||
|
]);
|
||||||
|
return redirect()
|
||||||
|
->route('admin.cv.show', ['cv' => $cv])
|
||||||
|
->with('success', 'Status wysłania ustawiono jako "wysłano".');
|
||||||
|
}
|
||||||
|
|
||||||
public function edit(CV $cv): InertiaResponse
|
public function edit(CV $cv): InertiaResponse
|
||||||
{
|
{
|
||||||
return inertia('CV/Edit', [
|
return inertia('CV/Edit', [
|
||||||
@ -64,15 +76,30 @@ class CVController extends Controller
|
|||||||
|
|
||||||
public function update(CVRequest $request, CV $cv): RedirectResponse
|
public function update(CVRequest $request, CV $cv): RedirectResponse
|
||||||
{
|
{
|
||||||
$cv->update([
|
$toUpdate = [
|
||||||
'recipient' => $request->get('recipient'),
|
'recipient' => $request->get('recipient'),
|
||||||
'email' => $request->get('email'),
|
'email' => $request->get('email'),
|
||||||
'phone_number' => $request->get('phone_number'),
|
'phone_number' => $request->get('phone_number'),
|
||||||
'locations' => ($locations = $request->get('locations')) === [''] ? [] : $locations,
|
'locations' => ($locations = $request->get('locations')) === [''] ? [] : $locations,
|
||||||
'mission' => ($mission = $request->get('mission')) === [''] ? [] : $mission,
|
'mission' => ($mission = $request->get('mission')) === [''] ? [] : $mission,
|
||||||
'rodo' => ($rodo =$request->get('rodo')) === '' ? null : $rodo,
|
'rodo' => ($rodo =$request->get('rodo')) === '' ? null : $rodo,
|
||||||
'position' => $request->get('position'),
|
'position' => $request->get('position'),
|
||||||
|
'notes' => $request->get('notes'),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($cv->sended && ! $request->boolean('sended')) {
|
||||||
|
$toUpdate = array_merge($toUpdate, [
|
||||||
|
'sended' => false,
|
||||||
|
'sended_timestamp' => null,
|
||||||
]);
|
]);
|
||||||
|
} else if (! $cv->sended && $request->boolean('sended')) {
|
||||||
|
$toUpdate = array_merge($toUpdate, [
|
||||||
|
'sended' => true,
|
||||||
|
'sended_timestamp' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cv->update($toUpdate);
|
||||||
return redirect()
|
return redirect()
|
||||||
->back()
|
->back()
|
||||||
->with('success', 'Zaktualizowano CV dla firmy ' . $request->get('recipient'));
|
->with('success', 'Zaktualizowano CV dla firmy ' . $request->get('recipient'));
|
||||||
|
45
app/Http/Controllers/Dashboard/MessageController.php
Normal file
45
app/Http/Controllers/Dashboard/MessageController.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Dashboard;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Resources\MessageCollection;
|
||||||
|
use App\Http\Resources\MessageResource;
|
||||||
|
use App\Models\Message;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Inertia\Response as InertiaResponse;
|
||||||
|
|
||||||
|
class MessageController extends Controller
|
||||||
|
{
|
||||||
|
public function index() : InertiaResponse {
|
||||||
|
return inertia('Messages/Index', [
|
||||||
|
'messages' => new MessageCollection(Message::query()->orderByDesc('id')->get()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Message $message) : InertiaResponse
|
||||||
|
{
|
||||||
|
return inertia('Messages/Show', [
|
||||||
|
'message' => new MessageResource($message),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Message $message) : InertiaResponse
|
||||||
|
{
|
||||||
|
return inertia('Messages/ConfirmDelete', [
|
||||||
|
'message' => new MessageResource($message),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Message $message) : RedirectResponse
|
||||||
|
{
|
||||||
|
$sender = $message->sender;
|
||||||
|
$message->delete();
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('admin.message.index')
|
||||||
|
->with(['success' => 'Wiadomość od '. $sender .' została usunięta']);
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,6 @@ class Kernel extends HttpKernel
|
|||||||
\App\Http\Middleware\TrimStrings::class,
|
\App\Http\Middleware\TrimStrings::class,
|
||||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||||
Core::class,
|
Core::class,
|
||||||
\App\Http\Middleware\HandleInertiaRequests::class,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $middlewareGroups = [
|
protected $middlewareGroups = [
|
||||||
@ -29,6 +28,7 @@ class Kernel extends HttpKernel
|
|||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
|
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
\App\Http\Middleware\HandleInertiaRequests::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'api' => [
|
'api' => [
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||||
|
@ -18,6 +18,20 @@ class CVRequest extends FormRequest
|
|||||||
'mission' => 'nullable|string',
|
'mission' => 'nullable|string',
|
||||||
'rodo' => 'nullable|string',
|
'rodo' => 'nullable|string',
|
||||||
'position' => 'nullable|string',
|
'position' => 'nullable|string',
|
||||||
|
'notes' => 'nullable|string',
|
||||||
|
'sended' => 'nullable|boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
$this->merge([
|
||||||
|
'sended' => $this->toBoolean($this->sended),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toBoolean($booleable): bool
|
||||||
|
{
|
||||||
|
return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
34
app/Http/Requests/MessageRequest.php
Normal file
34
app/Http/Requests/MessageRequest.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class MessageRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'message' => 'required|string|min:3|max:500',
|
||||||
|
'sender' => 'required|string|min:3|max:50',
|
||||||
|
'email' => 'required|email|max:250',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'message.required' => 'Pole wiadomości jest wymagane.',
|
||||||
|
'sender.required' => 'Pole nadawcy jest wymagane.',
|
||||||
|
'email.required' => 'Pole e-mail jest wymagane.',
|
||||||
|
'message.min' => 'Pole wiadomości wymaga 3 znaki.',
|
||||||
|
'sender.min' => 'Pole nadawcy wymaga 3 znaki.',
|
||||||
|
'message.max' => 'Pole wiadomości może mieć maksymalnie 50 znaków.',
|
||||||
|
'sender.max' => 'Pole nadawcy może mieć maksymalnie 500 znaków.',
|
||||||
|
'email.email' => 'Pole musi być e-mailem.',
|
||||||
|
'email.max' => 'Pole e-mail może mieć maksymalnie 250 znaków.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ class ProjectRequest extends FormRequest
|
|||||||
|
|
||||||
'project_url' => 'nullable|string',
|
'project_url' => 'nullable|string',
|
||||||
'project_version' => 'nullable|string',
|
'project_version' => 'nullable|string',
|
||||||
'description' => 'nullable|string',
|
'description' => 'required|string|min:3',
|
||||||
'visible' => 'required|boolean'
|
'visible' => 'required|boolean'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,16 @@ class FullCVResource extends JsonResource
|
|||||||
'locations' => $this->locations,
|
'locations' => $this->locations,
|
||||||
'views' => $this->resource->info()->select('id')->get()->count(),
|
'views' => $this->resource->info()->select('id')->get()->count(),
|
||||||
'registeredViews' => $this->views,
|
'registeredViews' => $this->views,
|
||||||
|
'sended' => [
|
||||||
|
'status' => $this->sended,
|
||||||
|
'datetime' => $this->sended_timestamp?->format('d-m-Y H:i:s'),
|
||||||
|
],
|
||||||
|
'position' => $this->position,
|
||||||
'mission' => explode(PHP_EOL, $this->mission ?? '', 5),
|
'mission' => explode(PHP_EOL, $this->mission ?? '', 5),
|
||||||
'rodo' => $this->rodo,
|
'rodo' => $this->rodo,
|
||||||
'position' => $this->position,
|
'notes' => $this->notes,
|
||||||
|
'created' => $this->created_at->format('d-m-Y H:i:s'),
|
||||||
|
'updated' => $this->updated_at->format('d-m-Y H:i:s'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
app/Http/Resources/MessageCollection.php
Normal file
17
app/Http/Resources/MessageCollection.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class MessageCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
22
app/Http/Resources/MessageResource.php
Normal file
22
app/Http/Resources/MessageResource.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class MessageResource extends JsonResource
|
||||||
|
{
|
||||||
|
public static $wrap = null;
|
||||||
|
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'sender' => $this->sender,
|
||||||
|
'email' => $this->email,
|
||||||
|
'message' => $this->message,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,9 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
* @property string|null $mission
|
* @property string|null $mission
|
||||||
* @property string|null $rodo
|
* @property string|null $rodo
|
||||||
* @property string|null $position
|
* @property string|null $position
|
||||||
|
* @property string|null $notes
|
||||||
* @property int $views
|
* @property int $views
|
||||||
|
* @property bool $sended
|
||||||
*/
|
*/
|
||||||
class CV extends Model
|
class CV extends Model
|
||||||
{
|
{
|
||||||
@ -30,6 +32,8 @@ class CV extends Model
|
|||||||
protected $casts = [
|
protected $casts = [
|
||||||
'locations' => 'array',
|
'locations' => 'array',
|
||||||
'views' => 'integer',
|
'views' => 'integer',
|
||||||
|
'sended' => 'boolean',
|
||||||
|
'sended_timestamp' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected function phoneNumber(): Attribute
|
protected function phoneNumber(): Attribute
|
||||||
|
23
app/Models/Message.php
Normal file
23
app/Models/Message.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $id
|
||||||
|
* @param string $message
|
||||||
|
* @param string $email
|
||||||
|
* @param string $sender
|
||||||
|
*/
|
||||||
|
class Message extends Model
|
||||||
|
{
|
||||||
|
use HasFactory,
|
||||||
|
SoftDeletes;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('cvs', function (Blueprint $table) {
|
||||||
|
$table->text('notes')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('cvs', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('notes');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('messages', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('message', 500);
|
||||||
|
$table->string('email', 250);
|
||||||
|
$table->string('sender', 50);
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('messages');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('cvs', function (Blueprint $table) {
|
||||||
|
$table->boolean('sended')->nullable()->default(false)->after('views');
|
||||||
|
$table->timestamp('sended_timestamp')->nullable()->after('sended');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('cvs', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('sended');
|
||||||
|
$table->dropColumn('sended_timestamp');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -39,7 +39,7 @@ services:
|
|||||||
entrypoint: [ 'npm' ]
|
entrypoint: [ 'npm' ]
|
||||||
ports:
|
ports:
|
||||||
- '3000:3000'
|
- '3000:3000'
|
||||||
- '3001:3001'
|
- '${VITE_PORT:-3001}:${VITE_PORT:-3001}'
|
||||||
volumes:
|
volumes:
|
||||||
- .:/application
|
- .:/application
|
||||||
networks:
|
networks:
|
||||||
|
@ -32,6 +32,7 @@ const form = useForm({
|
|||||||
mission: missionToString,
|
mission: missionToString,
|
||||||
rodo: null,
|
rodo: null,
|
||||||
position: null,
|
position: null,
|
||||||
|
notes: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
function createCV() {
|
function createCV() {
|
||||||
@ -85,15 +86,23 @@ function createCV() {
|
|||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="position"
|
id="position"
|
||||||
label="Stanowisko"
|
label="Stanowisko (opcjonalne)"
|
||||||
placeholder="Stanowisko na jakie jest rekrutacja."
|
placeholder="Stanowisko na jakie jest rekrutacja."
|
||||||
v-model="form.position"
|
v-model="form.position"
|
||||||
:error="form.errors.position"
|
:error="form.errors.position"
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
id="notes"
|
||||||
|
type="textarea"
|
||||||
|
label="Notatki (opcjonalne)"
|
||||||
|
placeholder="Notatka dla administratora"
|
||||||
|
v-model="form.notes"
|
||||||
|
:error="form.errors.notes"
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="mission"
|
id="mission"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
label="Misja - wstęp"
|
label="Misja - wstęp (opcjonalne)"
|
||||||
placeholder="Krótki opis, list motywacyjny."
|
placeholder="Krótki opis, list motywacyjny."
|
||||||
v-model="form.mission"
|
v-model="form.mission"
|
||||||
:error="form.errors.mission"
|
:error="form.errors.mission"
|
||||||
@ -101,7 +110,7 @@ function createCV() {
|
|||||||
<Input
|
<Input
|
||||||
id="rodo"
|
id="rodo"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
label="RODO"
|
label="RODO (opcjonalne)"
|
||||||
placeholder="Klauzula informacyjna RODO"
|
placeholder="Klauzula informacyjna RODO"
|
||||||
v-model="form.rodo"
|
v-model="form.rodo"
|
||||||
:error="form.errors.rodo"
|
:error="form.errors.rodo"
|
||||||
|
@ -39,6 +39,8 @@ const form = useForm({
|
|||||||
mission: missionToString,
|
mission: missionToString,
|
||||||
rodo: props.cv.rodo,
|
rodo: props.cv.rodo,
|
||||||
position: props.cv.position,
|
position: props.cv.position,
|
||||||
|
notes: props.cv.notes,
|
||||||
|
sended: props.cv.sended.status,
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateCV() {
|
function updateCV() {
|
||||||
@ -103,6 +105,14 @@ function updateCV() {
|
|||||||
v-model="form.position"
|
v-model="form.position"
|
||||||
:error="form.errors.position"
|
:error="form.errors.position"
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
id="notes"
|
||||||
|
type="textarea"
|
||||||
|
label="Notatki (opcjonalne)"
|
||||||
|
placeholder="Notatka dla administratora"
|
||||||
|
v-model="form.notes"
|
||||||
|
:error="form.errors.notes"
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="mission"
|
id="mission"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@ -119,7 +129,20 @@ function updateCV() {
|
|||||||
v-model="form.rodo"
|
v-model="form.rodo"
|
||||||
:error="form.errors.rodo"
|
:error="form.errors.rodo"
|
||||||
/>
|
/>
|
||||||
<button class="px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Edytuj CV</button>
|
<Input
|
||||||
|
id="sended"
|
||||||
|
label="Wysłany"
|
||||||
|
type="checkbox"
|
||||||
|
v-model="form.sended"
|
||||||
|
/>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 sm:gap-2 items-center">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
:href="`/dashboard/cv/${cv.token}`"
|
||||||
|
class="col-span-1 flex justify-center items-center gap-3 w-full px-2 py-1 border-t-4 border-b-4 border-transparent hover:border-b-black"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'backward']" />Anuluj</InertiaLink>
|
||||||
|
<button class="col-span-1 md:col-span-2 px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]">Edytuj CV</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import EmptyState from '@/Share/Components/EmptyState.vue';
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
cvs: {
|
cvs: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -39,7 +41,7 @@ function copySlug(slug) {
|
|||||||
><FontAwesomeIcon :icon="['fas', 'plus']" /></InertiaLink>
|
><FontAwesomeIcon :icon="['fas', 'plus']" /></InertiaLink>
|
||||||
</header>
|
</header>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table v-if="cvs.data.length" class="w-full min-w-[600px] border-separate border-spacing-y-2">
|
<table v-if="cvs.data.length" class="w-full min-w-[600px] border-separate border-spacing-y-2 cursor-pointer">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col class="w-min" />
|
<col class="w-min" />
|
||||||
</colgroup>
|
</colgroup>
|
||||||
@ -72,19 +74,20 @@ function copySlug(slug) {
|
|||||||
as="button"
|
as="button"
|
||||||
class="px-3 py-3 text-lime-600 hover:text-lime-800 border-t-2 border-b-2 border-transparent hover:border-b-lime-600"
|
class="px-3 py-3 text-lime-600 hover:text-lime-800 border-t-2 border-b-2 border-transparent hover:border-b-lime-600"
|
||||||
:href="`/dashboard/cv/${cv.token}/edit`"
|
:href="`/dashboard/cv/${cv.token}/edit`"
|
||||||
title="Edytuj projekt"><FontAwesomeIcon :icon="['fas', 'pen-to-square']" /></InertiaLink>
|
title="Edytuj CV"><FontAwesomeIcon :icon="['fas', 'pen-to-square']" /></InertiaLink>
|
||||||
<InertiaLink
|
<InertiaLink
|
||||||
as="button"
|
as="button"
|
||||||
class="px-3 py-3 text-red-600 hover:text-red-800"
|
class="px-3 py-3 text-red-600 hover:text-red-800"
|
||||||
:href="`/dashboard/cv/${cv.token}/delete`"
|
:href="`/dashboard/cv/${cv.token}/delete`"
|
||||||
title="Usuń projekt z listy"><FontAwesomeIcon :icon="['fas', 'trash']" /></InertiaLink>
|
title="Usuń CV z listy"><FontAwesomeIcon :icon="['fas', 'trash']" /></InertiaLink>
|
||||||
</td>
|
</td>
|
||||||
</InertiaLink>
|
</InertiaLink>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div v-else>
|
<EmptyState v-else :icon="['fas', 'file']">
|
||||||
Pusta lista
|
<template #title>Nie znaleziono</template>
|
||||||
</div>
|
<template #text>Nie dodano jeszcze CV do listy.</template>
|
||||||
|
</EmptyState>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
import { computed } from 'vue';
|
||||||
|
import { router } from '@inertiajs/inertia';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
cv: {
|
cv: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
@ -9,10 +12,25 @@ defineProps({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const CV_URL = import.meta.env.VITE_CV_APP_URL;
|
||||||
|
const cvNotes = computed(() => {
|
||||||
|
const notes = props.cv.notes;
|
||||||
|
return notes ? props.cv.notes.split("\n") : null;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<InertiaHead title="Szczegóły CV" />
|
<InertiaHead title="Szczegóły CV" />
|
||||||
|
<div class="px-3 py-2">
|
||||||
|
<InertiaLink
|
||||||
|
v-if="!cv.sended.status"
|
||||||
|
as="button"
|
||||||
|
method="post"
|
||||||
|
:href="`/dashboard/cv/${cv.token}/sended`"
|
||||||
|
class="w-full px-0.5 py-1 rounded-lg bg-[#436da7] border-4 border-[#436da7] text-white text-lg hover:bg-transparent hover:text-[#436da7]"
|
||||||
|
title="Ustaw jako wysłane">Ustaw jako wysłane do odbiorcy.</InertiaLink>
|
||||||
|
</div>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<header class="flex justify-between items-center pb-4">
|
<header class="flex justify-between items-center pb-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@ -23,27 +41,30 @@ defineProps({
|
|||||||
title="Wróć do listy CV"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
title="Wróć do listy CV"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||||
<h1 class="text-3xl font-roboto font-light">Szczegóły CV</h1>
|
<h1 class="text-3xl font-roboto font-light">Szczegóły CV</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-3 sm:gap-2">
|
||||||
<a
|
<a
|
||||||
class="px-2 py-1 text-blue-600 hover:text-blue-800"
|
class="flex items-center gap-2 px-2 py-1 text-blue-600 hover:text-blue-800"
|
||||||
:href="`https://cv.kamilcraft.com/show/${cv.token}`"
|
:href="`${CV_URL}/show/${cv.token}`"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
title="Przekieruj do CV"><FontAwesomeIcon :icon="['fas', 'arrow-up-right-from-square']" /></a>
|
title="Przekieruj do CV"><FontAwesomeIcon :icon="['fas', 'arrow-up-right-from-square']" /><span class="hidden sm:inline-block">Przejdź do CV</span></a>
|
||||||
<InertiaLink
|
<InertiaLink
|
||||||
as="button"
|
as="button"
|
||||||
:href="`/dashboard/cv/${cv.token}/edit`"
|
:href="`/dashboard/cv/${cv.token}/edit`"
|
||||||
class="flex items-center gap-2 px-2 py-1 text-lime-600 hover:text-white hover:bg-lime-600 rounded-md"
|
class="flex items-center gap-2 px-2 py-1 text-lime-600 hover:text-white hover:bg-lime-600 rounded-md"
|
||||||
title="Usuń CV"
|
title="Usuń CV"
|
||||||
><FontAwesomeIcon :icon="['fas', 'pen-to-square']" />Edytuj</InertiaLink>
|
><FontAwesomeIcon :icon="['fas', 'pen-to-square']" /><span class="hidden sm:inline-block">Edytuj</span></InertiaLink>
|
||||||
<InertiaLink
|
<InertiaLink
|
||||||
as="button"
|
as="button"
|
||||||
:href="`/dashboard/cv/${cv.token}/delete`"
|
:href="`/dashboard/cv/${cv.token}/delete`"
|
||||||
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
||||||
title="Usuń CV"
|
title="Usuń CV"
|
||||||
><FontAwesomeIcon :icon="['fas', 'trash']" />Usuń</InertiaLink>
|
><FontAwesomeIcon :icon="['fas', 'trash']" /><span class="hidden sm:inline-block">Usuń</span></InertiaLink>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
<div v-if="cv.sended.status" class="max-w-screen-lg my-2 lg:mx-auto px-2 py-3 rounded-md bg-yellow-100 text-yellow-600 text-center">
|
||||||
|
CV jest oznaczone jako wysłane - {{ cv.sended.datetime }}
|
||||||
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<header>
|
<header>
|
||||||
<h2 class="text-2xl font-roboto font-light pb-3">Podstawowe informacje</h2>
|
<h2 class="text-2xl font-roboto font-light pb-3">Podstawowe informacje</h2>
|
||||||
@ -73,6 +94,30 @@ defineProps({
|
|||||||
<div class="text-gray-500 pb-0.5">Lokalizacje</div>
|
<div class="text-gray-500 pb-0.5">Lokalizacje</div>
|
||||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.locations.join(' / ') }}</p>
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.locations.join(' / ') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="cvNotes" class="md:col-span-2">
|
||||||
|
<div class="text-gray-500 pb-0.5">Notatki</div>
|
||||||
|
<div class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">
|
||||||
|
<p
|
||||||
|
v-for="(noteLine, key) in cvNotes"
|
||||||
|
:key="key"
|
||||||
|
>{{ noteLine }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<header>
|
||||||
|
<h2 class="text-2xl font-roboto font-light pb-3">Statystyka</h2>
|
||||||
|
</header>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Utworzono</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.created }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Zmodyfikowano</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.updated }}</p>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-gray-500 pb-0.5">Zachowane wyświetlenia</div>
|
<div class="text-gray-500 pb-0.5">Zachowane wyświetlenia</div>
|
||||||
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.views }}</p>
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ cv.views }}</p>
|
||||||
|
37
resources/js/Pages/Messages/ConfirmDelete.vue
Normal file
37
resources/js/Pages/Messages/ConfirmDelete.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<script setup>
|
||||||
|
import { router } from '@inertiajs/vue3';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function confirmDelete() {
|
||||||
|
router.delete(`/dashboard/message/${props.message.id}/delete`);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InertiaHead title="Usuwanie wiadomości" />
|
||||||
|
<div class="p-4">
|
||||||
|
<header class="pb-4">
|
||||||
|
<h1 class="text-3xl font-roboto font-light">Usuwanie wiadomości</h1>
|
||||||
|
</header>
|
||||||
|
<div class="max-w-[600px]">
|
||||||
|
<p class="mb-4">Na pewno usunąć wiadomość od {{ message.sender }}?</p>
|
||||||
|
<div class="grid grid-cols-3 gap-2">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
:href="`/dashboard/message/${message.id}`"
|
||||||
|
class="col-span-1 flex justify-center items-center gap-3 w-full px-2 py-1 border-t-4 border-b-4 border-transparent hover:border-b-black"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'backward']" />Anuluj</InertiaLink>
|
||||||
|
<button
|
||||||
|
@click.prevent="confirmDelete"
|
||||||
|
class="col-span-2 flex justify-center items-center gap-3 w-full px-2 py-1 rounded-md bg-red-600 border-4 border-red-600 text-white text-lg hover:bg-transparent hover:text-red-600"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'trash']" /><span class="whitespace-nowrap overflow-hidden overflow-ellipsis">Usuń wiadomość od {{ message.sender }}</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
65
resources/js/Pages/Messages/Index.vue
Normal file
65
resources/js/Pages/Messages/Index.vue
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script setup>
|
||||||
|
import EmptyState from '@/Share/Components/EmptyState.vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
messages: {
|
||||||
|
type: Object,
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InertiaHead title="Wiadomości" />
|
||||||
|
<div class="p-4">
|
||||||
|
<header class="flex justify-between items-center pb-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
href="/dashboard"
|
||||||
|
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||||
|
title="Wróc do dashboard"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||||
|
<h1 class="text-3xl font-roboto font-light">Wiadomości</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table v-if="messages.data.length" class="table-fixed w-full min-w-[600px] border-separate border-spacing-y-2 cursor-pointer">
|
||||||
|
<colgroup>
|
||||||
|
<col class="w-[40px] max-w-[60px]" />
|
||||||
|
<col class="w-[250px]" />
|
||||||
|
<col class="w-auto" />
|
||||||
|
<col class="w-[50px]" />
|
||||||
|
</colgroup>
|
||||||
|
<thead class="text-left bg-gray-100">
|
||||||
|
<th class="w-[40px] max-w-[60px] p-2 text-center">ID</th>
|
||||||
|
<th class="w-[250px] p-2">Wysyłający</th>
|
||||||
|
<th class="p-2">E-mail</th>
|
||||||
|
<th class="w-[50px] p-2"></th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<InertiaLink
|
||||||
|
as="tr"
|
||||||
|
v-for="(message, key) in messages.data"
|
||||||
|
:key="key"
|
||||||
|
class="px-3 py-2 bg-white hover:bg-neutral-200 rounded-md z-10"
|
||||||
|
:href="`/dashboard/message/${message.id}`">
|
||||||
|
<td class="p-2 w-[60px] text-center">#{{ message.id }}</td>
|
||||||
|
<td class="p-2 whitespace-nowrap overflow-hidden overflow-ellipsis">{{ message.sender }}</td>
|
||||||
|
<td class="p-2">{{ message.email }}</td>
|
||||||
|
<td class="flex items-center justify-end gap-2 p-3 z-50">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
class="px-3 py-3 text-red-600 hover:text-red-800"
|
||||||
|
:href="`/dashboard/message/${message.id}/delete`"
|
||||||
|
title="Usuń wiadomość z listy"><FontAwesomeIcon :icon="['fas', 'trash']" /></InertiaLink>
|
||||||
|
</td>
|
||||||
|
</InertiaLink>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<EmptyState v-else :icon="['fas', 'message']">
|
||||||
|
<template #title>Brak wiadomości</template>
|
||||||
|
<template #text>Nie przesłano jeszcze żadnej wiadomości.</template>
|
||||||
|
</EmptyState>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
71
resources/js/Pages/Messages/Show.vue
Normal file
71
resources/js/Pages/Messages/Show.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const splitMessage = computed(() => props.message.message.split("\n"));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InertiaHead title="Szczegóły wiadomości" />
|
||||||
|
<div class="p-4">
|
||||||
|
<header class="flex justify-between items-center pb-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
href="/dashboard/message"
|
||||||
|
class="px-2 text-xl text-gray-700 hover:text-black"
|
||||||
|
title="Wróć do listy wiadomości"><FontAwesomeIcon :icon="['fas', 'caret-left']" /></InertiaLink>
|
||||||
|
<h1 class="text-3xl font-roboto font-light">Szczegóły wiadomości</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-3 sm:gap-2">
|
||||||
|
<InertiaLink
|
||||||
|
as="button"
|
||||||
|
:href="`/dashboard/message/${message.id}/delete`"
|
||||||
|
class="flex items-center gap-2 px-2 py-1 text-red-600 hover:text-white hover:bg-red-600 rounded-md"
|
||||||
|
title="Usuń wiadomość"
|
||||||
|
><FontAwesomeIcon :icon="['fas', 'trash']" /><span class="hidden sm:inline-block">Usuń</span></InertiaLink>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="mb-4">
|
||||||
|
<header>
|
||||||
|
<h2 class="text-2xl font-roboto font-light pb-3">Podstawowe informacje</h2>
|
||||||
|
</header>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">ID</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white whitespace-nowrap overflow-hidden overflow-ellipsis">{{ message.id }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">Nadawca</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ message.sender }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-500 pb-0.5">E-mail</div>
|
||||||
|
<p class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">{{ message.email }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<header>
|
||||||
|
<h2 class="text-2xl font-roboto font-light pb-3">Treść wiadomości</h2>
|
||||||
|
</header>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div class="col-span-1 sm:col-span-2">
|
||||||
|
<div class="text-gray-500 pb-0.5">Wiadomość</div>
|
||||||
|
<div class="w-full min-w-full max-w-full px-2.5 py-2 border-b-2 rounded-md bg-white">
|
||||||
|
<p
|
||||||
|
v-for="(messageLine, key) in splitMessage"
|
||||||
|
:key="key"
|
||||||
|
>{{ messageLine }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -1,4 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import EmptyState from '@/Share/Components/EmptyState.vue';
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
categories: {
|
categories: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -16,7 +18,7 @@ defineProps({
|
|||||||
href="/dashboard/category/create"
|
href="/dashboard/category/create"
|
||||||
class="bg-blue-400 hover:bg-blue-500 text-white px-2.5 py-1 rounded-full"><FontAwesomeIcon :icon="['fas', 'plus']" /></InertiaLink>
|
class="bg-blue-400 hover:bg-blue-500 text-white px-2.5 py-1 rounded-full"><FontAwesomeIcon :icon="['fas', 'plus']" /></InertiaLink>
|
||||||
</header>
|
</header>
|
||||||
<ul class="flex flex-col gap-2">
|
<ul v-if="categories.length" class="flex flex-col gap-2">
|
||||||
<li
|
<li
|
||||||
v-for="(category, key) in categories"
|
v-for="(category, key) in categories"
|
||||||
:key="key"
|
:key="key"
|
||||||
@ -38,5 +40,9 @@ defineProps({
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<EmptyState v-else :icon="['fas', 'list']">
|
||||||
|
<template #title>Brak kategorii</template>
|
||||||
|
<template #text>Nie dodano jeszcze żadnej kategorii.</template>
|
||||||
|
</EmptyState>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
31
resources/js/Share/Components/EmptyState.vue
Normal file
31
resources/js/Share/Components/EmptyState.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
defaut: ['far', 'folder-open'],
|
||||||
|
},
|
||||||
|
showDescription: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="text-center my-5 text-gray-500">
|
||||||
|
<slot name="head">
|
||||||
|
<FontAwesomeIcon :icon="icon" class="mx-auto w-12 h-12" />
|
||||||
|
</slot>
|
||||||
|
<h3 class="mt-2 text-sm font-medium">
|
||||||
|
<slot name="title">Brak danych</slot>
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
v-if="showDescription"
|
||||||
|
class="text-sm"
|
||||||
|
>
|
||||||
|
<slot name="text">
|
||||||
|
Nie znaleziono danych.
|
||||||
|
</slot>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -27,13 +27,13 @@ defineProps({
|
|||||||
</InertiaLink>
|
</InertiaLink>
|
||||||
<nav>
|
<nav>
|
||||||
<ul class="flex gap-3 items-center font-bold">
|
<ul class="flex gap-3 items-center font-bold">
|
||||||
<li><InertiaLink class="text-white active:text-kamilcraft-green hover:text-black hover:underline" href="/dashboard">Dashboard</InertiaLink></li>
|
|
||||||
<li><InertiaLink class="text-white active:text-kamilcraft-green hover:text-black hover:underline" href="/dashboard/cv">CV</InertiaLink></li>
|
<li><InertiaLink class="text-white active:text-kamilcraft-green hover:text-black hover:underline" href="/dashboard/cv">CV</InertiaLink></li>
|
||||||
|
<li><InertiaLink class="text-white active:text-kamilcraft-green hover:text-black hover:underline" href="/dashboard/message">Msg</InertiaLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div v-if="messages?.info" class="max-w-screen-lg mx-2 lg:mx-auto mt-2 px-2 py-3 rounded-md bg-yellow-100 text-yellow-600 text-center">
|
<div v-if="messages?.info" class="max-w-screen-lg mx-2 lg:mx-auto mt-2 px-2 py-3 rounded-md bg-yellow-100 text-yellow-600 text-center">
|
||||||
{{ messages.info }}
|
{{ messages.info }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="messages?.error" class="max-w-screen-lg mx-2 lg:mx-auto mt-2 px-2 py-3 rounded-md bg-red-100 text-red-600 text-center">
|
<div v-if="messages?.error" class="max-w-screen-lg mx-2 lg:mx-auto mt-2 px-2 py-3 rounded-md bg-red-100 text-red-600 text-center">
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import EmptyState from '@/Share/Components/EmptyState.vue';
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
projects: {
|
projects: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -38,8 +40,9 @@ defineProps({
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div v-else>
|
<EmptyState v-else :icon="['fas', 'bars-progress']">
|
||||||
Empty
|
<template #title>Brak projektów</template>
|
||||||
</div>
|
<template #text>Nie dodano jeszcze żadnego projektu.</template>
|
||||||
|
</EmptyState>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
@ -19,3 +19,5 @@ Route::prefix('project')->group(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::get('cv/{cv}', 'CVController@show');
|
Route::get('cv/{cv}', 'CVController@show');
|
||||||
|
|
||||||
|
Route::post('message', 'MessageController@store');
|
||||||
|
@ -5,9 +5,19 @@ declare(strict_types=1);
|
|||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::name('admin.')->group(function () {
|
Route::name('admin.')->group(function () {
|
||||||
Route::namespace('Dashboard')->middleware('auth')->group(function () {
|
Route::namespace('Dashboard')->middleware('auth')->group(function (): void {
|
||||||
Route::get('', 'AdminPanelController')->name('home');
|
Route::get('', 'AdminPanelController')->name('home');
|
||||||
Route::name('cv.')->prefix('cv')->group(function () {
|
Route::name('message.')->prefix('message')->group(function (): void {
|
||||||
|
Route::get('', 'MessageController@index')
|
||||||
|
->name('index');
|
||||||
|
Route::get('{message}', 'MessageController@show')
|
||||||
|
->name('show');
|
||||||
|
Route::get('{message}/delete', 'MessageController@delete')
|
||||||
|
->name('delete');
|
||||||
|
Route::delete('{message}/delete', 'MessageController@destroy')
|
||||||
|
->name('destroy');
|
||||||
|
});
|
||||||
|
Route::name('cv.')->prefix('cv')->group(function (): void {
|
||||||
Route::get('', 'CVController@index')
|
Route::get('', 'CVController@index')
|
||||||
->name('index');
|
->name('index');
|
||||||
Route::get('create', 'CVController@create')
|
Route::get('create', 'CVController@create')
|
||||||
@ -16,6 +26,8 @@ Route::name('admin.')->group(function () {
|
|||||||
->name('store');
|
->name('store');
|
||||||
Route::get('{cv}', 'CVController@show')
|
Route::get('{cv}', 'CVController@show')
|
||||||
->name('show');
|
->name('show');
|
||||||
|
Route::post('{cv}/sended', 'CVController@updateSendStatus')
|
||||||
|
->name('sended');
|
||||||
Route::post('', 'CVController@store')
|
Route::post('', 'CVController@store')
|
||||||
->name('store');
|
->name('store');
|
||||||
Route::get('{cv}/edit', 'CVController@edit')
|
Route::get('{cv}/edit', 'CVController@edit')
|
||||||
@ -27,7 +39,7 @@ Route::name('admin.')->group(function () {
|
|||||||
Route::delete('{cv}/delete', 'CVController@destroy')
|
Route::delete('{cv}/delete', 'CVController@destroy')
|
||||||
->name('destroy');
|
->name('destroy');
|
||||||
});
|
});
|
||||||
Route::name('category.')->prefix('category')->group(function () {
|
Route::name('category.')->prefix('category')->group(function (): void {
|
||||||
Route::get('create', 'CategoryController@create')
|
Route::get('create', 'CategoryController@create')
|
||||||
->name('create');
|
->name('create');
|
||||||
Route::post('', 'CategoryController@store')
|
Route::post('', 'CategoryController@store')
|
||||||
@ -44,7 +56,7 @@ Route::name('admin.')->group(function () {
|
|||||||
->name('destroy');
|
->name('destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::name('project.')->prefix('project')->group(function () {
|
Route::name('project.')->prefix('project')->group(function (): void {
|
||||||
Route::get('create', 'ProjectController@create')
|
Route::get('create', 'ProjectController@create')
|
||||||
->name('create');
|
->name('create');
|
||||||
Route::post('', 'ProjectController@store')
|
Route::post('', 'ProjectController@store')
|
||||||
@ -62,7 +74,7 @@ Route::name('admin.')->group(function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::name('auth.')->namespace('Auth')->group(function () {
|
Route::name('auth.')->namespace('Auth')->group(function (): void {
|
||||||
Route::get('login', 'LoginController@login')
|
Route::get('login', 'LoginController@login')
|
||||||
->name('login');
|
->name('login');
|
||||||
Route::post('login', 'LoginController@authenticate')
|
Route::post('login', 'LoginController@authenticate')
|
||||||
|
71
vite.config.js
vendored
71
vite.config.js
vendored
@ -1,40 +1,43 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import laravel from 'laravel-vite-plugin';
|
import laravel from 'laravel-vite-plugin';
|
||||||
import { networkInterfaces } from 'os'
|
import { networkInterfaces } from 'os';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig((mode) => {
|
||||||
server: {
|
const env = loadEnv(mode, process.cwd(), "");
|
||||||
host: Object.values(networkInterfaces()).flat().find(i => i.family === 'IPv4' && !i.internal).address,
|
return {
|
||||||
port: 3001,
|
server: {
|
||||||
hmr: {
|
host: Object.values(networkInterfaces()).flat().find(i => i.family === 'IPv4' && !i.internal).address,
|
||||||
host: 'localhost',
|
port: parseInt(env.VITE_PORT ?? 3001),
|
||||||
},
|
hmr: {
|
||||||
},
|
host: 'localhost',
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': '/resources/js',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
vue({
|
|
||||||
template: {
|
|
||||||
transformAssetUrls: {
|
|
||||||
base: null,
|
|
||||||
includeAbsolute: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
laravel({
|
resolve: {
|
||||||
input: 'resources/js/app.js',
|
alias: {
|
||||||
ssr: 'resources/js/ssr.js',
|
'@': '/resources/js',
|
||||||
refresh: true,
|
},
|
||||||
}),
|
},
|
||||||
],
|
plugins: [
|
||||||
ssr: {
|
vue({
|
||||||
noExternal: [
|
template: {
|
||||||
'@inertiajs/server',
|
transformAssetUrls: {
|
||||||
'@vue/runtime-dom'
|
base: null,
|
||||||
|
includeAbsolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
laravel({
|
||||||
|
input: 'resources/js/app.js',
|
||||||
|
ssr: 'resources/js/ssr.js',
|
||||||
|
refresh: true,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
},
|
ssr: {
|
||||||
|
noExternal: [
|
||||||
|
'@inertiajs/server',
|
||||||
|
'@vue/runtime-dom'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user