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' | ||||||
|  |             ], | ||||||
|  |         }, | ||||||
|  |     } | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user