feat: ✨ handle image on general config
This commit is contained in:
parent
159b96b8db
commit
4c45312724
13 changed files with 311 additions and 4 deletions
62
app/Http/Controllers/Bo/GeneralConfigController.php
Normal file
62
app/Http/Controllers/Bo/GeneralConfigController.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\GeneralConfig;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class GeneralConfigController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the general configuration edit form.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(): View
|
||||
{
|
||||
return view('bo.general-config.edit', [
|
||||
'config' => GeneralConfig::current(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload and store the "Nos métiers" page image.
|
||||
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function upload(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate(
|
||||
['image' => 'required|image|mimes:jpeg,png,jpg|max:2048|dimensions:width=742,height=464'],
|
||||
['image.dimensions' => "L'image doit faire exactement 742 × 464 px."]
|
||||
);
|
||||
|
||||
$config = GeneralConfig::current();
|
||||
$ancienne = $config->get('image');
|
||||
|
||||
$photo = $request->file('image');
|
||||
$imageName = 'image-' . now()->format('YmdHis') . '.' . $photo->extension();
|
||||
|
||||
$path = $photo->storeAs(
|
||||
sprintf('modelfiles/general-config/%s/%s', date('Y'), date('m')),
|
||||
$imageName,
|
||||
'public'
|
||||
);
|
||||
|
||||
$config->put('image', $path)->save();
|
||||
|
||||
if ($ancienne && $ancienne !== $path) {
|
||||
Storage::disk('public')->delete($ancienne);
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('bo.general-config.edit')
|
||||
->with('success', 'Image mise à jour.');
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Http\Controllers\Fo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\GeneralConfig;
|
||||
use App\Models\HomeSliders;
|
||||
|
||||
class HomeController extends Controller
|
||||
|
|
@ -16,7 +17,9 @@ class HomeController extends Controller
|
|||
$slides = HomeSliders::where([
|
||||
['status', 1]
|
||||
])->orderBy('order', 'ASC')->get();
|
||||
|
||||
$imageMetiers = GeneralConfig::current()->get('image');
|
||||
|
||||
return view('fo.home', compact('slides'));
|
||||
return view('fo.home', compact('slides', 'imageMetiers'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers\Fo;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\BusinessSector;
|
||||
use App\Models\Employees;
|
||||
use App\Models\GeneralConfig;
|
||||
|
||||
class OurTradeController extends Controller
|
||||
{
|
||||
|
|
@ -24,6 +25,8 @@ class OurTradeController extends Controller
|
|||
$nine_videos = Employees::query()->select('video', 'photo', 'firstname', 'lastname', 'job_title')
|
||||
->whereNotNull('video')->inRandomOrder()->limit(9)->get();
|
||||
|
||||
return view('fo.ourtrade', compact('secteurs_simples', 'secteurs_complexes', 'nine_videos'));
|
||||
$imageMetiers = GeneralConfig::current()->get('image');
|
||||
|
||||
return view('fo.ourtrade', compact('secteurs_simples', 'secteurs_complexes', 'nine_videos', 'imageMetiers'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
59
app/Models/GeneralConfig.php
Normal file
59
app/Models/GeneralConfig.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property array<string, mixed>|null $data
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
*/
|
||||
class GeneralConfig extends Model
|
||||
{
|
||||
|
||||
protected $fillable = ['data'];
|
||||
|
||||
protected $casts = [
|
||||
'data' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the unique configuration row.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function current(): self
|
||||
{
|
||||
return static::query()->firstOrCreate([], ['data' => []]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a setting value from the JSON data.
|
||||
*
|
||||
* @param string $key Setting key.
|
||||
* @param mixed $default Value returned.
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return data_get($this->data, $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a setting value in the JSON data.
|
||||
*
|
||||
* @param string $key Setting key.
|
||||
* @param mixed $value Value to store.
|
||||
* @return self
|
||||
*/
|
||||
public function put(string $key, mixed $value): self
|
||||
{
|
||||
$data = $this->data ?? [];
|
||||
data_set($data, $key, $value);
|
||||
$this->data = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?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('general_configs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->json('data')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('general_configs');
|
||||
}
|
||||
};
|
||||
3
resources/sass/bo/bo.scss
vendored
3
resources/sass/bo/bo.scss
vendored
|
|
@ -11,3 +11,6 @@
|
|||
@import "./base/fonts";
|
||||
@import "./base/classes";
|
||||
@import "./base/common";
|
||||
|
||||
// * PAGE
|
||||
@import "./pages/general-config";
|
||||
42
resources/sass/bo/pages/general-config.scss
vendored
Normal file
42
resources/sass/bo/pages/general-config.scss
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
.gc-image {
|
||||
width: 464px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.gc-preview {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--bs-border-color, #dee2e6);
|
||||
border-radius: var(--bs-border-radius, 0.375rem);
|
||||
margin-bottom: 1.5rem;
|
||||
aspect-ratio: 742 / 464;
|
||||
background-color: var(--bs-light, #f8f9fa);
|
||||
}
|
||||
|
||||
.gc-preview__img {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
inset: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.gc-preview__empty {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
color: var(--bs-secondary-color, #6c757d);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.gc-image__actions {
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#generalConfigAccordion {
|
||||
--bs-accordion-active-bg: #f8f8f8;
|
||||
--bs-accordion-active-color: #212529;
|
||||
--bs-accordion-btn-focus-box-shadow: none;
|
||||
}
|
||||
1
resources/sass/bo/vendor/_bootstrap.scss
vendored
1
resources/sass/bo/vendor/_bootstrap.scss
vendored
|
|
@ -39,6 +39,7 @@
|
|||
@import "bootstrap/scss/breadcrumb";
|
||||
@import "bootstrap/scss/badge";
|
||||
@import "bootstrap/scss/offcanvas";
|
||||
@import "bootstrap/scss/accordion";
|
||||
|
||||
// 6. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss`
|
||||
@import "bootstrap/scss/utilities/api";
|
||||
|
|
|
|||
88
resources/views/bo/general-config/edit.blade.php
Normal file
88
resources/views/bo/general-config/edit.blade.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
@extends('layouts.bo')
|
||||
|
||||
|
||||
@section('content')
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Configuration générale</h5>
|
||||
<a href="{{ route('bo.dashboard') }}" class="btn btn-sm btn-primary">Retour</a>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="accordion" id="generalConfigAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-metiers">
|
||||
<button class="accordion-button" type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapse-metiers"
|
||||
aria-expanded="true"
|
||||
aria-controls="collapse-metiers">
|
||||
Image de la page : <strong> Nos métiers</strong>
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
<div id="collapse-metiers"
|
||||
class="accordion-collapse collapse show"
|
||||
aria-labelledby="heading-metiers"
|
||||
data-bs-parent="#generalConfigAccordion">
|
||||
<div class="accordion-body">
|
||||
<form action="{{ route('bo.general-config.upload') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<div class="gc-image">
|
||||
<div class="gc-preview">
|
||||
<img id="preview-image"
|
||||
src="{{ $config->get('image') ? Storage::url($config->get('image')) : '' }}"
|
||||
alt="Image de la page Nos métiers"
|
||||
class="gc-preview__img {{ $config->get('image') ? '' : 'd-none' }}">
|
||||
|
||||
<span id="preview-empty"
|
||||
class="gc-preview__empty {{ $config->get('image') ? 'd-none' : '' }}">
|
||||
Aucune image
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<label for="image" class="form-label">
|
||||
Photo <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="file" id="image" name="image" accept="image/png, image/jpeg"
|
||||
class="form-control @error('image') is-invalid @enderror" required>
|
||||
<div class="form-text">JPEG ou PNG, 2 Mo max., 742 × 464 px.</div>
|
||||
@error('image')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
|
||||
<div class="gc-image__actions">
|
||||
<button type="submit" class="btn btn-secondary">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const input = document.getElementById('image');
|
||||
const img = document.getElementById('preview-image');
|
||||
const empty = document.getElementById('preview-empty');
|
||||
const original = img.getAttribute('src');
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
const file = input.files[0];
|
||||
const src = file ? URL.createObjectURL(file) : original;
|
||||
|
||||
if (src) img.src = src;
|
||||
|
||||
img.classList.toggle('d-none', !src);
|
||||
empty.classList.toggle('d-none', Boolean(src));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
@ -60,7 +60,11 @@
|
|||
</div>
|
||||
<div class="info">
|
||||
<h3 class="post-chapter">40 salarié(e)s</h3>
|
||||
<img src="{{ Vite::asset('resources/assets/images/quarante-salaries.jpg') }}" alt="40 salarié(e)s">
|
||||
<img src="{{ $imageMetiers
|
||||
? Storage::url($imageMetiers)
|
||||
: Vite::asset('resources/assets/images/quarante-salaries.jpg', 'build')
|
||||
}}" alt="40 salarié(e)s" width="363px" height="227px"
|
||||
>
|
||||
<div><a class="second-btn" href="{{ route('fo.ourtrade') }}">Rencontrez l'équipe Jeusselin</a></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,14 @@
|
|||
<section class="work--head container flex">
|
||||
<h1 class="post-title">Nos métiers</h1>
|
||||
<div class="work--head--detail grid">
|
||||
<div class="detail--img"><img src="{{ Vite::asset('resources/assets/images/quarante-salaries-large.jpg', 'build') }}" alt="L'Equipe Jeusselin"></div>
|
||||
|
||||
|
||||
<div class="detail--img">
|
||||
<img src="{{ $imageMetiers
|
||||
? Storage::url($imageMetiers)
|
||||
: Vite::asset('resources/assets/images/quarante-salaries-large.jpg', 'build') }}"
|
||||
alt="L'Equipe Jeusselin">
|
||||
</div>
|
||||
{{-- <div class="detail--txt">
|
||||
<h2 class="post-chapter">Rencontrez l'équipe en vidéo</h2>
|
||||
<p>Progressively disseminate high-payoff niche markets without enterprise action items. Completely provide access to cutting-edge methods of empowerment before synergistic networks. Conveniently orchestrate user friendly data before.</p>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="{{route('bo.business-sector.index')}}">Secteurs</a>
|
||||
<a class="dropdown-item" href="{{route('bo.employees.index')}}">Employés</a>
|
||||
<a class="dropdown-item" href="{{route('bo.general-config.edit')}}">Configuration générale</a>
|
||||
</div>
|
||||
</li>
|
||||
<a class="nav-link" href="{{route('bo.home-sliders.index')}}">Slider de l'accueil</a>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use App\Http\Controllers\Bo\CotationsController;
|
|||
use App\Http\Controllers\Bo\CustomerController;
|
||||
use App\Http\Controllers\Bo\DashboardController;
|
||||
use App\Http\Controllers\Bo\EmployeesController;
|
||||
use App\Http\Controllers\Bo\GeneralConfigController;
|
||||
use App\Http\Controllers\Bo\HomeSlidersController;
|
||||
use App\Http\Controllers\Bo\ObjectivesController;
|
||||
use App\Http\Controllers\Bo\PublicPdfsController;
|
||||
|
|
@ -82,5 +83,10 @@ Route::prefix('bo')->name('bo.')->middleware('admin-secure')->group(function ()
|
|||
->only(['index', 'edit', 'update'])
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware('htmlsanitize:content');
|
||||
|
||||
// * GENERAL CONFIG
|
||||
Route::get('/config-general', [GeneralConfigController::class, 'edit'])->name('general-config.edit');
|
||||
Route::patch('/config-general', [GeneralConfigController::class, 'upload'])->name('general-config.upload');
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue