feat(stationmgmt): add CSV import and export buttons for station management

This commit is contained in:
root
2026-05-30 22:48:57 +08:00
parent f58fc6fb77
commit 48654e123d
9 changed files with 152 additions and 5 deletions

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Exports;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
class StationExport implements FromCollection, WithHeadings
{
public function collection()
{
return DB::table('station')
->select('stationid', 'name', 'district', 'lng', 'lat', 'mainriverbasin', 'subriverbasin', 'rainfall', 'waterlevel', 'siren', 'cctv_link')
->orderBy('stationid')
->get();
}
public function headings(): array
{
return [
'Station ID',
'Name',
'District',
'Longitude',
'Latitude',
'Main River Basin',
'Sub River Basin',
'Rainfall',
'Water Level',
'Siren',
'CCTV Link',
];
}
}

View File

@@ -6,6 +6,9 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Rules\Password; use Illuminate\Validation\Rules\Password;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\StationExport;
use App\Imports\StationImport;
class AdminController extends Controller class AdminController extends Controller
{ {
@@ -271,4 +274,33 @@ class AdminController extends Controller
return redirect()->back()->with('success',__('toast.userdeleted')); return redirect()->back()->with('success',__('toast.userdeleted'));
} }
// Function Export Stations to CSV
public function exportStations()
{
return Excel::download(new StationExport, 'stations.csv', \Maatwebsite\Excel\Excel::CSV);
}
// Function Import Stations from CSV
public function importStations(Request $request)
{
$request->validate([
'csv_file' => 'required|file|mimes:csv,txt|max:10240',
]);
try {
Excel::import(new StationImport, $request->file('csv_file'));
return redirect()->route('stationmanagement')->with('success', __('toast.stationsimported'));
} catch (\Maatwebsite\Excel\Validators\ValidationException $e) {
$failures = $e->failures();
$firstError = collect($failures)->first();
$msg = $firstError
? "Row {$firstError->row()}: {$firstError->errors()[0]}"
: __('toast.error');
return redirect()->route('stationmanagement')->with('error', $msg);
} catch (\Exception $e) {
Log::error('Station CSV import failed', ['error' => $e->getMessage()]);
return redirect()->route('stationmanagement')->with('error', __('toast.error'));
}
}
} }

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Imports;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Concerns\ToCollection;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
class StationImport implements ToCollection, WithHeadingRow
{
public function collection(Collection $rows)
{
foreach ($rows as $row) {
$stationid = $row['station_id'] ?? $row['stationid'] ?? null;
if (!$stationid) {
continue;
}
DB::table('station')->updateOrInsert(
['stationid' => $stationid],
[
'name' => $row['name'] ?? '',
'district' => $row['district'] ?? '',
'lng' => is_numeric($row['longitude'] ?? null) ? $row['longitude'] : 0,
'lat' => is_numeric($row['latitude'] ?? null) ? $row['latitude'] : 0,
'mainriverbasin' => $row['main_river_basin'] ?? $row['mainriverbasin'] ?? '',
'subriverbasin' => $row['sub_river_basin'] ?? $row['subriverbasin'] ?? '',
'rainfall' => intval($row['rainfall'] ?? 0),
'waterlevel' => intval($row['water_level'] ?? $row['waterlevel'] ?? 0),
'siren' => intval($row['siren'] ?? 0),
'cctv_link' => $row['cctv_link'] ?? null,
]
);
}
}
}

View File

@@ -94,6 +94,9 @@
'nohistorysiren' => 'Tiada Data Sejarah Siren', 'nohistorysiren' => 'Tiada Data Sejarah Siren',
'nocurrentsiren' => 'Tiada siren aktif sejak 7 hari lalu', 'nocurrentsiren' => 'Tiada siren aktif sejak 7 hari lalu',
'nodataavailable' => 'Tiada Data Tersedia', 'nodataavailable' => 'Tiada Data Tersedia',
'dailyrainfall' => 'Hujan Harian',
'importcsv' => 'Import CSV',
'exportcsv' => 'Eksport CSV',
//Form //Form
'selectstation' => 'Pilih Stesen', 'selectstation' => 'Pilih Stesen',

View File

@@ -8,6 +8,8 @@
'passwordupdated' => 'Kata laluan berjaya dikemaskini', 'passwordupdated' => 'Kata laluan berjaya dikemaskini',
'stationdeleted' => 'Stesen berjaya dipadam', 'stationdeleted' => 'Stesen berjaya dipadam',
'userdeleted' => 'Pengguna berjaya dipadam', 'userdeleted' => 'Pengguna berjaya dipadam',
'linksupdated' => 'Pautan CCTV berjaya dikemaskini',
'stationsimported' => 'Stesen berjaya diimport',
]; ];

View File

@@ -90,7 +90,10 @@
'nohistorywl' => 'No Water Level Notification History Data', 'nohistorywl' => 'No Water Level Notification History Data',
'nohistorysiren' => 'No Siren History Data', 'nohistorysiren' => 'No Siren History Data',
'nocurrentsiren' => 'No siren triggered for the past 7 days', 'nocurrentsiren' => 'No siren triggered for the past 7 days',
'nodataavailable' => 'No Data Avaiable', 'nodataavailable' => 'No Data Avaiable',
'dailyrainfall' => 'Daily Rainfall',
'importcsv' => 'Import CSV',
'exportcsv' => 'Export CSV',
//Form //Form
'selectstation' => 'Select Station', 'selectstation' => 'Select Station',

View File

@@ -8,6 +8,8 @@
'passwordupdated' => 'Password updated succesfully', 'passwordupdated' => 'Password updated succesfully',
'stationdeleted' => 'Station deleted succesfully', 'stationdeleted' => 'Station deleted succesfully',
'userdeleted' => 'User deleted successfully', 'userdeleted' => 'User deleted successfully',
'linksupdated' => 'CCTV link updated successfully',
'stationsimported' => 'Stations imported successfully',
]; ];

View File

@@ -60,8 +60,11 @@
</div> </div>
<div class="button-export d-flex justify-content-around m-3"> <div class="button-export d-flex justify-content-around m-3">
<button type="button" class="btn btn-outline-primary btn-sm ms-auto" data-bs-toggle="modal" <button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal"
data-bs-target="#addModal">@lang('messages.addstation')</button> data-bs-target="#addModal">@lang('messages.addstation')</button>
<button type="button" class="btn btn-outline-success btn-sm" data-bs-toggle="modal"
data-bs-target="#importModal">@lang('messages.importcsv')</button>
<a href="{{ route('stationmanagement.export.csv') }}" class="btn btn-outline-info btn-sm">@lang('messages.exportcsv')</a>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
@@ -343,10 +346,33 @@
</div> </div>
</form> </form>
</div> </div>
</div>
<!-- Import CSV Modal -->
<div class="modal fade" id="importModal" tabindex="-1" aria-labelledby="importModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<form method="POST" action="{{ route('stationmanagement.import.csv') }}" enctype="multipart/form-data">
@csrf
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="importModalLabel">@lang('messages.importcsv')</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label" for="csv_file">CSV File</label>
<input type="file" class="form-control" name="csv_file" id="csv_file" accept=".csv,.txt" required>
<div class="form-text">Columns: Station ID, Name, District, Longitude, Latitude, Main River Basin, Sub River Basin, Rainfall (0/1), Water Level (0/1), Siren (0/1), CCTV Link</div>
</div>
</div>
<div class="modal-footer border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">@lang('messages.cancel')</button>
<button type="submit" class="btn btn-success">@lang('messages.importcsv')</button>
</div>
</form>
</div>
</div> </div>
</div> </div>
</section> </section>
@endsection @endsection

View File

@@ -74,6 +74,13 @@ Route::middleware(['admin'])->group(function () {
Route::post('/stationmanagement/{stationid}/update',[App\Http\Controllers\AdminController::class, 'updateStation'])->name('stationmanagement.update'); Route::post('/stationmanagement/{stationid}/update',[App\Http\Controllers\AdminController::class, 'updateStation'])->name('stationmanagement.update');
Route::delete('/stationmanagement/{stationid}/delete',[App\Http\Controllers\AdminController::class, 'deleteStation'])->name('stationmanagement.delete'); Route::delete('/stationmanagement/{stationid}/delete',[App\Http\Controllers\AdminController::class, 'deleteStation'])->name('stationmanagement.delete');
// CCTV Link Management
Route::post('/cctv/{stationid}/update',[App\Http\Controllers\cctvController::class, 'updateCctvLink'])->name('cctv.update');
// Station CSV Import/Export
Route::get('/stationmanagement/export-csv',[App\Http\Controllers\AdminController::class, 'exportStations'])->name('stationmanagement.export.csv');
Route::post('/stationmanagement/import-csv',[App\Http\Controllers\AdminController::class, 'importStations'])->name('stationmanagement.import.csv');
// User Management // User Management
Route::get('/usermgmt',[App\Http\Controllers\AdminController::class, 'userDisplay'])->name('usermgmt'); Route::get('/usermgmt',[App\Http\Controllers\AdminController::class, 'userDisplay'])->name('usermgmt');
Route::post('/usermgmt/store',[App\Http\Controllers\AdminController::class, 'storeUser'])->name('usermgmt.store'); Route::post('/usermgmt/store',[App\Http\Controllers\AdminController::class, 'storeUser'])->name('usermgmt.store');