Compare commits
10
Commits
1bc3056c77
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d40a1dc444 | ||
|
|
aee4ce5ff2 | ||
|
|
55abf78d4a | ||
|
|
b94d53fed3 | ||
|
|
35625e354e | ||
|
|
072dfa6ba2 | ||
|
|
76e3053e50 | ||
|
|
cdcd34fa0f | ||
|
|
9491676956 | ||
|
|
5c98259148 |
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(php artisan route:list:*)",
|
||||||
|
"Bash(lsof:*)",
|
||||||
|
"Bash(curl:*)",
|
||||||
|
"Bash(php:*)",
|
||||||
|
"Bash(npm run build:*)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\PageContent;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class PageContentController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index(): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$content = PageContent::all();
|
||||||
|
return response()->json($content);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud ophalen mislukt',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validate([
|
||||||
|
'key' => 'required|string|max:255|unique:page_content,key',
|
||||||
|
'value' => 'required|string',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
], [
|
||||||
|
'key.required' => 'Sleutel is verplicht',
|
||||||
|
'key.unique' => 'Deze sleutel bestaat al',
|
||||||
|
'key.max' => 'Sleutel mag maximaal 255 karakters bevatten',
|
||||||
|
'value.required' => 'Waarde is verplicht',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$content = PageContent::create($validated);
|
||||||
|
|
||||||
|
return response()->json($content, 201);
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validatie mislukt',
|
||||||
|
'errors' => $e->errors()
|
||||||
|
], 422);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud aanmaken mislukt',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(PageContent $pageContent): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return response()->json($pageContent);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud ophalen mislukt',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, PageContent $pageContent): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validate([
|
||||||
|
'value' => 'required|string',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
], [
|
||||||
|
'value.required' => 'Waarde is verplicht',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pageContent->update($validated);
|
||||||
|
|
||||||
|
return response()->json($pageContent);
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validatie mislukt',
|
||||||
|
'errors' => $e->errors()
|
||||||
|
], 422);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud bijwerken mislukt',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(PageContent $pageContent): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$pageContent->delete();
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud succesvol verwijderd'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud verwijderen mislukt',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific content by key
|
||||||
|
*/
|
||||||
|
public function getByKey(string $key): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$content = PageContent::where('key', $key)->first();
|
||||||
|
|
||||||
|
if (!$content) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud niet gevonden'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($content);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud ophalen mislukt',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update or create content by key
|
||||||
|
*/
|
||||||
|
public function updateByKey(Request $request, string $key): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validate([
|
||||||
|
'value' => 'required|string',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
], [
|
||||||
|
'value.required' => 'Waarde is verplicht',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$content = PageContent::updateOrCreate(
|
||||||
|
['key' => $key],
|
||||||
|
[
|
||||||
|
'value' => $validated['value'],
|
||||||
|
'description' => $validated['description'] ?? null
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json($content);
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validatie mislukt',
|
||||||
|
'errors' => $e->errors()
|
||||||
|
], 422);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud bijwerken mislukt',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all content as key-value pairs for public access
|
||||||
|
*/
|
||||||
|
public function getPublicContent(): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$content = PageContent::all()->pluck('value', 'key');
|
||||||
|
return response()->json($content);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Pagina-inhoud ophalen mislukt',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\ServiceRequest;
|
use App\Models\ServiceRequest;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\Communication;
|
use App\Models\Communication;
|
||||||
|
use App\Mail\ServiceRequestNotification;
|
||||||
|
use App\Mail\PickupConfirmation;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
@@ -114,11 +116,11 @@ class ServiceRequestController extends Controller
|
|||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'actual_repair_date' => 'nullable|date|after:today',
|
'actual_repair_date' => 'nullable|date|after:today',
|
||||||
'actual_repair_time' => 'nullable|date_format:H:i',
|
'actual_repair_time' => 'nullable|date_format:H:i',
|
||||||
'status' => 'nullable|in:pending,confirmed,completed,cancelled',
|
'status' => 'nullable|in:pending,confirmed,mail_sent,completed,cancelled',
|
||||||
], [
|
], [
|
||||||
'actual_repair_date.after' => 'Werkelijke reparatie datum moet in de toekomst liggen',
|
'actual_repair_date.after' => 'Werkelijke reparatie datum moet in de toekomst liggen',
|
||||||
'actual_repair_time.date_format' => 'Werkelijke reparatie tijd moet een geldig tijdformaat hebben (HH:MM)',
|
'actual_repair_time.date_format' => 'Werkelijke reparatie tijd moet een geldig tijdformaat hebben (HH:MM)',
|
||||||
'status.in' => 'Status moet een van de volgende waarden hebben: in afwachting, bevestigd, voltooid, geannuleerd',
|
'status.in' => 'Status moet een van de volgende waarden hebben: in afwachting, bevestigd, bevestigd mail verstuurd, voltooid, geannuleerd',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$serviceRequest->update($validated);
|
$serviceRequest->update($validated);
|
||||||
@@ -251,6 +253,74 @@ class ServiceRequestController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send pickup confirmation email to customer
|
||||||
|
*/
|
||||||
|
public function sendPickupConfirmation(ServiceRequest $serviceRequest): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (empty($serviceRequest->email)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Geen e-mailadres beschikbaar voor deze service aanvraag'
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($serviceRequest->actual_repair_date) || empty($serviceRequest->actual_repair_time)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Werkelijke reparatie datum en tijd zijn vereist'
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the custom message from settings
|
||||||
|
$customMessage = Setting::getValue('pickup_confirmation_email_text', 'Uw fiets is klaar en kan worden opgehaald.');
|
||||||
|
|
||||||
|
// Format the pickup date and time
|
||||||
|
$pickupDate = \Carbon\Carbon::parse($serviceRequest->actual_repair_date)->format('d-m-Y');
|
||||||
|
$pickupTime = $serviceRequest->actual_repair_time;
|
||||||
|
|
||||||
|
// Send email
|
||||||
|
Mail::to($serviceRequest->email)->send(
|
||||||
|
new PickupConfirmation(
|
||||||
|
$serviceRequest,
|
||||||
|
$customMessage,
|
||||||
|
$pickupDate,
|
||||||
|
$pickupTime
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update status to 'mail_sent'
|
||||||
|
$serviceRequest->update(['status' => 'mail_sent']);
|
||||||
|
|
||||||
|
// Store communication record
|
||||||
|
$communication = Communication::create([
|
||||||
|
'service_request_id' => $serviceRequest->id,
|
||||||
|
'type' => 'email',
|
||||||
|
'message' => 'Ophaalbevestiging verzonden - Datum: ' . $pickupDate . ', Tijd: ' . $pickupTime,
|
||||||
|
'direction' => 'admin_to_customer',
|
||||||
|
'recipient' => $serviceRequest->email,
|
||||||
|
'sent_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Log::info('Ophaalbevestiging e-mail verzonden naar klant', [
|
||||||
|
'service_request_id' => $serviceRequest->id,
|
||||||
|
'email' => $serviceRequest->email,
|
||||||
|
'pickup_date' => $pickupDate,
|
||||||
|
'pickup_time' => $pickupTime
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Ophaalbevestiging succesvol verzonden',
|
||||||
|
'communication' => $communication,
|
||||||
|
'service_request' => $serviceRequest->fresh()
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Ophaalbevestiging verzenden mislukt',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get communication history
|
* Get communication history
|
||||||
*/
|
*/
|
||||||
@@ -279,8 +349,9 @@ class ServiceRequestController extends Controller
|
|||||||
$adminEmail = Setting::getValue('admin_notification_email');
|
$adminEmail = Setting::getValue('admin_notification_email');
|
||||||
|
|
||||||
if ($adminEmail) {
|
if ($adminEmail) {
|
||||||
// TODO: Implement actual email sending logic
|
// Send email to admin
|
||||||
// For now, we'll just log it
|
Mail::to($adminEmail)->send(new ServiceRequestNotification($serviceRequest));
|
||||||
|
|
||||||
Log::info('Notificatie e-mail verzonden naar beheerder', [
|
Log::info('Notificatie e-mail verzonden naar beheerder', [
|
||||||
'service_request_id' => $serviceRequest->id,
|
'service_request_id' => $serviceRequest->id,
|
||||||
'admin_email' => $adminEmail,
|
'admin_email' => $adminEmail,
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\ServiceRequest;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class PickupConfirmation extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public ServiceRequest $serviceRequest,
|
||||||
|
public string $customMessage,
|
||||||
|
public string $pickupDate,
|
||||||
|
public string $pickupTime
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: 'Uw fiets wordt opgehaald - ' . $this->serviceRequest->name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
view: 'emails.pickup-confirmation',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\ServiceRequest;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ServiceRequestNotification extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public ServiceRequest $serviceRequest
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: 'Nieuwe Service Aanvraag - ' . $this->serviceRequest->name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
view: 'emails.service-request-notification',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PageContent extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'page_content';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'key',
|
||||||
|
'value',
|
||||||
|
'description',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
'updated_at' => 'datetime',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -24,10 +24,8 @@ class ServiceRequest extends Model
|
|||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'requested_date' => 'date',
|
'requested_date' => 'date:Y-m-d',
|
||||||
'requested_time' => 'datetime:H:i',
|
'actual_repair_date' => 'date:Y-m-d',
|
||||||
'actual_repair_date' => 'date',
|
|
||||||
'actual_repair_time' => 'datetime:H:i',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function communications()
|
public function communications()
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'from' => [
|
'from' => [
|
||||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
'address' => env('MAIL_FROM_ADDRESS', 'admin@bike-service.nl'),
|
||||||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('page_content', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('key')->unique();
|
||||||
|
$table->text('value');
|
||||||
|
$table->string('description')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert default content
|
||||||
|
DB::table('page_content')->insert([
|
||||||
|
// Home page - Hero section
|
||||||
|
[
|
||||||
|
'key' => 'home_hero_title',
|
||||||
|
'value' => '🚲 Welkom bij Fiets Service Centrum',
|
||||||
|
'description' => 'Home page hero title',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_hero_subtitle',
|
||||||
|
'value' => 'Professionele fietsreparatie en onderhoudsdiensten',
|
||||||
|
'description' => 'Home page hero subtitle',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
// Home page - Feature panels
|
||||||
|
[
|
||||||
|
'key' => 'home_feature_1_title',
|
||||||
|
'value' => '🔧 Deskundige Reparaties',
|
||||||
|
'description' => 'Home page feature 1 title',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_feature_1_text',
|
||||||
|
'value' => 'Professionele fietsmonteurs met jarenlange ervaring',
|
||||||
|
'description' => 'Home page feature 1 caption',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_feature_2_title',
|
||||||
|
'value' => '⚡ Snelle Service',
|
||||||
|
'description' => 'Home page feature 2 title',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_feature_2_text',
|
||||||
|
'value' => 'Snelle doorlooptijden voor de meeste reparaties',
|
||||||
|
'description' => 'Home page feature 2 caption',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_feature_3_title',
|
||||||
|
'value' => '💰 Eerlijke Prijzen',
|
||||||
|
'description' => 'Home page feature 3 title',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_feature_3_text',
|
||||||
|
'value' => 'Concurrerende tarieven zonder verborgen kosten',
|
||||||
|
'description' => 'Home page feature 3 caption',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_feature_4_title',
|
||||||
|
'value' => '📅 Eenvoudig Boeken',
|
||||||
|
'description' => 'Home page feature 4 title',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_feature_4_text',
|
||||||
|
'value' => 'Eenvoudig online boekingssysteem',
|
||||||
|
'description' => 'Home page feature 4 caption',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
// Home page - CTA section
|
||||||
|
[
|
||||||
|
'key' => 'home_cta_title',
|
||||||
|
'value' => 'Klaar om je fiets te laten repareren?',
|
||||||
|
'description' => 'Home page CTA title',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_cta_subtitle',
|
||||||
|
'value' => 'Boek je afspraak online en krijg je fiets weer in topconditie.',
|
||||||
|
'description' => 'Home page CTA subtitle',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_cta_button_1_text',
|
||||||
|
'value' => 'Nu Service Boeken',
|
||||||
|
'description' => 'Home page CTA button 1 text',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'home_cta_button_2_text',
|
||||||
|
'value' => '💰 Prijslijst Bekijken',
|
||||||
|
'description' => 'Home page CTA button 2 text',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
// Booking page
|
||||||
|
[
|
||||||
|
'key' => 'booking_header_title',
|
||||||
|
'value' => '🚲 Boek Je Fiets Service',
|
||||||
|
'description' => 'Booking page header title',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'booking_header_subtitle',
|
||||||
|
'value' => 'Vul het onderstaande formulier in om je fietsreparatie of onderhoud in te plannen',
|
||||||
|
'description' => 'Booking page header subtitle',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('page_content');
|
||||||
|
}
|
||||||
|
};
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// For SQLite, we need to recreate the table to modify the enum
|
||||||
|
// First, create a temporary table with the new structure
|
||||||
|
Schema::create('service_requests_temp', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->text('address');
|
||||||
|
$table->string('place');
|
||||||
|
$table->string('email')->nullable();
|
||||||
|
$table->string('phone')->nullable();
|
||||||
|
$table->text('repair_description');
|
||||||
|
$table->date('requested_date');
|
||||||
|
$table->time('requested_time');
|
||||||
|
$table->date('actual_repair_date')->nullable();
|
||||||
|
$table->time('actual_repair_time')->nullable();
|
||||||
|
$table->enum('status', ['pending', 'confirmed', 'mail_sent', 'completed', 'cancelled'])->default('pending');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy data from old table to temp table
|
||||||
|
DB::statement('INSERT INTO service_requests_temp SELECT * FROM service_requests');
|
||||||
|
|
||||||
|
// Drop the old table
|
||||||
|
Schema::dropIfExists('service_requests');
|
||||||
|
|
||||||
|
// Rename temp table to original name
|
||||||
|
Schema::rename('service_requests_temp', 'service_requests');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// Recreate the table with original enum values
|
||||||
|
Schema::create('service_requests_temp', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->text('address');
|
||||||
|
$table->string('place');
|
||||||
|
$table->string('email')->nullable();
|
||||||
|
$table->string('phone')->nullable();
|
||||||
|
$table->text('repair_description');
|
||||||
|
$table->date('requested_date');
|
||||||
|
$table->time('requested_time');
|
||||||
|
$table->date('actual_repair_date')->nullable();
|
||||||
|
$table->time('actual_repair_time')->nullable();
|
||||||
|
$table->enum('status', ['pending', 'confirmed', 'completed', 'cancelled'])->default('pending');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy data, converting 'mail_sent' status to 'confirmed'
|
||||||
|
DB::statement("INSERT INTO service_requests_temp SELECT id, name, address, place, email, phone, repair_description, requested_date, requested_time, actual_repair_date, actual_repair_time, CASE WHEN status = 'mail_sent' THEN 'confirmed' ELSE status END, created_at, updated_at FROM service_requests");
|
||||||
|
|
||||||
|
// Drop the current table
|
||||||
|
Schema::dropIfExists('service_requests');
|
||||||
|
|
||||||
|
// Rename temp table to original name
|
||||||
|
Schema::rename('service_requests_temp', 'service_requests');
|
||||||
|
}
|
||||||
|
};
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// Check if we're using MySQL
|
||||||
|
$driver = DB::connection()->getDriverName();
|
||||||
|
|
||||||
|
if ($driver === 'mysql') {
|
||||||
|
// For MySQL, use ALTER TABLE MODIFY to update the ENUM
|
||||||
|
DB::statement("ALTER TABLE service_requests MODIFY COLUMN status ENUM('pending', 'confirmed', 'mail_sent', 'completed', 'cancelled') NOT NULL DEFAULT 'pending'");
|
||||||
|
}
|
||||||
|
// For SQLite, the other migration handles it
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// Check if we're using MySQL
|
||||||
|
$driver = DB::connection()->getDriverName();
|
||||||
|
|
||||||
|
if ($driver === 'mysql') {
|
||||||
|
// Revert to original ENUM values
|
||||||
|
DB::statement("ALTER TABLE service_requests MODIFY COLUMN status ENUM('pending', 'confirmed', 'completed', 'cancelled') NOT NULL DEFAULT 'pending'");
|
||||||
|
}
|
||||||
|
// For SQLite, the other migration handles it
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -21,11 +21,11 @@ class DatabaseSeeder extends Seeder
|
|||||||
|
|
||||||
// Create admin user with specified credentials
|
// Create admin user with specified credentials
|
||||||
User::updateOrCreate(
|
User::updateOrCreate(
|
||||||
['email' => 'admin@fietservice.nl'],
|
['email' => 'bennY@fiet-service.nl'],
|
||||||
[
|
[
|
||||||
'name' => 'admin',
|
'name' => 'Marijke & Benny',
|
||||||
'email' => 'admin@fietservice.nl',
|
'email' => 'Admin@fiet-service.nl',
|
||||||
'password' => Hash::make('Admin174'),
|
'password' => Hash::make('B3nny4B1k'),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,18 @@ class SettingsSeeder extends Seeder
|
|||||||
'key' => 'working_hours',
|
'key' => 'working_hours',
|
||||||
'value' => 'Maandag-Vrijdag: 08:00 - 18:00, Zaterdag: 09:00 - 16:00',
|
'value' => 'Maandag-Vrijdag: 08:00 - 18:00, Zaterdag: 09:00 - 16:00',
|
||||||
'description' => 'Bedrijfsopeningstijden'
|
'description' => 'Bedrijfsopeningstijden'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'key' => 'pickup_confirmation_email_text',
|
||||||
|
'value' => 'Beste klant,
|
||||||
|
|
||||||
|
Uw fiets is klaar en kan worden opgehaald op het onderstaande tijdstip.
|
||||||
|
|
||||||
|
Wij zien u graag tegemoet!
|
||||||
|
|
||||||
|
Met vriendelijke groet,
|
||||||
|
Fiets Service Centrum',
|
||||||
|
'description' => 'Standaard tekst voor ophaalbevestiging e-mail'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,45 @@
|
|||||||
|
<svg viewBox="0 0 600 250" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Main Logo Group -->
|
||||||
|
<g transform="translate(300, 125)">
|
||||||
|
<!-- Bike Icon -->
|
||||||
|
<g transform="translate(-180, -30)">
|
||||||
|
<!-- Rear wheel -->
|
||||||
|
<circle cx="40" cy="45" r="30" fill="none" stroke="#ffffff" stroke-width="5"/>
|
||||||
|
<circle cx="40" cy="45" r="20" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.3"/>
|
||||||
|
<circle cx="40" cy="45" r="3" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Front wheel -->
|
||||||
|
<circle cx="160" cy="45" r="30" fill="none" stroke="#ffffff" stroke-width="5"/>
|
||||||
|
<circle cx="160" cy="45" r="20" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.3"/>
|
||||||
|
<circle cx="160" cy="45" r="3" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Frame -->
|
||||||
|
<path d="M 40 45 L 100 45 L 130 5 M 100 45 L 160 45"
|
||||||
|
fill="none" stroke="#ffffff" stroke-width="5"
|
||||||
|
stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Seat post and seat -->
|
||||||
|
<path d="M 70 45 L 90 10" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<ellipse cx="90" cy="5" rx="12" ry="5" fill="#f0d9b5"/>
|
||||||
|
|
||||||
|
<!-- Handlebars -->
|
||||||
|
<path d="M 130 5 L 145 5" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<circle cx="128" cy="5" r="4" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Pedal area -->
|
||||||
|
<circle cx="100" cy="45" r="8" fill="#f0d9b5"/>
|
||||||
|
<path d="M 95 50 L 105 50" fill="none" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Text -->
|
||||||
|
<text x="30" y="0" font-family="Arial, Helvetica, sans-serif" font-size="52" font-weight="bold" fill="#ffffff" letter-spacing="3">BIKE</text>
|
||||||
|
<text x="30" y="50" font-family="Arial, Helvetica, sans-serif" font-size="48" font-weight="600" fill="#f0d9b5" letter-spacing="2">SERVICE</text>
|
||||||
|
|
||||||
|
<!-- Accent lines -->
|
||||||
|
<line x1="-185" y1="68" x2="250" y2="68" stroke="#ffffff" stroke-width="3"/>
|
||||||
|
<line x1="-185" y1="72" x2="250" y2="72" stroke="#f0d9b5" stroke-width="1.5"/>
|
||||||
|
|
||||||
|
<!-- Tagline -->
|
||||||
|
<text x="32" y="100" font-family="Arial, Helvetica, sans-serif" font-size="16" font-style="italic" fill="#f0f0f0" letter-spacing="0.5">Jouw fiets, onze zorg</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,48 @@
|
|||||||
|
<svg viewBox="0 0 600 250" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- White background for clean export -->
|
||||||
|
<rect width="600" height="250" fill="white"/>
|
||||||
|
|
||||||
|
<!-- Main Logo Group -->
|
||||||
|
<g transform="translate(300, 125)">
|
||||||
|
<!-- Bike Icon -->
|
||||||
|
<g transform="translate(-180, -30)">
|
||||||
|
<!-- Rear wheel -->
|
||||||
|
<circle cx="40" cy="45" r="30" fill="none" stroke="#2c5f5d" stroke-width="5"/>
|
||||||
|
<circle cx="40" cy="45" r="20" fill="none" stroke="#2c5f5d" stroke-width="2" opacity="0.3"/>
|
||||||
|
<circle cx="40" cy="45" r="3" fill="#2c5f5d"/>
|
||||||
|
|
||||||
|
<!-- Front wheel -->
|
||||||
|
<circle cx="160" cy="45" r="30" fill="none" stroke="#2c5f5d" stroke-width="5"/>
|
||||||
|
<circle cx="160" cy="45" r="20" fill="none" stroke="#2c5f5d" stroke-width="2" opacity="0.3"/>
|
||||||
|
<circle cx="160" cy="45" r="3" fill="#2c5f5d"/>
|
||||||
|
|
||||||
|
<!-- Frame -->
|
||||||
|
<path d="M 40 45 L 100 45 L 130 5 M 100 45 L 160 45"
|
||||||
|
fill="none" stroke="#2c5f5d" stroke-width="5"
|
||||||
|
stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Seat post and seat -->
|
||||||
|
<path d="M 70 45 L 90 10" fill="none" stroke="#2c5f5d" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<ellipse cx="90" cy="5" rx="12" ry="5" fill="#8b6f47"/>
|
||||||
|
|
||||||
|
<!-- Handlebars -->
|
||||||
|
<path d="M 130 5 L 145 5" fill="none" stroke="#2c5f5d" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<circle cx="128" cy="5" r="4" fill="#2c5f5d"/>
|
||||||
|
|
||||||
|
<!-- Pedal area -->
|
||||||
|
<circle cx="100" cy="45" r="8" fill="#8b6f47"/>
|
||||||
|
<path d="M 95 50 L 105 50" fill="none" stroke="#2c5f5d" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Text -->
|
||||||
|
<text x="30" y="0" font-family="Arial, Helvetica, sans-serif" font-size="52" font-weight="bold" fill="#2c5f5d" letter-spacing="3">BIKE</text>
|
||||||
|
<text x="30" y="50" font-family="Arial, Helvetica, sans-serif" font-size="48" font-weight="600" fill="#8b6f47" letter-spacing="2">SERVICE</text>
|
||||||
|
|
||||||
|
<!-- Accent lines -->
|
||||||
|
<line x1="-185" y1="68" x2="250" y2="68" stroke="#2c5f5d" stroke-width="3"/>
|
||||||
|
<line x1="-185" y1="72" x2="250" y2="72" stroke="#8b6f47" stroke-width="1.5"/>
|
||||||
|
|
||||||
|
<!-- Tagline -->
|
||||||
|
<text x="32" y="100" font-family="Arial, Helvetica, sans-serif" font-size="16" font-style="italic" fill="#666666" letter-spacing="0.5">Jouw fiets, onze zorg</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="nl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/images/bikeservice_logo_primary.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="Professionele fietsreparatie en onderhoudsdiensten" />
|
||||||
|
<title>Bike Service - Jouw fiets, onze zorg</title>
|
||||||
|
<script type="module" crossorigin src="/assets/index-Cl0qWLNb.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-Te_wrVZr.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Uw fiets wordt opgehaald</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #2c5f5d 0%, #3d7a77 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
max-width: 200px;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 4px solid #2c5f5d;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
.pickup-info {
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||||
|
padding: 25px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 3px solid #2c5f5d;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.pickup-info h2 {
|
||||||
|
color: #2c5f5d;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
.info-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.info-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-right: 15px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
.info-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c5f5d;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
.info-value {
|
||||||
|
color: #333;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.customer-info {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.customer-info h3 {
|
||||||
|
color: #2c5f5d;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.customer-row {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.customer-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
.customer-value {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
border-top: 1px solid #e1e8ed;
|
||||||
|
}
|
||||||
|
.footer p {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<img src="{{ config('app.url') }}/images/bikeservice_logo_navbar.svg" alt="Fiets Service Logo" class="logo" />
|
||||||
|
<h1>🚲 Uw fiets wordt opgehaald</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="message">
|
||||||
|
{{ $customMessage }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pickup-info">
|
||||||
|
<h2>📅 Ophaalgegevens</h2>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-icon">📅</div>
|
||||||
|
<div>
|
||||||
|
<div class="info-label">Datum van ophalen:</div>
|
||||||
|
<div class="info-value">{{ $pickupDate }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-icon">🕐</div>
|
||||||
|
<div>
|
||||||
|
<div class="info-label">Tijd van ophalen:</div>
|
||||||
|
<div class="info-value">{{ $pickupTime }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="customer-info">
|
||||||
|
<h3>Uw gegevens</h3>
|
||||||
|
<div class="customer-row">
|
||||||
|
<div class="customer-label">Naam:</div>
|
||||||
|
<div class="customer-value">{{ $serviceRequest->name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="customer-row">
|
||||||
|
<div class="customer-label">Adres:</div>
|
||||||
|
<div class="customer-value">{{ $serviceRequest->address }}, {{ $serviceRequest->place }}</div>
|
||||||
|
</div>
|
||||||
|
@if($serviceRequest->phone)
|
||||||
|
<div class="customer-row">
|
||||||
|
<div class="customer-label">Telefoon:</div>
|
||||||
|
<div class="customer-value">{{ $serviceRequest->phone }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p><strong>Fiets Service Centrum</strong></p>
|
||||||
|
<p>Dit is een automatisch gegenereerde e-mail.</p>
|
||||||
|
<p>Heeft u vragen? Neem dan contact met ons op.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Nieuwe Service Aanvraag</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #2c5f5d 0%, #3d7a77 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
.info-row {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c5f5d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>🚲 Nieuwe Service Aanvraag</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<p>Er is een nieuwe service aanvraag ontvangen via de website:</p>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="label">Naam:</div>
|
||||||
|
<div class="value">{{ $serviceRequest->name }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="label">Adres:</div>
|
||||||
|
<div class="value">{{ $serviceRequest->address }}, {{ $serviceRequest->place }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($serviceRequest->email)
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="label">E-mail:</div>
|
||||||
|
<div class="value">{{ $serviceRequest->email }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($serviceRequest->phone)
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="label">Telefoon:</div>
|
||||||
|
<div class="value">{{ $serviceRequest->phone }}</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="label">Gewenste datum:</div>
|
||||||
|
<div class="value">{{ \Carbon\Carbon::parse($serviceRequest->requested_date)->format('d-m-Y') }} om {{ $serviceRequest->requested_time }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="label">Reparatie beschrijving:</div>
|
||||||
|
<div class="value">{{ $serviceRequest->repair_description }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="label">Status:</div>
|
||||||
|
<div class="value">{{ ucfirst($serviceRequest->status) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="label">Aangemaakt op:</div>
|
||||||
|
<div class="value">{{ $serviceRequest->created_at->format('d-m-Y H:i') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p style="margin-top: 20px;">
|
||||||
|
<a href="{{ config('app.url') }}/admin" style="background-color: #2c5f5d; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;">
|
||||||
|
Bekijk in Admin Paneel
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Dit is een automatisch gegenereerde e-mail van uw Fiets Service systeem.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\BlockedDateController;
|
|||||||
use App\Http\Controllers\SettingController;
|
use App\Http\Controllers\SettingController;
|
||||||
use App\Http\Controllers\AuthController;
|
use App\Http\Controllers\AuthController;
|
||||||
use App\Http\Controllers\PricelistController;
|
use App\Http\Controllers\PricelistController;
|
||||||
|
use App\Http\Controllers\PageContentController;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -31,6 +32,9 @@ Route::get('/public/blocked-dates', [BlockedDateController::class, 'publicIndex'
|
|||||||
// Public service request creation (customers can submit requests)
|
// Public service request creation (customers can submit requests)
|
||||||
Route::post('/public/service-requests', [ServiceRequestController::class, 'store']);
|
Route::post('/public/service-requests', [ServiceRequestController::class, 'store']);
|
||||||
|
|
||||||
|
// Public page content (customers can view page text)
|
||||||
|
Route::get('/public/page-content', [PageContentController::class, 'getPublicContent']);
|
||||||
|
|
||||||
// Protected routes (require authentication)
|
// Protected routes (require authentication)
|
||||||
Route::middleware('auth:sanctum')->group(function () {
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
Route::get('/user', function (Request $request) {
|
Route::get('/user', function (Request $request) {
|
||||||
@@ -44,6 +48,7 @@ Route::middleware('auth:sanctum')->group(function () {
|
|||||||
Route::apiResource('service-requests', ServiceRequestController::class);
|
Route::apiResource('service-requests', ServiceRequestController::class);
|
||||||
Route::post('service-requests/{serviceRequest}/send-email', [ServiceRequestController::class, 'sendEmail']);
|
Route::post('service-requests/{serviceRequest}/send-email', [ServiceRequestController::class, 'sendEmail']);
|
||||||
Route::post('service-requests/{serviceRequest}/send-sms', [ServiceRequestController::class, 'sendSms']);
|
Route::post('service-requests/{serviceRequest}/send-sms', [ServiceRequestController::class, 'sendSms']);
|
||||||
|
Route::post('service-requests/{serviceRequest}/send-pickup-confirmation', [ServiceRequestController::class, 'sendPickupConfirmation']);
|
||||||
Route::get('service-requests/{serviceRequest}/communications', [ServiceRequestController::class, 'communications']);
|
Route::get('service-requests/{serviceRequest}/communications', [ServiceRequestController::class, 'communications']);
|
||||||
|
|
||||||
Route::apiResource('blocked-dates', BlockedDateController::class);
|
Route::apiResource('blocked-dates', BlockedDateController::class);
|
||||||
@@ -56,4 +61,9 @@ Route::middleware('auth:sanctum')->group(function () {
|
|||||||
// Admin pricelist management
|
// Admin pricelist management
|
||||||
Route::get('/admin/pricelist', [PricelistController::class, 'adminIndex']);
|
Route::get('/admin/pricelist', [PricelistController::class, 'adminIndex']);
|
||||||
Route::apiResource('pricelist', PricelistController::class)->except(['index']);
|
Route::apiResource('pricelist', PricelistController::class)->except(['index']);
|
||||||
|
|
||||||
|
// Admin page content management
|
||||||
|
Route::apiResource('page-content', PageContentController::class);
|
||||||
|
Route::get('page-content/key/{key}', [PageContentController::class, 'getByKey']);
|
||||||
|
Route::put('page-content/key/{key}', [PageContentController::class, 'updateByKey']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
// Serve the Vue SPA for all non-API routes
|
||||||
return ['Laravel' => app()->version()];
|
Route::get('/{any}', function () {
|
||||||
});
|
return file_get_contents(public_path('index.html'));
|
||||||
|
})->where('any', '.*');
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
This guide will help you deploy your Laravel + Vue.js application to an Ubuntu server.
|
This guide will help you deploy your Laravel + Vue.js application to an Ubuntu server.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
wwthe
|
||||||
- Ubuntu 20.04+ server with root/sudo access
|
- Ubuntu 20.04+ server with root/sudo access
|
||||||
- Domain name (optional but recommended)
|
- Domain name (optional but recommended)
|
||||||
- SSH access to your server
|
- SSH access to your server
|
||||||
|
|||||||
+4
-3
@@ -1,10 +1,11 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="nl">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/images/bikeservice_logo_primary.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vue + Laravel App</title>
|
<meta name="description" content="Professionele fietsreparatie en onderhoudsdiensten" />
|
||||||
|
<title>Bike Service - Jouw fiets, onze zorg</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<svg viewBox="0 0 600 250" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Main Logo Group -->
|
||||||
|
<g transform="translate(300, 125)">
|
||||||
|
<!-- Bike Icon -->
|
||||||
|
<g transform="translate(-180, -30)">
|
||||||
|
<!-- Rear wheel -->
|
||||||
|
<circle cx="40" cy="45" r="30" fill="none" stroke="#ffffff" stroke-width="5"/>
|
||||||
|
<circle cx="40" cy="45" r="20" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.3"/>
|
||||||
|
<circle cx="40" cy="45" r="3" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Front wheel -->
|
||||||
|
<circle cx="160" cy="45" r="30" fill="none" stroke="#ffffff" stroke-width="5"/>
|
||||||
|
<circle cx="160" cy="45" r="20" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.3"/>
|
||||||
|
<circle cx="160" cy="45" r="3" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Frame -->
|
||||||
|
<path d="M 40 45 L 100 45 L 130 5 M 100 45 L 160 45"
|
||||||
|
fill="none" stroke="#ffffff" stroke-width="5"
|
||||||
|
stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Seat post and seat -->
|
||||||
|
<path d="M 70 45 L 90 10" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<ellipse cx="90" cy="5" rx="12" ry="5" fill="#f0d9b5"/>
|
||||||
|
|
||||||
|
<!-- Handlebars -->
|
||||||
|
<path d="M 130 5 L 145 5" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<circle cx="128" cy="5" r="4" fill="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- Pedal area -->
|
||||||
|
<circle cx="100" cy="45" r="8" fill="#f0d9b5"/>
|
||||||
|
<path d="M 95 50 L 105 50" fill="none" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Text -->
|
||||||
|
<text x="30" y="0" font-family="Arial, Helvetica, sans-serif" font-size="52" font-weight="bold" fill="#ffffff" letter-spacing="3">BIKE</text>
|
||||||
|
<text x="30" y="50" font-family="Arial, Helvetica, sans-serif" font-size="48" font-weight="600" fill="#f0d9b5" letter-spacing="2">SERVICE</text>
|
||||||
|
|
||||||
|
<!-- Accent lines -->
|
||||||
|
<line x1="-185" y1="68" x2="250" y2="68" stroke="#ffffff" stroke-width="3"/>
|
||||||
|
<line x1="-185" y1="72" x2="250" y2="72" stroke="#f0d9b5" stroke-width="1.5"/>
|
||||||
|
|
||||||
|
<!-- Tagline -->
|
||||||
|
<text x="32" y="100" font-family="Arial, Helvetica, sans-serif" font-size="16" font-style="italic" fill="#f0f0f0" letter-spacing="0.5">Jouw fiets, onze zorg</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,48 @@
|
|||||||
|
<svg viewBox="0 0 600 250" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- White background for clean export -->
|
||||||
|
<rect width="600" height="250" fill="white"/>
|
||||||
|
|
||||||
|
<!-- Main Logo Group -->
|
||||||
|
<g transform="translate(300, 125)">
|
||||||
|
<!-- Bike Icon -->
|
||||||
|
<g transform="translate(-180, -30)">
|
||||||
|
<!-- Rear wheel -->
|
||||||
|
<circle cx="40" cy="45" r="30" fill="none" stroke="#2c5f5d" stroke-width="5"/>
|
||||||
|
<circle cx="40" cy="45" r="20" fill="none" stroke="#2c5f5d" stroke-width="2" opacity="0.3"/>
|
||||||
|
<circle cx="40" cy="45" r="3" fill="#2c5f5d"/>
|
||||||
|
|
||||||
|
<!-- Front wheel -->
|
||||||
|
<circle cx="160" cy="45" r="30" fill="none" stroke="#2c5f5d" stroke-width="5"/>
|
||||||
|
<circle cx="160" cy="45" r="20" fill="none" stroke="#2c5f5d" stroke-width="2" opacity="0.3"/>
|
||||||
|
<circle cx="160" cy="45" r="3" fill="#2c5f5d"/>
|
||||||
|
|
||||||
|
<!-- Frame -->
|
||||||
|
<path d="M 40 45 L 100 45 L 130 5 M 100 45 L 160 45"
|
||||||
|
fill="none" stroke="#2c5f5d" stroke-width="5"
|
||||||
|
stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Seat post and seat -->
|
||||||
|
<path d="M 70 45 L 90 10" fill="none" stroke="#2c5f5d" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<ellipse cx="90" cy="5" rx="12" ry="5" fill="#8b6f47"/>
|
||||||
|
|
||||||
|
<!-- Handlebars -->
|
||||||
|
<path d="M 130 5 L 145 5" fill="none" stroke="#2c5f5d" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<circle cx="128" cy="5" r="4" fill="#2c5f5d"/>
|
||||||
|
|
||||||
|
<!-- Pedal area -->
|
||||||
|
<circle cx="100" cy="45" r="8" fill="#8b6f47"/>
|
||||||
|
<path d="M 95 50 L 105 50" fill="none" stroke="#2c5f5d" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Text -->
|
||||||
|
<text x="30" y="0" font-family="Arial, Helvetica, sans-serif" font-size="52" font-weight="bold" fill="#2c5f5d" letter-spacing="3">BIKE</text>
|
||||||
|
<text x="30" y="50" font-family="Arial, Helvetica, sans-serif" font-size="48" font-weight="600" fill="#8b6f47" letter-spacing="2">SERVICE</text>
|
||||||
|
|
||||||
|
<!-- Accent lines -->
|
||||||
|
<line x1="-185" y1="68" x2="250" y2="68" stroke="#2c5f5d" stroke-width="3"/>
|
||||||
|
<line x1="-185" y1="72" x2="250" y2="72" stroke="#8b6f47" stroke-width="1.5"/>
|
||||||
|
|
||||||
|
<!-- Tagline -->
|
||||||
|
<text x="32" y="100" font-family="Arial, Helvetica, sans-serif" font-size="16" font-style="italic" fill="#666666" letter-spacing="0.5">Jouw fiets, onze zorg</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
+63
-10
@@ -3,7 +3,7 @@
|
|||||||
<nav class="navbar">
|
<nav class="navbar">
|
||||||
<div class="nav-container">
|
<div class="nav-container">
|
||||||
<router-link to="/" class="nav-logo">
|
<router-link to="/" class="nav-logo">
|
||||||
🚲 Fiets Service
|
<img src="/images/bikeservice_logo_navbar.svg" alt="Bike Service Logo" class="logo-image" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="nav-menu">
|
<div class="nav-menu">
|
||||||
<router-link to="/" class="nav-link">Home</router-link>
|
<router-link to="/" class="nav-link">Home</router-link>
|
||||||
@@ -41,6 +41,19 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: #2c5f5d;
|
||||||
|
--primary-dark: #1f4443;
|
||||||
|
--primary-light: #3d7a77;
|
||||||
|
--secondary-color: #8b6f47;
|
||||||
|
--secondary-dark: #6d5637;
|
||||||
|
--secondary-light: #a68a5f;
|
||||||
|
--text-dark: #2c3e50;
|
||||||
|
--text-gray: #666;
|
||||||
|
--bg-light: #f5f5f5;
|
||||||
|
--bg-white: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -49,13 +62,13 @@ export default {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
background-color: #f5f5f5;
|
background-color: var(--bg-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
|
||||||
padding: 1rem 0;
|
padding: 0.5rem 0;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 10px rgba(44, 95, 93, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-container {
|
.nav-container {
|
||||||
@@ -68,15 +81,26 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-logo {
|
.nav-logo {
|
||||||
color: white;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.5rem;
|
}
|
||||||
font-weight: bold;
|
|
||||||
|
.logo-image {
|
||||||
|
height: 70px;
|
||||||
|
width: auto;
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-image:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-menu {
|
.nav-menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link {
|
.nav-link {
|
||||||
@@ -84,11 +108,17 @@ body {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
transition: background-color 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link:hover {
|
.nav-link:hover {
|
||||||
background-color: rgba(255,255,255,0.1);
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link.router-link-active {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
@@ -96,4 +126,27 @@ body {
|
|||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nav-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-image {
|
||||||
|
height: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+527
-18
@@ -29,6 +29,43 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Status Filter -->
|
||||||
|
<div class="status-filter">
|
||||||
|
<div class="filter-title">Filter op status:</div>
|
||||||
|
<div class="filter-checkboxes">
|
||||||
|
<label class="filter-checkbox">
|
||||||
|
<input type="checkbox" v-model="statusFilters.pending" />
|
||||||
|
<span class="checkbox-label">
|
||||||
|
<span :class="['status-badge', 'pending']">{{ getStatusText('pending') }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="filter-checkbox">
|
||||||
|
<input type="checkbox" v-model="statusFilters.confirmed" />
|
||||||
|
<span class="checkbox-label">
|
||||||
|
<span :class="['status-badge', 'confirmed']">{{ getStatusText('confirmed') }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="filter-checkbox">
|
||||||
|
<input type="checkbox" v-model="statusFilters.mail_sent" />
|
||||||
|
<span class="checkbox-label">
|
||||||
|
<span :class="['status-badge', 'mail_sent']">{{ getStatusText('mail_sent') }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="filter-checkbox">
|
||||||
|
<input type="checkbox" v-model="statusFilters.completed" />
|
||||||
|
<span class="checkbox-label">
|
||||||
|
<span :class="['status-badge', 'completed']">{{ getStatusText('completed') }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="filter-checkbox">
|
||||||
|
<input type="checkbox" v-model="statusFilters.cancelled" />
|
||||||
|
<span class="checkbox-label">
|
||||||
|
<span :class="['status-badge', 'cancelled']">{{ getStatusText('cancelled') }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="requests-table">
|
<div class="requests-table">
|
||||||
<div class="table-header">
|
<div class="table-header">
|
||||||
<div class="header-cell">ID</div>
|
<div class="header-cell">ID</div>
|
||||||
@@ -42,11 +79,11 @@
|
|||||||
<div class="loading-text">Aanvragen laden...</div>
|
<div class="loading-text">Aanvragen laden...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="requests.length === 0" class="empty-row">
|
<div v-else-if="filteredRequests.length === 0" class="empty-row">
|
||||||
<div class="empty-text">Geen service aanvragen gevonden</div>
|
<div class="empty-text">Geen service aanvragen gevonden met de geselecteerde filters</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="table-row" v-for="request in requests" :key="request.id">
|
<div v-else class="table-row" v-for="request in filteredRequests" :key="request.id">
|
||||||
<div class="cell">{{ request.id }}</div>
|
<div class="cell">{{ request.id }}</div>
|
||||||
<div class="cell">{{ request.name }}</div>
|
<div class="cell">{{ request.name }}</div>
|
||||||
<div class="cell">{{ formatDateTime(request.requested_date, request.requested_time) }}</div>
|
<div class="cell">{{ formatDateTime(request.requested_date, request.requested_time) }}</div>
|
||||||
@@ -135,6 +172,149 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Page Content Tab -->
|
||||||
|
<div v-if="activeTab === 'content'" class="tab-content">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Pagina Teksten Beheer</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-sections">
|
||||||
|
<!-- Home Page Hero -->
|
||||||
|
<div class="content-section">
|
||||||
|
<h3>Openingspagina - Hero Sectie</h3>
|
||||||
|
<div class="content-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Hoofdtekst (Header)</label>
|
||||||
|
<input type="text" :value="getContentValue('home_hero_title')" @input="updateContentValue('home_hero_title', $event.target.value)" @blur="savePageContent('home_hero_title')" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Subtekst (Subheader)</label>
|
||||||
|
<input type="text" :value="getContentValue('home_hero_subtitle')" @input="updateContentValue('home_hero_subtitle', $event.target.value)" @blur="savePageContent('home_hero_subtitle')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Home Page Features -->
|
||||||
|
<div class="content-section">
|
||||||
|
<h3>Openingspagina - Panelen</h3>
|
||||||
|
<div class="content-form">
|
||||||
|
<div class="feature-group">
|
||||||
|
<h4>Paneel 1</h4>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Koptekst</label>
|
||||||
|
<input type="text" :value="getContentValue('home_feature_1_title')" @input="updateContentValue('home_feature_1_title', $event.target.value)" @blur="savePageContent('home_feature_1_title')" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Onderschrift</label>
|
||||||
|
<textarea :value="getContentValue('home_feature_1_text')" @input="updateContentValue('home_feature_1_text', $event.target.value)" @blur="savePageContent('home_feature_1_text')" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-group">
|
||||||
|
<h4>Paneel 2</h4>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Koptekst</label>
|
||||||
|
<input type="text" :value="getContentValue('home_feature_2_title')" @input="updateContentValue('home_feature_2_title', $event.target.value)" @blur="savePageContent('home_feature_2_title')" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Onderschrift</label>
|
||||||
|
<textarea :value="getContentValue('home_feature_2_text')" @input="updateContentValue('home_feature_2_text', $event.target.value)" @blur="savePageContent('home_feature_2_text')" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-group">
|
||||||
|
<h4>Paneel 3</h4>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Koptekst</label>
|
||||||
|
<input type="text" :value="getContentValue('home_feature_3_title')" @input="updateContentValue('home_feature_3_title', $event.target.value)" @blur="savePageContent('home_feature_3_title')" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Onderschrift</label>
|
||||||
|
<textarea :value="getContentValue('home_feature_3_text')" @input="updateContentValue('home_feature_3_text', $event.target.value)" @blur="savePageContent('home_feature_3_text')" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-group">
|
||||||
|
<h4>Paneel 4</h4>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Koptekst</label>
|
||||||
|
<input type="text" :value="getContentValue('home_feature_4_title')" @input="updateContentValue('home_feature_4_title', $event.target.value)" @blur="savePageContent('home_feature_4_title')" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Onderschrift</label>
|
||||||
|
<textarea :value="getContentValue('home_feature_4_text')" @input="updateContentValue('home_feature_4_text', $event.target.value)" @blur="savePageContent('home_feature_4_text')" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Home Page CTA -->
|
||||||
|
<div class="content-section">
|
||||||
|
<h3>Openingspagina - Knoppen Container</h3>
|
||||||
|
<div class="content-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Hoofdtekst</label>
|
||||||
|
<input type="text" :value="getContentValue('home_cta_title')" @input="updateContentValue('home_cta_title', $event.target.value)" @blur="savePageContent('home_cta_title')" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Subtekst</label>
|
||||||
|
<textarea :value="getContentValue('home_cta_subtitle')" @input="updateContentValue('home_cta_subtitle', $event.target.value)" @blur="savePageContent('home_cta_subtitle')" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Knop 1 Tekst</label>
|
||||||
|
<input type="text" :value="getContentValue('home_cta_button_1_text')" @input="updateContentValue('home_cta_button_1_text', $event.target.value)" @blur="savePageContent('home_cta_button_1_text')" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Knop 2 Tekst</label>
|
||||||
|
<input type="text" :value="getContentValue('home_cta_button_2_text')" @input="updateContentValue('home_cta_button_2_text', $event.target.value)" @blur="savePageContent('home_cta_button_2_text')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Booking Page -->
|
||||||
|
<div class="content-section">
|
||||||
|
<h3>Boekingspagina - Header</h3>
|
||||||
|
<div class="content-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Hoofdtekst (Header)</label>
|
||||||
|
<input type="text" :value="getContentValue('booking_header_title')" @input="updateContentValue('booking_header_title', $event.target.value)" @blur="savePageContent('booking_header_title')" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Onderschrift</label>
|
||||||
|
<textarea :value="getContentValue('booking_header_subtitle')" @input="updateContentValue('booking_header_subtitle', $event.target.value)" @blur="savePageContent('booking_header_subtitle')" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Section -->
|
||||||
|
<div class="content-section">
|
||||||
|
<h3>Openingspagina - Contact Sectie</h3>
|
||||||
|
<div class="content-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Hoofdtekst (Header)</label>
|
||||||
|
<input type="text" :value="getContentValue('home_contact_title')" @input="updateContentValue('home_contact_title', $event.target.value)" @blur="savePageContent('home_contact_title')" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Onderschrift</label>
|
||||||
|
<textarea :value="getContentValue('home_contact_subtitle')" @input="updateContentValue('home_contact_subtitle', $event.target.value)" @blur="savePageContent('home_contact_subtitle')" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Telefoonnummer</label>
|
||||||
|
<input type="text" :value="getContentValue('home_contact_phone')" @input="updateContentValue('home_contact_phone', $event.target.value)" @blur="savePageContent('home_contact_phone')" placeholder="+31 6 12345678" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>E-mailadres</label>
|
||||||
|
<input type="email" :value="getContentValue('home_contact_email')" @input="updateContentValue('home_contact_email', $event.target.value)" @blur="savePageContent('home_contact_email')" placeholder="info@fiet-service.nl" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Adres</label>
|
||||||
|
<input type="text" :value="getContentValue('home_contact_address')" @input="updateContentValue('home_contact_address', $event.target.value)" @blur="savePageContent('home_contact_address')" placeholder="Fietsstraat 123, 1234 AB Amsterdam" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Settings Tab -->
|
<!-- Settings Tab -->
|
||||||
<div v-if="activeTab === 'settings'" class="tab-content">
|
<div v-if="activeTab === 'settings'" class="tab-content">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -167,6 +347,19 @@
|
|||||||
💾
|
💾
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-group">
|
||||||
|
<label for="pickup_confirmation_email_text">Ophaalbevestiging E-mail Tekst</label>
|
||||||
|
<textarea
|
||||||
|
id="pickup_confirmation_email_text"
|
||||||
|
v-model="settings.pickup_confirmation_email_text"
|
||||||
|
rows="8"
|
||||||
|
placeholder="Standaard tekst voor ophaalbevestiging e-mail"
|
||||||
|
></textarea>
|
||||||
|
<button @click="updateSetting('pickup_confirmation_email_text', settings.pickup_confirmation_email_text)" class="save-btn" title="Opslaan">
|
||||||
|
💾
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -209,21 +402,30 @@
|
|||||||
<select v-model="selectedRequest.status" @change="updateRequestStatus">
|
<select v-model="selectedRequest.status" @change="updateRequestStatus">
|
||||||
<option value="pending">In afwachting</option>
|
<option value="pending">In afwachting</option>
|
||||||
<option value="confirmed">Bevestigd</option>
|
<option value="confirmed">Bevestigd</option>
|
||||||
|
<option value="mail_sent">Bevestigd mail verstuurd</option>
|
||||||
<option value="completed">Voltooid</option>
|
<option value="completed">Voltooid</option>
|
||||||
<option value="cancelled">Geannuleerd</option>
|
<option value="cancelled">Geannuleerd</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-row">
|
<div class="detail-row">
|
||||||
<strong>Werkelijke Reparatie Datum:</strong>
|
<strong>Werkelijke ophaalDatum:</strong>
|
||||||
<input type="date" v-model="selectedRequest.actual_repair_date" @change="updateRequestDate" />
|
<input type="date" v-model="selectedRequest.actual_repair_date" @change="updateRequestDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-row">
|
<div class="detail-row">
|
||||||
<strong>Werkelijke Reparatie Tijd:</strong>
|
<strong>Werkelijke ophaaltijd:</strong>
|
||||||
<input type="time" v-model="selectedRequest.actual_repair_time" @change="updateRequestTime" />
|
<input type="time" v-model="selectedRequest.actual_repair_time" @change="updateRequestTime" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
<button
|
||||||
|
@click="sendPickupConfirmation"
|
||||||
|
class="btn-pickup-confirmation"
|
||||||
|
:disabled="!selectedRequest?.email || !selectedRequest?.actual_repair_date || !selectedRequest?.actual_repair_time || pickupConfirmationLoading"
|
||||||
|
title="Verstuur ophaalbevestiging e-mail naar klant"
|
||||||
|
>
|
||||||
|
{{ pickupConfirmationLoading ? '📧 Versturen...' : '📧 Ophaalbevestiging Versturen' }}
|
||||||
|
</button>
|
||||||
<button @click="closeRequestModal" class="btn-primary">Sluiten</button>
|
<button @click="closeRequestModal" class="btn-primary">Sluiten</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -381,6 +583,16 @@ export default {
|
|||||||
const blockedDates = ref([])
|
const blockedDates = ref([])
|
||||||
const settings = ref({})
|
const settings = ref({})
|
||||||
const pricelist = ref([])
|
const pricelist = ref([])
|
||||||
|
const pageContent = ref([])
|
||||||
|
|
||||||
|
// Status filter state
|
||||||
|
const statusFilters = ref({
|
||||||
|
pending: true,
|
||||||
|
confirmed: true,
|
||||||
|
mail_sent: true,
|
||||||
|
completed: true,
|
||||||
|
cancelled: true
|
||||||
|
})
|
||||||
|
|
||||||
// Modal states
|
// Modal states
|
||||||
const showRequestModal = ref(false)
|
const showRequestModal = ref(false)
|
||||||
@@ -405,11 +617,13 @@ export default {
|
|||||||
// Loading states
|
// Loading states
|
||||||
const emailLoading = ref(false)
|
const emailLoading = ref(false)
|
||||||
const smsLoading = ref(false)
|
const smsLoading = ref(false)
|
||||||
|
const pickupConfirmationLoading = ref(false)
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'requests', label: 'Service Aanvragen' },
|
{ id: 'requests', label: 'Service Aanvragen' },
|
||||||
{ id: 'blocked', label: 'Geblokkeerde Data' },
|
{ id: 'blocked', label: 'Geblokkeerde Data' },
|
||||||
{ id: 'pricelist', label: 'Prijslijst' },
|
{ id: 'pricelist', label: 'Prijslijst' },
|
||||||
|
{ id: 'content', label: 'Pagina Teksten' },
|
||||||
{ id: 'settings', label: 'Instellingen' }
|
{ id: 'settings', label: 'Instellingen' }
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -419,11 +633,19 @@ export default {
|
|||||||
return tomorrow.toISOString().split('T')[0]
|
return tomorrow.toISOString().split('T')[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Filtered requests based on status checkboxes
|
||||||
|
const filteredRequests = computed(() => {
|
||||||
|
return requests.value.filter(request => {
|
||||||
|
return statusFilters.value[request.status] === true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Helper functions for Dutch text
|
// Helper functions for Dutch text
|
||||||
const getStatusText = (status) => {
|
const getStatusText = (status) => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'pending': 'In afwachting',
|
'pending': 'In afwachting',
|
||||||
'confirmed': 'Bevestigd',
|
'confirmed': 'Bevestigd',
|
||||||
|
'mail_sent': 'Bevestigd mail verstuurd',
|
||||||
'completed': 'Voltooid',
|
'completed': 'Voltooid',
|
||||||
'cancelled': 'Geannuleerd'
|
'cancelled': 'Geannuleerd'
|
||||||
}
|
}
|
||||||
@@ -517,6 +739,19 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchPageContent = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/page-content', {
|
||||||
|
headers: authStore.getAuthHeaders()
|
||||||
|
})
|
||||||
|
if (response.ok) {
|
||||||
|
pageContent.value = await response.json()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch page content:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const refreshRequests = async () => {
|
const refreshRequests = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
@@ -535,16 +770,29 @@ export default {
|
|||||||
})
|
})
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const latestRequest = await response.json()
|
const latestRequest = await response.json()
|
||||||
|
|
||||||
|
// Format dates for HTML date inputs (YYYY-MM-DD)
|
||||||
|
if (latestRequest.actual_repair_date) {
|
||||||
|
// Extract just the date part if it's a datetime string
|
||||||
|
latestRequest.actual_repair_date = latestRequest.actual_repair_date.split('T')[0]
|
||||||
|
}
|
||||||
|
|
||||||
selectedRequest.value = { ...latestRequest }
|
selectedRequest.value = { ...latestRequest }
|
||||||
} else {
|
} else {
|
||||||
// Fallback to local data if API call fails
|
// Fallback to local data if API call fails
|
||||||
const latestRequest = requests.value.find(r => r.id === request.id)
|
const latestRequest = requests.value.find(r => r.id === request.id)
|
||||||
|
if (latestRequest && latestRequest.actual_repair_date) {
|
||||||
|
latestRequest.actual_repair_date = latestRequest.actual_repair_date.split('T')[0]
|
||||||
|
}
|
||||||
selectedRequest.value = latestRequest ? { ...latestRequest } : { ...request }
|
selectedRequest.value = latestRequest ? { ...latestRequest } : { ...request }
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch latest request data:', error)
|
console.error('Failed to fetch latest request data:', error)
|
||||||
// Fallback to local data if API call fails
|
// Fallback to local data if API call fails
|
||||||
const latestRequest = requests.value.find(r => r.id === request.id)
|
const latestRequest = requests.value.find(r => r.id === request.id)
|
||||||
|
if (latestRequest && latestRequest.actual_repair_date) {
|
||||||
|
latestRequest.actual_repair_date = latestRequest.actual_repair_date.split('T')[0]
|
||||||
|
}
|
||||||
selectedRequest.value = latestRequest ? { ...latestRequest } : { ...request }
|
selectedRequest.value = latestRequest ? { ...latestRequest } : { ...request }
|
||||||
} finally {
|
} finally {
|
||||||
requestModalLoading.value = false
|
requestModalLoading.value = false
|
||||||
@@ -653,6 +901,51 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sendPickupConfirmation = async () => {
|
||||||
|
if (!selectedRequest.value?.email) {
|
||||||
|
alert('Geen e-mailadres beschikbaar voor deze klant')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedRequest.value?.actual_repair_date || !selectedRequest.value?.actual_repair_time) {
|
||||||
|
alert('Vul eerst de werkelijke ophaaldatum en tijd in')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('Weet u zeker dat u de ophaalbevestiging wilt versturen naar ' + selectedRequest.value.email + '?')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pickupConfirmationLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/service-requests/${selectedRequest.value.id}/send-pickup-confirmation`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: authStore.getAuthHeaders()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json()
|
||||||
|
alert('Ophaalbevestiging succesvol verzonden!')
|
||||||
|
|
||||||
|
// Update the selected request with the new status
|
||||||
|
if (data.service_request) {
|
||||||
|
selectedRequest.value = data.service_request
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the requests list
|
||||||
|
await fetchRequests()
|
||||||
|
} else {
|
||||||
|
const error = await response.json()
|
||||||
|
alert('Fout bij versturen: ' + (error.message || 'Onbekende fout'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to send pickup confirmation:', error)
|
||||||
|
alert('Fout bij versturen van ophaalbevestiging')
|
||||||
|
} finally {
|
||||||
|
pickupConfirmationLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const editBlockedDate = (date) => {
|
const editBlockedDate = (date) => {
|
||||||
editingBlockedDate.value = date
|
editingBlockedDate.value = date
|
||||||
blockedDateForm.value = { date: date.date, reason: date.reason || '' }
|
blockedDateForm.value = { date: date.date, reason: date.reason || '' }
|
||||||
@@ -828,6 +1121,41 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Page content functions
|
||||||
|
const getContentValue = (key) => {
|
||||||
|
const content = pageContent.value.find(c => c.key === key)
|
||||||
|
return content ? content.value : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateContentValue = (key, value) => {
|
||||||
|
const content = pageContent.value.find(c => c.key === key)
|
||||||
|
if (content) {
|
||||||
|
content.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const savePageContent = async (key) => {
|
||||||
|
const content = pageContent.value.find(c => c.key === key)
|
||||||
|
if (!content) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/page-content/key/${key}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: authStore.getAuthHeaders(),
|
||||||
|
body: JSON.stringify({
|
||||||
|
value: content.value,
|
||||||
|
description: content.description
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log(`Content ${key} updated successfully`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update page content:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
const formatDateTime = (date, time) => {
|
const formatDateTime = (date, time) => {
|
||||||
if (!date) return 'N/A'
|
if (!date) return 'N/A'
|
||||||
@@ -847,6 +1175,7 @@ export default {
|
|||||||
fetchRequests()
|
fetchRequests()
|
||||||
fetchBlockedDates()
|
fetchBlockedDates()
|
||||||
fetchPricelist()
|
fetchPricelist()
|
||||||
|
fetchPageContent()
|
||||||
fetchSettings()
|
fetchSettings()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -855,9 +1184,12 @@ export default {
|
|||||||
tabs,
|
tabs,
|
||||||
loading,
|
loading,
|
||||||
requests,
|
requests,
|
||||||
|
filteredRequests,
|
||||||
|
statusFilters,
|
||||||
blockedDates,
|
blockedDates,
|
||||||
settings,
|
settings,
|
||||||
pricelist,
|
pricelist,
|
||||||
|
pageContent,
|
||||||
user: computed(() => authStore.user),
|
user: computed(() => authStore.user),
|
||||||
showRequestModal,
|
showRequestModal,
|
||||||
showAddBlockedDate,
|
showAddBlockedDate,
|
||||||
@@ -877,6 +1209,7 @@ export default {
|
|||||||
communications,
|
communications,
|
||||||
emailLoading,
|
emailLoading,
|
||||||
smsLoading,
|
smsLoading,
|
||||||
|
pickupConfirmationLoading,
|
||||||
minDate,
|
minDate,
|
||||||
getStatusText,
|
getStatusText,
|
||||||
getCommTypeText,
|
getCommTypeText,
|
||||||
@@ -895,6 +1228,7 @@ export default {
|
|||||||
viewCommunications,
|
viewCommunications,
|
||||||
submitEmail,
|
submitEmail,
|
||||||
submitSms,
|
submitSms,
|
||||||
|
sendPickupConfirmation,
|
||||||
editBlockedDate,
|
editBlockedDate,
|
||||||
saveBlockedDate,
|
saveBlockedDate,
|
||||||
deleteBlockedDate,
|
deleteBlockedDate,
|
||||||
@@ -905,6 +1239,9 @@ export default {
|
|||||||
updateRequestStatus,
|
updateRequestStatus,
|
||||||
updateRequestDate,
|
updateRequestDate,
|
||||||
updateRequestTime,
|
updateRequestTime,
|
||||||
|
getContentValue,
|
||||||
|
updateContentValue,
|
||||||
|
savePageContent,
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
formatDate
|
formatDate
|
||||||
}
|
}
|
||||||
@@ -924,13 +1261,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.admin-header h1 {
|
.admin-header h1 {
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-header p {
|
.admin-header p {
|
||||||
color: #666;
|
color: var(--text-gray);
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
@@ -984,8 +1321,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-button.active {
|
.tab-button.active {
|
||||||
border-bottom-color: #3498db;
|
border-bottom-color: var(--primary-color);
|
||||||
color: #3498db;
|
color: var(--primary-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,12 +1345,68 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-header h2 {
|
.section-header h2 {
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Status Filter Styles */
|
||||||
|
.status-filter {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border: 2px solid #e1e8ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-checkboxes {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-checkbox:hover {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-checkbox input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-checkbox .status-badge {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.refresh-btn, .add-btn {
|
.refresh-btn, .add-btn {
|
||||||
background: #3498db;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@@ -1029,9 +1422,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.refresh-btn:hover:not(:disabled), .add-btn:hover {
|
.refresh-btn:hover:not(:disabled), .add-btn:hover {
|
||||||
background: #2980b9;
|
background: var(--primary-dark);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
box-shadow: 0 4px 8px rgba(44, 95, 93, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh-btn:disabled {
|
.refresh-btn:disabled {
|
||||||
@@ -1140,6 +1533,12 @@ export default {
|
|||||||
color: #0c5460;
|
color: #0c5460;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-badge.mail_sent {
|
||||||
|
background: #e7d4c5;
|
||||||
|
color: #6d5637;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.status-badge.completed {
|
.status-badge.completed {
|
||||||
background: #d4edda;
|
background: #d4edda;
|
||||||
color: #155724;
|
color: #155724;
|
||||||
@@ -1433,16 +1832,24 @@ export default {
|
|||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-group input {
|
.setting-group input,
|
||||||
|
.setting-group textarea {
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border: 2px solid #e1e8ed;
|
border: 2px solid #e1e8ed;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-group input:focus {
|
.setting-group input:focus,
|
||||||
|
.setting-group textarea:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #3498db;
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-group textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-btn {
|
.save-btn {
|
||||||
@@ -1612,12 +2019,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: #3498db;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled) {
|
.btn-primary:hover:not(:disabled) {
|
||||||
background: #2980b9;
|
background: var(--primary-dark);
|
||||||
|
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
@@ -1629,6 +2037,30 @@ export default {
|
|||||||
background: #5a6268;
|
background: #5a6268;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-pickup-confirmation {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: var(--secondary-color);
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-pickup-confirmation:hover:not(:disabled) {
|
||||||
|
background: var(--secondary-dark);
|
||||||
|
box-shadow: 0 4px 10px rgba(139, 111, 71, 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-pickup-confirmation:disabled {
|
||||||
|
background: #bdc3c7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
/* Form Styles */
|
/* Form Styles */
|
||||||
.form-group {
|
.form-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1800,4 +2232,81 @@ export default {
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Page Content Styles */
|
||||||
|
.content-sections {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #e1e8ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section h3 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section h4 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-group {
|
||||||
|
background: white;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e1e8ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-group:not(:last-child) {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-form .form-group {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-form .form-group label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-form .form-group input,
|
||||||
|
.content-form .form-group textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 2px solid #e1e8ed;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-form .form-group input:focus,
|
||||||
|
.content-form .form-group textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
background: #f8f9fa;
|
||||||
|
box-shadow: 0 0 0 3px rgba(44, 95, 93, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-form .form-group textarea {
|
||||||
|
resize: vertical;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="booking">
|
<div class="booking">
|
||||||
<div class="booking-header">
|
<div class="booking-header">
|
||||||
<h1>🚲 Boek Je Fiets Service</h1>
|
<h1>{{ content.booking_header_title || '🚲 Boek Je Fiets Service' }}</h1>
|
||||||
<p>Vul het onderstaande formulier in om je fietsreparatie of onderhoud in te plannen</p>
|
<p>{{ content.booking_header_subtitle || 'Vul het onderstaande formulier in om je fietsreparatie of onderhoud in te plannen' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="booking-form-container">
|
<div class="booking-form-container">
|
||||||
@@ -181,6 +181,7 @@ export default {
|
|||||||
const formError = ref('')
|
const formError = ref('')
|
||||||
const showSuccessModal = ref(false)
|
const showSuccessModal = ref(false)
|
||||||
const submittedRequest = ref(null)
|
const submittedRequest = ref(null)
|
||||||
|
const content = ref({})
|
||||||
|
|
||||||
// Date picker state
|
// Date picker state
|
||||||
const currentDate = ref(new Date())
|
const currentDate = ref(new Date())
|
||||||
@@ -275,6 +276,17 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchContent = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/public/page-content')
|
||||||
|
if (response.ok) {
|
||||||
|
content.value = await response.json()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch page content:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const previousMonth = () => {
|
const previousMonth = () => {
|
||||||
currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)
|
currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)
|
||||||
}
|
}
|
||||||
@@ -343,6 +355,7 @@ export default {
|
|||||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||||
form.value.requested_date = tomorrow.toISOString().split('T')[0]
|
form.value.requested_date = tomorrow.toISOString().split('T')[0]
|
||||||
fetchBlockedDates() // Fetch blocked dates on mount
|
fetchBlockedDates() // Fetch blocked dates on mount
|
||||||
|
fetchContent() // Fetch page content on mount
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -351,6 +364,7 @@ export default {
|
|||||||
formError,
|
formError,
|
||||||
showSuccessModal,
|
showSuccessModal,
|
||||||
submittedRequest,
|
submittedRequest,
|
||||||
|
content,
|
||||||
minDate,
|
minDate,
|
||||||
submitForm,
|
submitForm,
|
||||||
closeSuccessModal,
|
closeSuccessModal,
|
||||||
@@ -379,13 +393,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.booking-header h1 {
|
.booking-header h1 {
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-header p {
|
.booking-header p {
|
||||||
color: #666;
|
color: var(--text-gray);
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,7 +433,7 @@ export default {
|
|||||||
|
|
||||||
.form-group label {
|
.form-group label {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +449,8 @@ export default {
|
|||||||
.form-group input:focus,
|
.form-group input:focus,
|
||||||
.form-group textarea:focus {
|
.form-group textarea:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #3498db;
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(44, 95, 93, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group textarea {
|
.form-group textarea {
|
||||||
@@ -476,7 +491,7 @@ export default {
|
|||||||
|
|
||||||
.current-month {
|
.current-month {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +507,7 @@ export default {
|
|||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,14 +552,15 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.calendar-date.selected {
|
.calendar-date.selected {
|
||||||
background: #3498db;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-date.today {
|
.calendar-date.today {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #3498db;
|
color: var(--primary-color);
|
||||||
|
border: 2px solid var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-date.today.selected {
|
.calendar-date.today.selected {
|
||||||
@@ -566,7 +582,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
background: #3498db;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
@@ -574,11 +590,14 @@ export default {
|
|||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn:hover:not(:disabled) {
|
.submit-btn:hover:not(:disabled) {
|
||||||
background: #2980b9;
|
background: var(--primary-dark);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 15px rgba(44, 95, 93, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn:disabled {
|
.submit-btn:disabled {
|
||||||
@@ -615,7 +634,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-header h3 {
|
.modal-header h3 {
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,18 +653,21 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: #3498db;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transition: background-color 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 6px rgba(44, 95, 93, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.btn-primary:hover {
|
||||||
background: #2980b9;
|
background: var(--primary-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
+181
-27
@@ -1,46 +1,101 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<div class="hero">
|
<div class="hero">
|
||||||
<h1>🚲 Welkom bij Fiets Service Centrum</h1>
|
<h1>{{ content.home_hero_title || 'Welkom bij Fiets Service Centrum' }}</h1>
|
||||||
<p>Professionele fietsreparatie en onderhoudsdiensten</p>
|
<p>{{ content.home_hero_subtitle || 'Professionele fietsreparatie en onderhoudsdiensten' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="features">
|
<div class="features">
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>🔧 Deskundige Reparaties</h3>
|
<h3>{{ content.home_feature_1_title || '🔧 Deskundige Reparaties' }}</h3>
|
||||||
<p>Professionele fietsmonteurs met jarenlange ervaring</p>
|
<p>{{ content.home_feature_1_text || 'Professionele fietsmonteurs met jarenlange ervaring' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>⚡ Snelle Service</h3>
|
<h3>{{ content.home_feature_2_title || '⚡ Snelle Service' }}</h3>
|
||||||
<p>Snelle doorlooptijden voor de meeste reparaties</p>
|
<p>{{ content.home_feature_2_text || 'Snelle doorlooptijden voor de meeste reparaties' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>💰 Eerlijke Prijzen</h3>
|
<h3>{{ content.home_feature_3_title || '💰 Eerlijke Prijzen' }}</h3>
|
||||||
<p>Concurrerende tarieven zonder verborgen kosten</p>
|
<p>{{ content.home_feature_3_text || 'Concurrerende tarieven zonder verborgen kosten' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>📅 Eenvoudig Boeken</h3>
|
<h3>{{ content.home_feature_4_title || '📅 Eenvoudig Boeken' }}</h3>
|
||||||
<p>Eenvoudig online boekingssysteem</p>
|
<p>{{ content.home_feature_4_text || 'Eenvoudig online boekingssysteem' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cta">
|
<div class="cta">
|
||||||
<h2>Klaar om je fiets te laten repareren?</h2>
|
<h2>{{ content.home_cta_title || 'Klaar om je fiets te laten repareren?' }}</h2>
|
||||||
<p>Boek je afspraak online en krijg je fiets weer in topconditie.</p>
|
<p>{{ content.home_cta_subtitle || 'Boek je afspraak online en krijg je fiets weer in topconditie.' }}</p>
|
||||||
<div class="cta-buttons">
|
<div class="cta-buttons">
|
||||||
<router-link to="/booking" class="cta-button">Nu Service Boeken</router-link>
|
<router-link to="/booking" class="cta-button">{{ content.home_cta_button_1_text || 'Nu Service Boeken' }}</router-link>
|
||||||
<router-link to="/pricelist" class="cta-button secondary">💰 Prijslijst Bekijken</router-link>
|
<router-link to="/pricelist" class="cta-button secondary">{{ content.home_cta_button_2_text || '💰 Prijslijst Bekijken' }}</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contact-section">
|
||||||
|
<h2>{{ content.home_contact_title || 'Neem Contact Op' }}</h2>
|
||||||
|
<p class="contact-subtitle">{{ content.home_contact_subtitle || 'Heeft u vragen of wilt u meer informatie? Neem gerust contact met ons op!' }}</p>
|
||||||
|
|
||||||
|
<div class="contact-info">
|
||||||
|
<div class="contact-item" v-if="content.home_contact_phone">
|
||||||
|
<div class="contact-icon">📞</div>
|
||||||
|
<div class="contact-details">
|
||||||
|
<h4>Telefoon</h4>
|
||||||
|
<a :href="'tel:' + content.home_contact_phone.replace(/\s/g, '')">{{ content.home_contact_phone }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contact-item" v-if="content.home_contact_email">
|
||||||
|
<div class="contact-icon">📧</div>
|
||||||
|
<div class="contact-details">
|
||||||
|
<h4>Email</h4>
|
||||||
|
<a :href="'mailto:' + content.home_contact_email">{{ content.home_contact_email }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contact-item" v-if="content.home_contact_address">
|
||||||
|
<div class="contact-icon">📍</div>
|
||||||
|
<div class="contact-details">
|
||||||
|
<h4>Adres</h4>
|
||||||
|
<p>{{ content.home_contact_address }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home'
|
name: 'Home',
|
||||||
|
setup() {
|
||||||
|
const content = ref({})
|
||||||
|
|
||||||
|
const fetchContent = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/public/page-content')
|
||||||
|
if (response.ok) {
|
||||||
|
content.value = await response.json()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch page content:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -50,11 +105,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hero {
|
.hero {
|
||||||
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 4rem 2rem;
|
padding: 4rem 2rem;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
margin-bottom: 3rem;
|
margin-bottom: 3rem;
|
||||||
|
box-shadow: 0 4px 20px rgba(44, 95, 93, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero h1 {
|
.hero h1 {
|
||||||
@@ -87,30 +143,31 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.feature-card h3 {
|
.feature-card h3 {
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-card p {
|
.feature-card p {
|
||||||
color: #666;
|
color: var(--text-gray);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta {
|
.cta {
|
||||||
background: #f8f9fa;
|
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||||
padding: 3rem 2rem;
|
padding: 3rem 2rem;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
border: 2px dashed #3498db;
|
border: 3px solid var(--primary-color);
|
||||||
|
box-shadow: 0 4px 20px rgba(44, 95, 93, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta h2 {
|
.cta h2 {
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta p {
|
.cta p {
|
||||||
color: #666;
|
color: var(--text-gray);
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
@@ -123,25 +180,122 @@ export default {
|
|||||||
|
|
||||||
.cta-button {
|
.cta-button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #3498db;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
transition: background-color 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-button:hover {
|
.cta-button:hover {
|
||||||
background: #2980b9;
|
background: var(--primary-dark);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 15px rgba(44, 95, 93, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-button.secondary {
|
.cta-button.secondary {
|
||||||
background: #e74c3c;
|
background: var(--secondary-color);
|
||||||
|
box-shadow: 0 4px 10px rgba(139, 111, 71, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-button.secondary:hover {
|
.cta-button.secondary:hover {
|
||||||
background: #c0392b;
|
background: var(--secondary-dark);
|
||||||
|
box-shadow: 0 6px 15px rgba(139, 111, 71, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-section {
|
||||||
|
background: white;
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-top: 3rem;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-section h2 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-subtitle {
|
||||||
|
color: var(--text-gray);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid #e1e8ed;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-item:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.2);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-icon {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-details {
|
||||||
|
text-align: left;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-details h4 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-details p {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-details a {
|
||||||
|
color: var(--secondary-color);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-details a:hover {
|
||||||
|
color: var(--secondary-dark);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -39,12 +39,6 @@
|
|||||||
{{ loading ? 'Inloggen...' : 'Inloggen' }}
|
{{ loading ? 'Inloggen...' : 'Inloggen' }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="login-info">
|
|
||||||
<p><strong>Demo gegevens:</strong></p>
|
|
||||||
<p>E-mail: admin@fietservice.nl</p>
|
|
||||||
<p>Wachtwoord: Admin174</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -114,7 +108,7 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,13 +127,13 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.login-header h1 {
|
.login-header h1 {
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-header p {
|
.login-header p {
|
||||||
color: #666;
|
color: var(--text-gray);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +151,7 @@ export default {
|
|||||||
|
|
||||||
.form-group label {
|
.form-group label {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +165,8 @@ export default {
|
|||||||
|
|
||||||
.form-group input:focus {
|
.form-group input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #3498db;
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(44, 95, 93, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group input:disabled {
|
.form-group input:disabled {
|
||||||
@@ -190,7 +185,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.login-btn {
|
.login-btn {
|
||||||
background: #3498db;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
@@ -198,11 +193,14 @@ export default {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 10px rgba(44, 95, 93, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-btn:hover:not(:disabled) {
|
.login-btn:hover:not(:disabled) {
|
||||||
background: #2980b9;
|
background: var(--primary-dark);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 15px rgba(44, 95, 93, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-btn:disabled {
|
.login-btn:disabled {
|
||||||
@@ -225,7 +223,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.login-info strong {
|
.login-info strong {
|
||||||
color: #2c3e50;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export default {
|
|||||||
|
|
||||||
.back-btn {
|
.back-btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #6c757d;
|
background: var(--secondary-color);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -103,7 +103,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.back-btn:hover {
|
.back-btn:hover {
|
||||||
background: #5a6268;
|
background: var(--secondary-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-state, .empty-state {
|
.loading-state, .empty-state {
|
||||||
@@ -114,7 +114,7 @@ export default {
|
|||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
border: 4px solid #f3f3f3;
|
border: 4px solid #f3f3f3;
|
||||||
border-top: 4px solid #3498db;
|
border-top: 4px solid var(--primary-color);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@@ -146,7 +146,7 @@ export default {
|
|||||||
.price-card:hover {
|
.price-card:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
||||||
border-color: #3498db;
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-header {
|
.price-header {
|
||||||
@@ -165,7 +165,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.price {
|
.price {
|
||||||
background: #3498db;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
@@ -184,7 +184,7 @@ export default {
|
|||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
padding: 3rem 2rem;
|
padding: 3rem 2rem;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
border: 2px dashed #3498db;
|
border: 2px dashed var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-section h2 {
|
.cta-section h2 {
|
||||||
@@ -200,7 +200,7 @@ export default {
|
|||||||
|
|
||||||
.cta-button {
|
.cta-button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #3498db;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -211,7 +211,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cta-button:hover {
|
.cta-button:hover {
|
||||||
background: #2980b9;
|
background: var(--primary-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user