Diversen
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Models\ServiceRequest;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Communication;
|
||||
use App\Mail\ServiceRequestNotification;
|
||||
use App\Mail\PickupConfirmation;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -115,11 +116,11 @@ class ServiceRequestController extends Controller
|
||||
$validated = $request->validate([
|
||||
'actual_repair_date' => 'nullable|date|after:today',
|
||||
'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_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);
|
||||
@@ -252,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
|
||||
*/
|
||||
@@ -261,7 +330,7 @@ class ServiceRequestController extends Controller
|
||||
$communications = $serviceRequest->communications()
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
|
||||
return response()->json($communications);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
|
||||
@@ -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 is klaar voor ophalen - ' . $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 [];
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,8 @@ class ServiceRequest extends Model
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'requested_date' => 'date',
|
||||
'requested_time' => 'datetime:H:i',
|
||||
'actual_repair_date' => 'date',
|
||||
'actual_repair_time' => 'datetime:H:i',
|
||||
'requested_date' => 'date:Y-m-d',
|
||||
'actual_repair_date' => 'date:Y-m-d',
|
||||
];
|
||||
|
||||
public function communications()
|
||||
|
||||
+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');
|
||||
}
|
||||
};
|
||||
@@ -38,6 +38,18 @@ class SettingsSeeder extends Seeder
|
||||
'key' => 'working_hours',
|
||||
'value' => 'Maandag-Vrijdag: 08:00 - 18:00, Zaterdag: 09:00 - 16:00',
|
||||
'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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,8 +6,8 @@
|
||||
<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-CTOGI7pY.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-jfsEyp3I.css">
|
||||
<script type="module" crossorigin src="/assets/index-aYPxxDKl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DAYhzhBJ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Uw fiets is klaar voor ophalen</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;
|
||||
}
|
||||
.highlight {
|
||||
background: #8b6f47;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
</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 is klaar!</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="highlight">
|
||||
Vergeet niet uw afhaalbewijs mee te nemen!
|
||||
</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>
|
||||
@@ -48,6 +48,7 @@ Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::apiResource('service-requests', ServiceRequestController::class);
|
||||
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-pickup-confirmation', [ServiceRequestController::class, 'sendPickupConfirmation']);
|
||||
Route::get('service-requests/{serviceRequest}/communications', [ServiceRequestController::class, 'communications']);
|
||||
|
||||
Route::apiResource('blocked-dates', BlockedDateController::class);
|
||||
|
||||
@@ -283,6 +283,19 @@
|
||||
💾
|
||||
</button>
|
||||
</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>
|
||||
|
||||
@@ -321,25 +334,34 @@
|
||||
<strong>Gewenste Datum/Tijd:</strong> {{ formatDateTime(selectedRequest.requested_date, selectedRequest.requested_time) }}
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<strong>Status:</strong>
|
||||
<strong>Status:</strong>
|
||||
<select v-model="selectedRequest.status" @change="updateRequestStatus">
|
||||
<option value="pending">In afwachting</option>
|
||||
<option value="confirmed">Bevestigd</option>
|
||||
<option value="mail_sent">Bevestigd mail verstuurd</option>
|
||||
<option value="completed">Voltooid</option>
|
||||
<option value="cancelled">Geannuleerd</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<strong>Werkelijke Reparatie Datum:</strong>
|
||||
<strong>Werkelijke ophaalDatum:</strong>
|
||||
<input type="date" v-model="selectedRequest.actual_repair_date" @change="updateRequestDate" />
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<strong>Werkelijke Reparatie Tijd:</strong>
|
||||
<strong>Werkelijke ophaaltijd:</strong>
|
||||
<input type="time" v-model="selectedRequest.actual_repair_time" @change="updateRequestTime" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -522,6 +544,7 @@ export default {
|
||||
// Loading states
|
||||
const emailLoading = ref(false)
|
||||
const smsLoading = ref(false)
|
||||
const pickupConfirmationLoading = ref(false)
|
||||
|
||||
const tabs = [
|
||||
{ id: 'requests', label: 'Service Aanvragen' },
|
||||
@@ -542,6 +565,7 @@ export default {
|
||||
const statusMap = {
|
||||
'pending': 'In afwachting',
|
||||
'confirmed': 'Bevestigd',
|
||||
'mail_sent': 'Bevestigd mail verstuurd',
|
||||
'completed': 'Voltooid',
|
||||
'cancelled': 'Geannuleerd'
|
||||
}
|
||||
@@ -658,7 +682,7 @@ export default {
|
||||
const viewRequest = async (request) => {
|
||||
requestModalLoading.value = true
|
||||
showRequestModal.value = true
|
||||
|
||||
|
||||
// Get the latest data from the database to ensure we have the most current information
|
||||
try {
|
||||
const response = await fetch(`/api/service-requests/${request.id}`, {
|
||||
@@ -666,16 +690,29 @@ export default {
|
||||
})
|
||||
if (response.ok) {
|
||||
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 }
|
||||
} else {
|
||||
// Fallback to local data if API call fails
|
||||
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 }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch latest request data:', error)
|
||||
// Fallback to local data if API call fails
|
||||
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 }
|
||||
} finally {
|
||||
requestModalLoading.value = false
|
||||
@@ -764,7 +801,7 @@ export default {
|
||||
|
||||
const submitSms = async () => {
|
||||
if (!smsForm.value.message.trim()) return
|
||||
|
||||
|
||||
smsLoading.value = true
|
||||
try {
|
||||
const response = await fetch(`/api/service-requests/${selectedRequest.value.id}/send-sms`, {
|
||||
@@ -772,7 +809,7 @@ export default {
|
||||
headers: authStore.getAuthHeaders(),
|
||||
body: JSON.stringify({ message: smsForm.value.message })
|
||||
})
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
closeSmsModal()
|
||||
await fetchRequests() // Refresh to show new communication
|
||||
@@ -783,6 +820,51 @@ export default {
|
||||
smsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
editingBlockedDate.value = date
|
||||
@@ -1045,6 +1127,7 @@ export default {
|
||||
communications,
|
||||
emailLoading,
|
||||
smsLoading,
|
||||
pickupConfirmationLoading,
|
||||
minDate,
|
||||
getStatusText,
|
||||
getCommTypeText,
|
||||
@@ -1063,6 +1146,7 @@ export default {
|
||||
viewCommunications,
|
||||
submitEmail,
|
||||
submitSms,
|
||||
sendPickupConfirmation,
|
||||
editBlockedDate,
|
||||
saveBlockedDate,
|
||||
deleteBlockedDate,
|
||||
@@ -1311,6 +1395,12 @@ export default {
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.status-badge.mail_sent {
|
||||
background: #e7d4c5;
|
||||
color: #6d5637;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-badge.completed {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
@@ -1604,16 +1694,24 @@ export default {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.setting-group input {
|
||||
.setting-group input,
|
||||
.setting-group textarea {
|
||||
padding: 0.75rem;
|
||||
border: 2px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.setting-group input:focus {
|
||||
.setting-group input:focus,
|
||||
.setting-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.setting-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
@@ -1801,6 +1899,30 @@ export default {
|
||||
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-group {
|
||||
display: flex;
|
||||
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
|
||||
.back-btn {
|
||||
display: inline-block;
|
||||
background: #6c757d;
|
||||
background: var(--secondary-color);
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
text-decoration: none;
|
||||
@@ -103,7 +103,7 @@ export default {
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background: #5a6268;
|
||||
background: var(--secondary-dark);
|
||||
}
|
||||
|
||||
.loading-state, .empty-state {
|
||||
@@ -114,7 +114,7 @@ export default {
|
||||
|
||||
.loading-spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-top: 4px solid var(--primary-color);
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
@@ -146,7 +146,7 @@ export default {
|
||||
.price-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
||||
border-color: #3498db;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.price-header {
|
||||
@@ -165,7 +165,7 @@ export default {
|
||||
}
|
||||
|
||||
.price {
|
||||
background: #3498db;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 25px;
|
||||
@@ -184,7 +184,7 @@ export default {
|
||||
background: #f8f9fa;
|
||||
padding: 3rem 2rem;
|
||||
border-radius: 15px;
|
||||
border: 2px dashed #3498db;
|
||||
border: 2px dashed var(--primary-color);
|
||||
}
|
||||
|
||||
.cta-section h2 {
|
||||
@@ -200,7 +200,7 @@ export default {
|
||||
|
||||
.cta-button {
|
||||
display: inline-block;
|
||||
background: #3498db;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
text-decoration: none;
|
||||
@@ -211,7 +211,7 @@ export default {
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
background: #2980b9;
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
Reference in New Issue
Block a user