Merge branch 'trunk'

This commit is contained in:
Jérémy Munsch 2021-09-14 11:48:44 +02:00
commit bf98cfc545
89 changed files with 3192 additions and 1121 deletions

1
.gitignore vendored
View file

@ -19,3 +19,4 @@ storage/crm_migration.csv
MIG/errors_sql.txt
public/js
public/css
public/assets

View file

@ -95,9 +95,10 @@ class ApiError extends BaseEnumRoutable
'customerExists' => 'L\'utilisateur est déjà enregistré sur ce site',
'customerNotActivated' => 'Le compte n\'est pas activé',
'customerAlreadyActive' => 'Le compte est déjà activé',
'singleOptinCustomerExists' => 'Le signle optin ne peut être ajouté car un compte existe.',
'authFailed' => 'Echec de l\'authentification',
'singleOptinCustomerExists' => 'Le single optin ne peut être ajouté car un compte existe.',
'authFailed' => 'Echec de l\'authentification du compte',
'needAuth' => 'Vous devez être authentifié',
'apiAuthFailed' => 'Echec de l\'authentification client à l\'Api LDC-CRM pour ce service',
'methodNotAllowed' => 'This method is not allowed for this request',
'wrongTimeStamp' => 'Your request is malformed, please check timestamp header and parameter',
'missingParameter' => 'The request is missing some parameters',

View file

@ -0,0 +1,33 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self singleOptin()
* @method static self customer()
* @method static self both()
*
* @package LDCCrmClient
* @subpackage enums
*/
class CustomerExportType extends BaseEnumRoutable
{
public static function values(): array
{
return [
'singleOptin' => 1,
'customer' => 2,
'both' => 3
];
}
public static function labels(): array
{
return [
'singleOptin' => trans('Les single optins uniquement'),
'customer' => trans('Les clients uniquement acceptant de recevoir les newsletters'),
'both' => trans('La totalité des client acceptant de recevoir les newsletters')
];
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Answer;
use Illuminate\Database\Eloquent\Builder;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class AnswerController extends Controller
{
use CrudController {
index as crudIndex;
}
/** @var Log */
public static $modelClass = Answer::class;
public static $layout = 'layouts.app';
public function indexQuery(Builder $query): Builder
{
$parent = request()->question;
if ($parent) {
$query->whereHas('question', function ($q) use ($parent) {
return $q->whereId($parent->id);
});
}
return $query;
}
public function index(CrudIndexRequest $request)
{
/** @var \Illuminate\View\View */
$view = $this->crudIndex($request);
if ($request->ajax()) {
return $view;
}
return view('answer.index', $view->getData());
}
}

View file

@ -3,29 +3,28 @@
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\ManageBrandRequest;
use App\Models\Brand;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class BrandController extends Controller
{
public function index()
{
$brands = Brand::all();
return view('brands.index', compact('brands'));
use CrudController {
index as crudIndex;
}
public function manage(ManageBrandRequest $request, Brand $brand)
{
if ($request->isMethod('post')) {
if (!$brand) {
$brand = new Brand($request->validated());
} else {
$brand->fill($request->validated());
}
$brand->save();
return redirect()->route('brands')->with('success', trans('Modifications enregistrées !'));
}
/** @var Brand */
public static $modelClass = Brand::class;
return view('brands.manage', compact('brand'));
public static $layout = 'layouts.app';
public function index(CrudIndexRequest $request)
{
/** @var \Illuminate\View\View */
$view = $this->crudIndex($request);
if ($request->ajax()) {
return $view;
}
return view('brand.index', $view->getData());
}
}

View file

@ -2,17 +2,108 @@
namespace App\Http\Controllers;
use App\Enums\CustomerExportType;
use App\Http\Requests\CustomerExport;
use App\Models\Brand;
use App\Models\Customer;
use App\Models\CustomerInfo;
use App\Models\CustomerLogin;
use App\Models\SingleOptin;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use League\Csv\Writer;
use Symfony\Component\HttpFoundation\StreamedResponse;
class CustomerController extends Controller
{
public function index()
public function index(Request $request)
{
$brands = Brand::select('id', 'name')->get();
return view('customers.index', compact('brands'));
$modelTable = (new Customer())->getTable();
$modelClass = Customer::class;
$brands = Brand::select('id', 'name')->get();
$columns = ['customer_infos.lastname', 'customer_infos.firstname'];
// Jointure pour pouvoir trier les colonnes
/** @var \Illuminate\Database\Eloquent\Builder */
$query = CustomerInfo::join('customer_logins', function (JoinClause $join) {
$join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id')
->on('customer_logins.customer_id', '=', 'customer_infos.customer_id');
})
->join('customers', function (JoinClause $join) {
$join->on('customers.id', '=', 'customer_infos.customer_id');
})
->join('brands', function (JoinClause $join) {
$join->on('brands.id', '=', 'customer_infos.brand_id');
});
if (request()->brand or Session::has("crud.$modelTable.brand")) {
$brand = $request->brand ?? Session::get("crud.$modelTable.brand");
Session::put("crud.$modelTable.brand", $brand);
$query = $query->where('customer_infos.brand_id', $brand);
}
// Search on crud Label column
if ($request->search or Session::has("crud.$modelTable.search")) {
$search = $request->search ?? Session::get("crud.$modelTable.search");
Session::put("crud.$modelTable.search", $search);
$query->where(function ($q) use ($columns, $search) {
foreach ($columns as $column) {
$q = $q->orWhere($column, 'LIKE', "%$search%");
}
$q = $q->orWhere('customers.email', 'LIKE', "%$search%");
$q = $q->orWhere('customer_logins.last_connection', 'LIKE', "%$search%");
});
}
// Sort columns
if ($request->sort_col) {
Session::put("crud.$modelTable.sort_col", $request->sort_col);
}
if ($request->sort_way) {
Session::put("crud.$modelTable.sort_way", $request->sort_way);
}
if ($request->rst) {
Session::remove("crud.$modelTable.brand");
Session::remove("crud.$modelTable.search");
Session::remove("crud.$modelTable.sort_way");
Session::remove("crud.$modelTable.sort_col");
}
if (
Session::has("crud.$modelTable.sort_col") and
Session::has("crud.$modelTable.sort_way") and
(Schema::hasColumn($modelTable, Session::get("crud.$modelTable.sort_col")) or
in_array(Session::get("crud.$modelTable.sort_col"), [
'brands.name',
'customers.email',
'customer_infos.firstname',
'customer_infos.lastname',
'customer_infos.opt_in',
'customer_logins.last_connection',
'customer_logins.status'
]))
) {
$query = $query->orderBy(
Session::get("crud.$modelTable.sort_col"),
Session::get("crud.$modelTable.sort_way")
);
}
/** @var \Illuminate\Pagination\AbstractPaginator */
$customers = $query->paginate(config('crud.paginate', 50));
// use a crud controller (any) to gather crudModels
$modelsClasses = (new BrandController())->getCrudModels();
return view('customer.index', compact(
'modelsClasses',
'modelClass',
'modelTable',
'customers',
'brands'
));
}
public function show(Customer $customer, Brand $brand)
@ -21,48 +112,14 @@ class CustomerController extends Controller
->where('brand_id', $brand->id)->firstOrFail();
$customerLogin = CustomerLogin::where('customer_id', $customer->id)
->where('brand_id', $brand->id)->first();
return view('customers.show', compact('customerInfos', 'customerLogin'));
}
public function filter($texte, Brand $brand = null)
{
if ($brand) {
$customers = Customer::join('customer_infos', 'customers.id', '=', 'customer_infos.customer_id')
->join('customer_logins', 'customers.id', '=', 'customer_logins.customer_id')->select(
'customer_infos.customer_id',
'customer_infos.firstname',
'customer_infos.lastname',
'customer_infos.opt_in',
'customers.email',
'customer_infos.brand_id',
'customer_logins.last_connection'
)->where([
['lastname', 'like', '%' . $texte . '%'],
['customer_infos.brand_id', $brand->id],
['customer_logins.brand_id', $brand->id]
])->orWhere([
['email', 'like', '%' . $texte . '%'],
['customer_infos.brand_id', $brand->id],
['customer_logins.brand_id', $brand->id]
])->get();
} else {
$customers = CustomerInfo::join('customers', 'customers.id', '=', 'customer_infos.customer_id')
->join('customer_logins', function ($join) {
$join->on('customer_logins.customer_id', '=', 'customer_infos.customer_id');
$join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id');
})->select(
'customer_infos.customer_id',
'customer_infos.firstname',
'customer_infos.lastname',
'customer_infos.opt_in',
'customers.email',
'customer_infos.brand_id',
'customer_logins.last_connection'
)->where('lastname', 'like', '%' . $texte . '%')
->orWhere('email', 'like', '%' . $texte . '%')->get();
}
return response()->json($customers);
// use a crud controller (any) to gather crudModels
$modelsClasses = (new BrandController())->getCrudModels();
return view('customer.show', compact(
'modelsClasses',
'customerInfos',
'customerLogin'
));
}
public function delete(Customer $customer, Brand $brand)
@ -77,6 +134,104 @@ class CustomerController extends Controller
$customer->delete();
}
return redirect()->route('customers')->with('success', "Compte supprimé.");
return redirect()->route('customers.index')->with('success', "Compte supprimé.");
}
public function export()
{
// use a crud controller (any) to gather crudModels
$modelsClasses = (new BrandController())->getCrudModels();
$brands = Brand::all();
return view('customer.export', compact(
'brands',
'modelsClasses'
));
}
public function exportSubmit(CustomerExport $request)
{
$pagination = 2000;
$header = '"lastname";"firstname";"email";"hashcode"';
$w = Writer::createFromString($header . PHP_EOL);
$w->setDelimiter(';');
$w->setEnclosure('"');
$w->setOutputBOM(Writer::BOM_UTF8);
$exportType = CustomerExportType::make(\intval($request->export_type));
//We send the CSV using Symfony StreamedResponse
$response = new StreamedResponse();
$response->headers->set('Content-Encoding', 'none');
$response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
$response->headers->set('Description', 'File Transfer');
$response->headers->set(
'Content-Disposition',
sprintf(
'attachment; filename="Export pour newsletter %s %s.csv"',
Carbon::now()->format('d-m-Y_H-i'),
Str::slug($exportType)
)
);
switch ($exportType) {
case CustomerExportType::both():
$continue = true;
// continue
case CustomerExportType::customer():
$i = 0;
/** @var \Illuminate\Database\Eloquent\Builder $req */
$req = CustomerInfo::where('brand_id', $request->brand);
$total = $req->count();
$pages = $total / $pagination;
do {
/** @var \Illuminate\Pagination\LengthAwarePaginator $items */
$items = $req->with('customer', function ($q) {
$q->select(['id', 'email']);
})->paginate(
$pagination,
['lastname', 'firstname', 'hashcode', 'customer_id'],
'page',
$i
);
$items->map(function (CustomerInfo $customerInfo) use ($w) {
$w->insertOne([
'lastname' => $customerInfo->lastname,
'firstname' => $customerInfo->firstname,
'email' => $customerInfo->customer->email,
'hashcode' => $customerInfo->hashcode
]);
});
} while ($i++ <= $pages);
if (!isset($continue)) {
break;
}
// continue
case CustomerExportType::singleOptin():
SingleOptin::where('brand_id', $request->brand)->get()
->map(function (SingleOptin $singleOptin) use ($w) {
$w->insertOne([
'lastname' => '',
'firstname' => '',
'email' => $singleOptin->email,
'hashcode' => ''
]);
});
break;
}
//we create a callable to output the CSV in chunk
//with Symfony StreamResponse you can flush the body content if necessary
//see Symfony documentation for more information
$flush_threshold = 1000; //the flush value should depend on your CSV size.
$response->setCallback(function () use ($w, $flush_threshold) {
foreach ($w->chunk(1024) as $offset => $chunk) {
echo $chunk;
if ($offset % $flush_threshold === 0) {
flush();
}
}
});
return $response;
}
}

View file

@ -21,7 +21,7 @@ class LoginController extends Controller
$request->remember !== null ? true : false
)
) {
return redirect()->route('customers');
return redirect()->route('customers.index');
} else {
return back()->withInput($request->except("password"))
->withErrors("Les identifiants entrés sont incorrects, veuillez réessayer.");

View file

@ -0,0 +1,125 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\QuestionExport;
use App\Models\Brand;
use App\Models\Question;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
use League\Csv\Writer;
use Symfony\Component\HttpFoundation\StreamedResponse;
class QuestionController extends Controller
{
use CrudController {
index as crudIndex;
}
/** @var Question */
public static $modelClass = Question::class;
public static $layout = 'layouts.app';
public function index(CrudIndexRequest $request)
{
/** @var \Illuminate\View\View */
$view = $this->crudIndex($request);
if ($request->ajax()) {
return $view;
}
return view('question.index', $view->getData());
}
public function indexQuery(Builder $query): Builder
{
if (request()->brand) {
$query = $query->where('brand_id', '=', request()->brand);
}
return $query;
}
public function export()
{
$brands = Brand::all();
return view('question.export', compact('brands'));
}
public function exportSubmit(QuestionExport $request)
{
$results = [];
$brand = Brand::findOrFail($request->brand);
$questions = $brand->questions->load('answers');
foreach ($questions as $question) {
$answersCounts = DB::query()
->selectRaw('count(*) as total, answer_id')
->from('customer_answer')
->whereIn('answer_id', $question->answers->pluck('id'))
->groupBy('answer_id')
->get()->keyBy('answer_id');
$results[$question->id] = [
'question' => $question->question,
'answers' => []
];
foreach ($question->answers as $answer) {
$results[$question->id]['answers'][] =
[
"$answer->answer ",
$answersCounts[$answer->id]->total ?? 0
];
}
}
$pagination = 2000;
$w = Writer::createFromString('');
$w->setDelimiter(';');
$w->setEnclosure('"');
$w->setOutputBOM(Writer::BOM_UTF8);
//We send the CSV using Symfony StreamedResponse
$response = new StreamedResponse();
$response->headers->set('Content-Encoding', 'none');
$response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
$response->headers->set('Description', 'File Transfer');
$response->headers->set(
'Content-Disposition',
sprintf(
'attachment; filename="Export pour newsletter %s %s.csv"',
Carbon::now()->format('d-m-Y_H-i'),
Str::slug($brand->name)
)
);
foreach ($results as $result) {
$w->insertOne([
'question' => $result['question'],
]);
foreach ($result['answers'] as $answer) {
$w->insertOne($answer);
}
$w->insertOne([]);
}
//we create a callable to output the CSV in chunk
//with Symfony StreamResponse you can flush the body content if necessary
//see Symfony documentation for more information
$flush_threshold = 1000; //the flush value should depend on your CSV size.
$response->setCallback(function () use ($w, $flush_threshold) {
foreach ($w->chunk(1024) as $offset => $chunk) {
echo $chunk;
if ($offset % $flush_threshold === 0) {
flush();
}
}
});
return $response;
}
}

View file

@ -2,49 +2,45 @@
namespace App\Http\Controllers;
use App\Http\Requests\ManageServiceRequest;
use App\Models\Brand;
use App\Http\Requests\GenerateApiToken;
use App\Mail\ServiceCredentialRenewed;
use App\Models\Service;
use Illuminate\Support\Facades\Mail;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class ServiceController extends Controller
{
public function index()
{
$services = Service::all();
return view('services.index', compact('services'));
use CrudController {
index as crudIndex;
}
public function manage(ManageServiceRequest $request, Service $service = null)
/** @var Service */
public static $modelClass = Service::class;
public static $layout = 'layouts.app';
public function index(CrudIndexRequest $request)
{
$brands = Brand::all();
$edit = $service ? true : false;
if ($request->isMethod('post')) {
if (!$service) {
$service = new Service($request->validated());
} else {
$service->fill($request->validated());
}
$brand = Brand::findOrFail($request->brand_id);
$service->brand()->associate($brand);
$service->save();
return redirect()->route('services')->with('success', $edit ? 'Modifications enregistrées !' : sprintf(
'Token du service : \'%s\'',
$service->bearer_token
));
/** @var \Illuminate\View\View */
$view = $this->crudIndex($request);
if ($request->ajax()) {
return $view;
}
return view('services.manage', compact('service', 'brands', 'edit'));
return view('service.index', $view->getData());
}
public function generateApiToken(Service $service)
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
public function generateApiToken(GenerateApiToken $request, Service $service)
{
/** @var \App\Models\User $user */
$user = auth()->user();
$bearerToken = $service->generateBearerToken();
$bearerPwd = $service->bearer_pwd;
$service->saveOrFail();
Mail::send(new ServiceCredentialRenewed($service, $user));
return back()
// phpcs:ignore Generic.Files.LineLength.TooLong
->with('success', "Nouveau token d'API de $service->name : '$bearerToken' et mot de passe '$bearerPwd'");
->with('error', "ATTENTION : METTEZ À JOUR LE SITE ! \nNouveau token d'API de $service->name : '$bearerToken' et mot de passe '$bearerPwd'");
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers;
use App\Models\SingleOptin;
use Illuminate\Database\Eloquent\Builder;
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
use Kwaadpepper\CrudPolicies\Traits\CrudController;
class SingleOptinController extends Controller
{
use CrudController {
index as crudIndex;
}
/** @var SingleOptin */
public static $modelClass = SingleOptin::class;
public static $layout = 'layouts.app';
public function index(CrudIndexRequest $request)
{
/** @var \Illuminate\View\View */
$view = $this->crudIndex($request);
if ($request->ajax()) {
return $view;
}
return view('single_optin.index', $view->getData());
}
public function indexQuery(Builder $query): Builder
{
if (request()->brand) {
$query = $query->where('brand_id', request()->brand);
}
return $query;
}
}

View file

@ -1,37 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Resources\StatusResource;
use App\Models\Brand;
use App\Models\SingleOptin;
class SingleOptinsController extends Controller
{
public function index()
{
$brands = Brand::select('id', 'name')->get();
return view('single-optins.index', compact('brands'));
}
public function filter($texte, $brandId = null)
{
if ($brandId !== null) {
$customers = SingleOptin::where([['email', 'like', '%' . $texte . '%'], ['brand_id', $brandId]])->get();
} else {
$customers = SingleOptin::where('email', 'like', '%' . $texte . '%')->get();
}
return response()->json($customers);
}
public function delete($id)
{
// TODO : Gestion avec les stats
$optin = SingleOptin::find($id);
if ($optin) {
$optin->delete();
}
return new StatusResource(['status' => "OK"]);
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Http\Requests;
use App\Enums\CustomerExportType;
use Illuminate\Foundation\Http\FormRequest;
class CustomerExport extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'brand' => 'required|numeric|exists:brands,id',
'export_type' => [
'required',
'numeric',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if (!\in_array($value, CustomerExportType::values())) {
$fail(trans('Le type d\'export invalide'));
}
}
]
];
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class GenerateApiToken extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class QuestionExport extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'brand' => 'required|numeric|exists:brands,id'
];
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace App\Mail;
use App\Models\Service;
use App\Models\User;
use Illuminate\Mail\Mailable;
class ServiceCredentialRenewed extends Mailable
{
/**
* The concerned service
*
* @var \App\Models\Service
*/
public $service;
/**
* The user who did the action
*
* @var \App\Models\User
*/
public $user;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(Service $service, User $user)
{
$this->service = $service;
$this->user = $user;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('mail.service-renewed')
->subject('!!! Warning !!!, New service credential have been generated !')
->text('mail.service-renewed-plain')
->with('appName', config('app.name'))
->with('service', $this->service)
->with('by', sprintf('%s %s (%s)', $this->user->firstname, $this->user->lastname, $this->user->email))
->to(
config('mail.contactsList.serviceweb', 'equipe.web@ldc.fr'),
sprintf('%s service warning', config('app.name'))
);
}
}

View file

@ -4,15 +4,45 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
use Kwaadpepper\CrudPolicies\Enums\CrudType;
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
class Answer extends Model
{
use HasFactory;
use IsCrudModel;
protected $crudLabelColumn = 'answer';
protected $crudValueColumn = 'id';
protected $fillable = [
'answer'
];
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'answer' => [
'label' => Str::ucfirst(trans('models.common.answer')),
'type' => CrudType::string(),
'validate' => [
'min:1',
'max:255'
],
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
]
];
parent::__construct($attributes);
}
/**
* Question BelongsTo relation
*

View file

@ -4,13 +4,22 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
use Kwaadpepper\CrudPolicies\Enums\CrudType;
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
use Rennokki\QueryCache\Traits\QueryCacheable;
class Brand extends Model
{
use IsCrudModel;
use HasFactory;
use QueryCacheable;
protected $crudLabelColumn = 'name';
protected $crudValueColumn = 'id';
protected $cacheFor = 0;
protected $fillable = [
@ -20,6 +29,34 @@ class Brand extends Model
public function __construct(array $attributes = [])
{
$this->cacheFor = config('api.servicesDBCache') * 60;
self::$editableProperties = [
'name' => [
'label' => Str::ucfirst(trans('models.common.name')),
'type' => CrudType::string(),
'validate' => [
'min:5',
'max:255'
],
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'services' => [
'label' => Str::ucfirst(Str::plural(trans('models.classes.services'))),
'type' => CrudType::belongsToMany(),
'belongsToMany' => Service::class,
'belongsToManyLabel' => 'name',
'actions' => [
CrudAction::view(),
CrudAction::create(),
CrudAction::update(),
]
]
];
parent::__construct($attributes);
}

View file

@ -100,6 +100,16 @@ class CustomerInfo extends Model
return $this->belongsTo(Customer::class);
}
/**
* get Login
*
* @return \App\Models\CustomerLogin
*/
public function login()
{
return $this->customer->login($this->brand);
}
/**
* Brand BelongsTo relation
*

View file

@ -5,12 +5,21 @@ namespace App\Models;
use App\Enums\QuestionType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
use Kwaadpepper\CrudPolicies\Enums\CrudType;
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Question extends Model
{
use CastsEnums;
use HasFactory;
use IsCrudModel;
protected $crudLabelColumn = 'question';
protected $crudValueColumn = 'id';
protected $enumCasts = [
'type' => QuestionType::class
@ -21,6 +30,57 @@ class Question extends Model
'type'
];
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'question' => [
'label' => Str::ucfirst(trans('models.common.question')),
'type' => CrudType::string(),
'validate' => [
'min:10',
'max:255'
],
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'type' => [
'label' => Str::ucfirst(trans('models.common.type')),
'type' => CrudType::enum(),
'enum' => QuestionType::class,
'actions' => [
CrudAction::view(),
CrudAction::create(),
CrudAction::update(),
]
],
'brand_id' => [
'label' => Str::ucfirst(trans('models.classes.brand')),
'type' => CrudType::belongsTo(),
'belongsTo' => Brand::class,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::create(),
CrudAction::update(),
]
],
'answers' => [
'label' => Str::ucfirst(Str::plural(trans('models.classes.answers'))),
'type' => CrudType::belongsToMany(),
'belongsToMany' => Answer::class,
'belongsToManyLabel' => 'answer',
'actions' => [
CrudAction::view()
]
]
];
parent::__construct($attributes);
}
/**
* Brand BelongsTo relation
*

View file

@ -8,13 +8,21 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
use Kwaadpepper\CrudPolicies\Enums\CrudType;
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
use Rennokki\QueryCache\Traits\QueryCacheable;
class Service extends Model
{
use IsCrudModel;
use HasFactory;
use QueryCacheable;
protected $crudLabelColumn = 'name';
protected $crudValueColumn = 'id';
protected $cacheFor = 0;
protected $fillable = [
@ -24,6 +32,32 @@ class Service extends Model
public function __construct(array $attributes = [])
{
$this->cacheFor = config('api.servicesDBCache') * 60;
self::$editableProperties = [
'name' => [
'label' => Str::ucfirst(trans('models.common.name')),
'type' => CrudType::string(),
'validate' => [
'min:5',
'max:255'
],
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'brand_id' => [
'label' => Str::ucfirst(trans('models.classes.brand')),
'type' => CrudType::belongsTo(),
'belongsTo' => Brand::class,
'actions' => [
CrudAction::viewAny(),
CrudAction::view()
]
],
];
parent::__construct($attributes);
}

View file

@ -4,15 +4,76 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
use Kwaadpepper\CrudPolicies\Enums\CrudType;
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
class SingleOptin extends Model
{
use HasFactory;
use IsCrudModel;
protected $crudLabelColumn = 'email';
protected $crudValueColumn = 'id';
protected $fillable = [
'email'
];
public function __construct(array $attributes = [])
{
self::$editableProperties = [
'email' => [
'label' => Str::ucfirst(trans('models.common.email')),
'type' => CrudType::email(),
'validate' => [
'required',
'email:rfc,dns',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
$q = static::query();
if (request()->user) {
$q = $q->where('id', '!=', $this->id);
}
if ($q->where('email', $value)->exists()) {
$fail(__(':attribute est déjà utilisé', [
'attribute' => $attribute
]));
}
}
],
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::create(),
CrudAction::update()
]
],
'brand_id' => [
'label' => Str::ucfirst(trans('models.classes.brand')),
'type' => CrudType::belongsTo(),
'belongsTo' => Brand::class,
'actions' => [
CrudAction::viewAny(),
CrudAction::view()
]
],
'created_at' => [
'label' => Str::ucfirst(trans('models.common.optinDate')),
'type' => CrudType::string(),
'readonly' => true,
'actions' => [
CrudAction::viewAny(),
CrudAction::view(),
CrudAction::update()
]
],
];
parent::__construct($attributes);
}
/**
* Brand BelongsTo relation
*

View file

@ -0,0 +1,29 @@
<?php
namespace App\Policies;
use App\Policies\Rules\UserStaticRules as USR;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
class AnswerPolicy extends RootPolicy
{
/**
* Init policies on call
*
* @return void
*/
public function setPolicies()
{
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'answer' => [
'viewAny' => USR::make('everyOne'),
'update' => USR::make('everyOne'),
'create' => USR::make('everyOne'),
'delete' => USR::make('everyOne')
]
];
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Policies;
use App\Policies\Rules\UserStaticRules as USR;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
class BrandPolicy extends RootPolicy
{
/**
* Init policies on call
*
* @return void
*/
public function setPolicies()
{
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'brand' => [
'viewAny' => USR::make('everyOne'),
'update' => USR::make('everyOne'),
'create' => USR::make('everyOne')
]
];
}
}

View file

@ -4,12 +4,33 @@ namespace App\Policies;
use App\Models\Customer;
use App\Models\Service;
use App\Policies\Rules\UserStaticRules as USR;
use Illuminate\Auth\Access\HandlesAuthorization;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
class CustomerPolicy
class CustomerPolicy extends RootPolicy
{
use HandlesAuthorization;
/**
* Init policies on call
*
* @return void
*/
public function setPolicies()
{
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'customer' => [
'viewAny' => USR::make('everyOne'),
'view' => USR::make('everyOne'),
'delete' => USR::make('everyOne')
]
];
}
public function customerBelongsToService(Service $service, Customer $customer)
{
return !\is_null($customer->login($service->brand));

View file

@ -0,0 +1,30 @@
<?php
namespace App\Policies;
use App\Policies\Rules\UserStaticRules as USR;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
class QuestionPolicy extends RootPolicy
{
/**
* Init policies on call
*
* @return void
*/
public function setPolicies()
{
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'question' => [
'viewAny' => USR::make('everyOne'),
'view' => USR::make('everyOne'),
'update' => USR::make('everyOne'),
'create' => USR::make('everyOne'),
'delete' => USR::make('everyOne')
]
];
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace App\Policies\Rules;
use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
/**
* Userlevel Policiy Static Rules
*! Rules has to return boolean value
*/
abstract class UserStaticRules extends StaticRules
{
protected static function everyOne(): bool
{
return true;
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Policies;
use App\Policies\Rules\UserStaticRules as USR;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
class ServicePolicy extends RootPolicy
{
/**
* Init policies on call
*
* @return void
*/
public function setPolicies()
{
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'service' => [
'viewAny' => USR::make('everyOne'),
'view' => USR::make('everyOne'),
'update' => USR::make('everyOne'),
'create' => USR::make('everyOne')
]
];
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Policies;
use App\Policies\Rules\UserStaticRules as USR;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
class SingleOptinPolicy extends RootPolicy
{
/**
* Init policies on call
*
* @return void
*/
public function setPolicies()
{
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'singleoptin' => [
'viewAny' => USR::make('everyOne'),
'delete' => USR::make('everyOne')
]
];
}
}

View file

@ -3,7 +3,11 @@
namespace App\Providers;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -31,5 +35,14 @@ class AppServiceProvider extends ServiceProvider
if (!in_array(config('app.env'), ['local', 'testing'])) {
URL::forceScheme('https');
}
Paginator::useBootstrap();
$appInfos = Cache::remember('composer', 360, function () {
return json_decode(File::get(\app_path('../composer.json')));
});
View::share('name', $appInfos->name);
View::share('version', $appInfos->version);
View::share('license', $appInfos->license);
}
}

View file

@ -2,8 +2,13 @@
namespace App\Providers;
use App\Models\Answer;
use App\Models\Brand;
use App\Models\Customer;
use App\Models\CustomerInfo;
use App\Models\Question;
use App\Models\Service;
use App\Models\SingleOptin;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
@ -36,6 +41,12 @@ class RouteServiceProvider extends ServiceProvider
Route::middleware('web')->namespace($this->namespace)->group(base_path('routes/web.php'));
});
Route::model('brand', Brand::class);
Route::model('service', Service::class);
Route::model('single_optin', SingleOptin::class);
Route::model('question', Question::class);
Route::model('answer', Answer::class);
Route::bind('customer', function ($value) {
if (\ctype_digit($value)) {
$customer = Customer::where('id', '=', $value)->firstOrFail();

@ -1 +1 @@
Subproject commit b208c78b97e68c078345620a5a023186a1753ee8
Subproject commit b29fb6a5bc1a2740c3ce5276ff1d38199eed539c

View file

@ -1,7 +1,7 @@
{
"name": "ldc/ldccrmv2client",
"type": "project",
"version": "2.1.5",
"version": "2.1.7",
"description": "LDC CRM V2",
"authors": [
{
@ -32,11 +32,14 @@
"fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"kwaadpepper/crud-policies": "^0.1.17",
"kwaadpepper/enum": "^2.0",
"laravel-lang/lang": "~7.0",
"laravel/framework": "^8.39",
"laravel/tinker": "^2.5",
"rennokki/laravel-eloquent-query-cache": "^3.0"
"league/csv": "^9.7",
"rennokki/laravel-eloquent-query-cache": "^3.0",
"spatie/laravel-mix-preload": "^1.2"
},
"require-dev": {
"facade/ignition": "^2.5",
@ -81,6 +84,7 @@
"prefer-stable": true,
"scripts": {
"test": [
"echo 'check memcached is running';netcat -z localhost 11211",
"@configclear",
"./test.sh"
],

609
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0df11a64ace2bcbbbc3710af5cc6868f",
"content-hash": "52ddf0e771e6661178474f2a87aeaf7e",
"packages": [
{
"name": "arondeparon/laravel-request-sanitizer",
@ -128,16 +128,16 @@
},
{
"name": "brick/math",
"version": "0.9.2",
"version": "0.9.3",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0"
"reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/dff976c2f3487d42c1db75a3b180e2b9f0e72ce0",
"reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0",
"url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae",
"reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae",
"shasum": ""
},
"require": {
@ -147,7 +147,7 @@
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0",
"vimeo/psalm": "4.3.2"
"vimeo/psalm": "4.9.2"
},
"type": "library",
"autoload": {
@ -172,15 +172,19 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.9.2"
"source": "https://github.com/brick/math/tree/0.9.3"
},
"funding": [
{
"url": "https://github.com/BenMorel",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/brick/math",
"type": "tidelift"
}
],
"time": "2021-01-20T22:51:39+00:00"
"time": "2021-08-15T20:50:18+00:00"
},
{
"name": "cache/adapter-common",
@ -447,16 +451,16 @@
},
{
"name": "composer/package-versions-deprecated",
"version": "1.11.99.2",
"version": "1.11.99.3",
"source": {
"type": "git",
"url": "https://github.com/composer/package-versions-deprecated.git",
"reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c"
"reference": "fff576ac850c045158a250e7e27666e146e78d18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c",
"reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c",
"url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/fff576ac850c045158a250e7e27666e146e78d18",
"reference": "fff576ac850c045158a250e7e27666e146e78d18",
"shasum": ""
},
"require": {
@ -500,7 +504,7 @@
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
"support": {
"issues": "https://github.com/composer/package-versions-deprecated/issues",
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2"
"source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.3"
},
"funding": [
{
@ -516,7 +520,7 @@
"type": "tidelift"
}
],
"time": "2021-05-24T07:46:03+00:00"
"time": "2021-08-17T13:49:14+00:00"
},
{
"name": "doctrine/cache",
@ -1167,6 +1171,124 @@
],
"time": "2020-12-29T14:50:06+00:00"
},
{
"name": "facade/flare-client-php",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/facade/flare-client-php.git",
"reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/47b639dc02bcfdfc4ebb83de703856fa01e35f5f",
"reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f",
"shasum": ""
},
"require": {
"facade/ignition-contracts": "~1.0",
"illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0",
"php": "^7.1|^8.0",
"symfony/http-foundation": "^3.3|^4.1|^5.0",
"symfony/mime": "^3.4|^4.0|^5.1",
"symfony/var-dumper": "^3.4|^4.0|^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"phpunit/phpunit": "^7.5.16",
"spatie/phpunit-snapshot-assertions": "^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Facade\\FlareClient\\": "src"
},
"files": [
"src/helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Send PHP errors to Flare",
"homepage": "https://github.com/facade/flare-client-php",
"keywords": [
"exception",
"facade",
"flare",
"reporting"
],
"support": {
"issues": "https://github.com/facade/flare-client-php/issues",
"source": "https://github.com/facade/flare-client-php/tree/1.8.1"
},
"funding": [
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2021-05-31T19:23:29+00:00"
},
{
"name": "facade/ignition-contracts",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition-contracts.git",
"reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition-contracts/zipball/3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
"reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
"shasum": ""
},
"require": {
"php": "^7.3|^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^v2.15.8",
"phpunit/phpunit": "^9.3.11",
"vimeo/psalm": "^3.17.1"
},
"type": "library",
"autoload": {
"psr-4": {
"Facade\\IgnitionContracts\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://flareapp.io",
"role": "Developer"
}
],
"description": "Solution contracts for Ignition",
"homepage": "https://github.com/facade/ignition-contracts",
"keywords": [
"contracts",
"flare",
"ignition"
],
"support": {
"issues": "https://github.com/facade/ignition-contracts/issues",
"source": "https://github.com/facade/ignition-contracts/tree/1.0.2"
},
"time": "2020-10-16T08:27:54+00:00"
},
{
"name": "fideloper/proxy",
"version": "4.4.1",
@ -1606,6 +1728,70 @@
},
"time": "2021-06-30T20:03:07+00:00"
},
{
"name": "kwaadpepper/crud-policies",
"version": "0.1.17",
"source": {
"type": "git",
"url": "https://github.com/Kwaadpepper/crud-policies.git",
"reference": "0cd52413f9ee2f95b2ebc2d2d4621cfc2a104e47"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Kwaadpepper/crud-policies/zipball/0cd52413f9ee2f95b2ebc2d2d4621cfc2a104e47",
"reference": "0cd52413f9ee2f95b2ebc2d2d4621cfc2a104e47",
"shasum": ""
},
"require": {
"facade/flare-client-php": "^1.8",
"illuminate/support": "~7|~8",
"kwaadpepper/enum": "^2.0",
"laravel/framework": "~7|~8"
},
"require-dev": {
"orchestra/testbench": "^6.13",
"phpunit/phpunit": "~9.0",
"slevomat/coding-standard": "^7.0",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Kwaadpepper\\CrudPolicies\\Providers\\CrudPoliciesServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Kwaadpepper\\CrudPolicies\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jérémy Munsch",
"email": "github@jeremydev.ovh",
"homepage": "https://jeremydev.ovh"
}
],
"description": "Makes CRUD easy, make policies easy and everyone happy",
"homepage": "https://github.com/kwaadpepper/crud-policies",
"keywords": [
"Scafolding",
"crud",
"laravel",
"policies"
],
"support": {
"issues": "https://github.com/Kwaadpepper/crud-policies/issues",
"source": "https://github.com/Kwaadpepper/crud-policies/tree/0.1.17"
},
"time": "2021-09-10T14:23:18+00:00"
},
{
"name": "kwaadpepper/enum",
"version": "2.0.0",
@ -1708,16 +1894,16 @@
},
{
"name": "laravel/framework",
"version": "v8.53.1",
"version": "v8.56.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "5df423c2790f32b26baee90518bb659572aa404f"
"reference": "d700f336dc672dcbfe46db05d4e5fe9347b3a5b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/5df423c2790f32b26baee90518bb659572aa404f",
"reference": "5df423c2790f32b26baee90518bb659572aa404f",
"url": "https://api.github.com/repos/laravel/framework/zipball/d700f336dc672dcbfe46db05d4e5fe9347b3a5b6",
"reference": "d700f336dc672dcbfe46db05d4e5fe9347b3a5b6",
"shasum": ""
},
"require": {
@ -1790,7 +1976,7 @@
"illuminate/view": "self.version"
},
"require-dev": {
"aws/aws-sdk-php": "^3.186.4",
"aws/aws-sdk-php": "^3.189.0",
"doctrine/dbal": "^2.6|^3.0",
"filp/whoops": "^2.8",
"guzzlehttp/guzzle": "^6.5.5|^7.0.1",
@ -1803,7 +1989,7 @@
"symfony/cache": "^5.1.4"
},
"suggest": {
"aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.186.4).",
"aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.189.0).",
"brianium/paratest": "Required to run tests in parallel (^6.0).",
"doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).",
"ext-ftp": "Required to use the Flysystem FTP driver.",
@ -1872,7 +2058,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2021-08-05T14:04:08+00:00"
"time": "2021-08-24T13:46:30+00:00"
},
{
"name": "laravel/tinker",
@ -2044,17 +2230,101 @@
"time": "2021-07-17T17:13:23+00:00"
},
{
"name": "league/flysystem",
"version": "1.1.4",
"name": "league/csv",
"version": "9.7.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32"
"url": "https://github.com/thephpleague/csv.git",
"reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3ad69181b8afed2c9edf7be5a2918144ff4ea32",
"reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/0ec57e8264ec92565974ead0d1724cf1026e10c1",
"reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"php": "^7.3 || ^8.0"
},
"require-dev": {
"ext-curl": "*",
"ext-dom": "*",
"friendsofphp/php-cs-fixer": "^2.16",
"phpstan/phpstan": "^0.12.0",
"phpstan/phpstan-phpunit": "^0.12.0",
"phpstan/phpstan-strict-rules": "^0.12.0",
"phpunit/phpunit": "^9.5"
},
"suggest": {
"ext-dom": "Required to use the XMLConverter and or the HTMLConverter classes",
"ext-iconv": "Needed to ease transcoding CSV using iconv stream filters"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "9.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Csv\\": "src"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://github.com/nyamsprod/",
"role": "Developer"
}
],
"description": "CSV data manipulation made easy in PHP",
"homepage": "http://csv.thephpleague.com",
"keywords": [
"convert",
"csv",
"export",
"filter",
"import",
"read",
"transform",
"write"
],
"support": {
"docs": "https://csv.thephpleague.com",
"issues": "https://github.com/thephpleague/csv/issues",
"rss": "https://github.com/thephpleague/csv/releases.atom",
"source": "https://github.com/thephpleague/csv"
},
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2021-04-17T16:32:08+00:00"
},
{
"name": "league/flysystem",
"version": "1.1.5",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "18634df356bfd4119fe3d6156bdb990c414c14ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/18634df356bfd4119fe3d6156bdb990c414c14ea",
"reference": "18634df356bfd4119fe3d6156bdb990c414c14ea",
"shasum": ""
},
"require": {
@ -2127,7 +2397,7 @@
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/1.1.4"
"source": "https://github.com/thephpleague/flysystem/tree/1.1.5"
},
"funding": [
{
@ -2135,7 +2405,7 @@
"type": "other"
}
],
"time": "2021-06-23T21:56:05+00:00"
"time": "2021-08-17T13:49:42+00:00"
},
{
"name": "league/mime-type-detection",
@ -2291,16 +2561,16 @@
},
{
"name": "nesbot/carbon",
"version": "2.51.1",
"version": "2.52.0",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922"
"reference": "369c0e2737c56a0f39c946dd261855255a6fccbe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922",
"reference": "8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/369c0e2737c56a0f39c946dd261855255a6fccbe",
"reference": "369c0e2737c56a0f39c946dd261855255a6fccbe",
"shasum": ""
},
"require": {
@ -2381,7 +2651,7 @@
"type": "tidelift"
}
],
"time": "2021-07-28T13:16:28+00:00"
"time": "2021-08-14T19:10:52+00:00"
},
{
"name": "nikic/php-parser",
@ -3181,16 +3451,16 @@
},
{
"name": "ramsey/uuid",
"version": "4.1.1",
"version": "4.2.1",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "cd4032040a750077205918c86049aa0f43d22947"
"reference": "fe665a03df4f056aa65af552a96e1976df8c8dae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/cd4032040a750077205918c86049aa0f43d22947",
"reference": "cd4032040a750077205918c86049aa0f43d22947",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/fe665a03df4f056aa65af552a96e1976df8c8dae",
"reference": "fe665a03df4f056aa65af552a96e1976df8c8dae",
"shasum": ""
},
"require": {
@ -3204,26 +3474,26 @@
"rhumsaa/uuid": "self.version"
},
"require-dev": {
"codeception/aspect-mock": "^3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7.0",
"captainhook/captainhook": "^5.10",
"captainhook/plugin-composer": "^5.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"doctrine/annotations": "^1.8",
"goaop/framework": "^2",
"ergebnis/composer-normalize": "^2.15",
"mockery/mockery": "^1.3",
"moontoast/math": "^1.1",
"paragonie/random-lib": "^2",
"php-mock/php-mock": "^2.2",
"php-mock/php-mock-mockery": "^1.3",
"php-mock/php-mock-phpunit": "^2.5",
"php-parallel-lint/php-parallel-lint": "^1.1",
"phpbench/phpbench": "^0.17.1",
"phpbench/phpbench": "^1.0",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-mockery": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpunit/phpunit": "^8.5",
"psy/psysh": "^0.10.0",
"slevomat/coding-standard": "^6.0",
"phpunit/phpunit": "^8.5 || ^9",
"slevomat/coding-standard": "^7.0",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "3.9.4"
"vimeo/psalm": "^4.9"
},
"suggest": {
"ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
@ -3236,7 +3506,10 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.x-dev"
"dev-main": "4.x-dev"
},
"captainhook": {
"force-install": true
}
},
"autoload": {
@ -3252,7 +3525,6 @@
"MIT"
],
"description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
"homepage": "https://github.com/ramsey/uuid",
"keywords": [
"guid",
"identifier",
@ -3260,16 +3532,19 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"rss": "https://github.com/ramsey/uuid/releases.atom",
"source": "https://github.com/ramsey/uuid"
"source": "https://github.com/ramsey/uuid/tree/4.2.1"
},
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/uuid",
"type": "tidelift"
}
],
"time": "2020-08-18T17:17:46+00:00"
"time": "2021-08-11T01:06:55+00:00"
},
{
"name": "rennokki/laravel-eloquent-query-cache",
@ -3336,6 +3611,74 @@
],
"time": "2021-07-17T17:42:40+00:00"
},
{
"name": "spatie/laravel-mix-preload",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-mix-preload.git",
"reference": "b89dc366fa8a714ecb3d6af836db3c038795a3b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-mix-preload/zipball/b89dc366fa8a714ecb3d6af836db3c038795a3b6",
"reference": "b89dc366fa8a714ecb3d6af836db3c038795a3b6",
"shasum": ""
},
"require": {
"illuminate/support": "^6.0|^7.0|^8.0",
"php": "^7.2.5|^8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.0|^9.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\MixPreload\\MixPreloadServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Spatie\\MixPreload\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sebastian De Deyne",
"email": "sebastian@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Add preload and prefetch links based your Mix manifest",
"homepage": "https://github.com/spatie/laravel-mix-preload",
"keywords": [
"laravel-mix-preload",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/laravel-mix-preload/issues",
"source": "https://github.com/spatie/laravel-mix-preload/tree/1.2.2"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
},
{
"url": "https://www.patreon.com/spatie",
"type": "patreon"
}
],
"time": "2021-01-04T08:01:50+00:00"
},
{
"name": "swiftmailer/swiftmailer",
"version": "v6.2.7",
@ -6072,83 +6415,18 @@
],
"time": "2020-11-10T18:47:58+00:00"
},
{
"name": "facade/flare-client-php",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/facade/flare-client-php.git",
"reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/47b639dc02bcfdfc4ebb83de703856fa01e35f5f",
"reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f",
"shasum": ""
},
"require": {
"facade/ignition-contracts": "~1.0",
"illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0",
"php": "^7.1|^8.0",
"symfony/http-foundation": "^3.3|^4.1|^5.0",
"symfony/mime": "^3.4|^4.0|^5.1",
"symfony/var-dumper": "^3.4|^4.0|^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"phpunit/phpunit": "^7.5.16",
"spatie/phpunit-snapshot-assertions": "^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Facade\\FlareClient\\": "src"
},
"files": [
"src/helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Send PHP errors to Flare",
"homepage": "https://github.com/facade/flare-client-php",
"keywords": [
"exception",
"facade",
"flare",
"reporting"
],
"support": {
"issues": "https://github.com/facade/flare-client-php/issues",
"source": "https://github.com/facade/flare-client-php/tree/1.8.1"
},
"funding": [
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2021-05-31T19:23:29+00:00"
},
{
"name": "facade/ignition",
"version": "2.11.2",
"version": "2.12.0",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition.git",
"reference": "7c4e7a7da184cd00c7ce6eacc590200bb9672de7"
"reference": "74dcc32a2895a126d1e5f2cd3bbab499cac66db1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition/zipball/7c4e7a7da184cd00c7ce6eacc590200bb9672de7",
"reference": "7c4e7a7da184cd00c7ce6eacc590200bb9672de7",
"url": "https://api.github.com/repos/facade/ignition/zipball/74dcc32a2895a126d1e5f2cd3bbab499cac66db1",
"reference": "74dcc32a2895a126d1e5f2cd3bbab499cac66db1",
"shasum": ""
},
"require": {
@ -6211,60 +6489,7 @@
"issues": "https://github.com/facade/ignition/issues",
"source": "https://github.com/facade/ignition"
},
"time": "2021-07-20T14:01:22+00:00"
},
{
"name": "facade/ignition-contracts",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition-contracts.git",
"reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition-contracts/zipball/3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
"reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
"shasum": ""
},
"require": {
"php": "^7.3|^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^v2.15.8",
"phpunit/phpunit": "^9.3.11",
"vimeo/psalm": "^3.17.1"
},
"type": "library",
"autoload": {
"psr-4": {
"Facade\\IgnitionContracts\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://flareapp.io",
"role": "Developer"
}
],
"description": "Solution contracts for Ignition",
"homepage": "https://github.com/facade/ignition-contracts",
"keywords": [
"contracts",
"flare",
"ignition"
],
"support": {
"issues": "https://github.com/facade/ignition-contracts/issues",
"source": "https://github.com/facade/ignition-contracts/tree/1.0.2"
},
"time": "2020-10-16T08:27:54+00:00"
"time": "2021-08-24T09:53:54+00:00"
},
{
"name": "fakerphp/faker",
@ -6585,16 +6810,16 @@
},
{
"name": "nunomaduro/collision",
"version": "v5.6.0",
"version": "v5.8.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "0122ac6b03c75279ef78d1c0ad49725dfc52a8d2"
"reference": "0c3c393462eada1233513664e2d22bb9f69ca393"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/0122ac6b03c75279ef78d1c0ad49725dfc52a8d2",
"reference": "0122ac6b03c75279ef78d1c0ad49725dfc52a8d2",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/0c3c393462eada1233513664e2d22bb9f69ca393",
"reference": "0c3c393462eada1233513664e2d22bb9f69ca393",
"shasum": ""
},
"require": {
@ -6606,7 +6831,7 @@
"require-dev": {
"brianium/paratest": "^6.1",
"fideloper/proxy": "^4.4.1",
"friendsofphp/php-cs-fixer": "^2.17.3",
"friendsofphp/php-cs-fixer": "^3.0",
"fruitcake/laravel-cors": "^2.0.3",
"laravel/framework": "^8.0 || ^9.0",
"nunomaduro/larastan": "^0.6.2",
@ -6669,7 +6894,7 @@
"type": "patreon"
}
],
"time": "2021-07-26T20:39:06+00:00"
"time": "2021-08-13T14:23:01+00:00"
},
{
"name": "phar-io/manifest",
@ -8567,16 +8792,16 @@
},
{
"name": "slevomat/coding-standard",
"version": "7.0.13",
"version": "7.0.14",
"source": {
"type": "git",
"url": "https://github.com/slevomat/coding-standard.git",
"reference": "122a9bf9a4a2195f74100f47dfb8375982f43cc9"
"reference": "15b2b4630c148775debea8e412bc7e128d9868a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slevomat/coding-standard/zipball/122a9bf9a4a2195f74100f47dfb8375982f43cc9",
"reference": "122a9bf9a4a2195f74100f47dfb8375982f43cc9",
"url": "https://api.github.com/repos/slevomat/coding-standard/zipball/15b2b4630c148775debea8e412bc7e128d9868a3",
"reference": "15b2b4630c148775debea8e412bc7e128d9868a3",
"shasum": ""
},
"require": {
@ -8587,12 +8812,12 @@
},
"require-dev": {
"phing/phing": "2.16.4",
"php-parallel-lint/php-parallel-lint": "1.3.0",
"phpstan/phpstan": "0.12.93",
"php-parallel-lint/php-parallel-lint": "1.3.1",
"phpstan/phpstan": "0.12.96",
"phpstan/phpstan-deprecation-rules": "0.12.6",
"phpstan/phpstan-phpunit": "0.12.21",
"phpstan/phpstan-strict-rules": "0.12.10",
"phpunit/phpunit": "7.5.20|8.5.5|9.5.7"
"phpstan/phpstan-phpunit": "0.12.22",
"phpstan/phpstan-strict-rules": "0.12.11",
"phpunit/phpunit": "7.5.20|8.5.5|9.5.8"
},
"type": "phpcodesniffer-standard",
"extra": {
@ -8612,7 +8837,7 @@
"description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.",
"support": {
"issues": "https://github.com/slevomat/coding-standard/issues",
"source": "https://github.com/slevomat/coding-standard/tree/7.0.13"
"source": "https://github.com/slevomat/coding-standard/tree/7.0.14"
},
"funding": [
{
@ -8624,7 +8849,7 @@
"type": "tidelift"
}
],
"time": "2021-07-29T14:30:22+00:00"
"time": "2021-08-26T12:17:56+00:00"
},
{
"name": "squizlabs/php_codesniffer",

View file

@ -227,7 +227,11 @@ return [
'View' => Illuminate\Support\Facades\View::class,
'Carbon' => Illuminate\Support\Carbon::class,
'ArcadiaMailType' => App\Enums\ArcadiaMailType::class,
'ArcadiaOrderType' => App\Enums\ArcadiaOrderType::class
'ArcadiaOrderType' => App\Enums\ArcadiaOrderType::class,
'CustomerExportType' => App\Enums\CustomerExportType::class,
'Brand' => App\Models\Brand::class,
'Customer' => App\Models\Customer::class,
'Answer' => App\Models\Answer::class,
],
];

View file

@ -2,28 +2,27 @@
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js"
"production": "mix --production"
},
"devDependencies": {
"axios": "^0.19",
"bootstrap": "^4.5.0",
"cross-env": "^7.0",
"jquery": "^3.2",
"laravel-mix": "^5.0.1",
"lodash": "^4.17.19",
"popper.js": "^1.12",
"resolve-url-loader": "^3.1.0",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.12"
},
"dependencies": {
"axios": "^0.21",
"clean-webpack-plugin": "^4.0.0-alpha.0",
"laravel-mix": "^6.0.6",
"laravel-mix-polyfill": "^3.0.0",
"moment": "^2.29.1",
"sass": "^1.28.0",
"vue": "^2.6.12"
"postcss": "^8.1.14",
"postcss-discard-comments": "^4.0.2",
"resolve-url-loader": "^3.1.3",
"sass": "^1.32.11",
"sass-loader": "^11.0.1",
"swiper": "^6.6.1",
"vue": "^2.6.12",
"vue-loader": "^15.9.6",
"vue-template-compiler": "^2.6.12"
}
}

View file

@ -1,18 +0,0 @@
/*!
* Vue.js v2.6.12
* (c) 2014-2020 Evan You
* Released under the MIT License.
*/
/**
* @license
* Lodash <https://lodash.com/>
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
//! moment.js
//! moment.js locale configuration

View file

@ -1,4 +1,4 @@
{
"/js/app.js": "/js/app.js",
"/css/app.css": "/css/app.css"
"/js/preload-back.js": "/js/preload-back.js",
"/css/preload-back.css": "/css/preload-back.css"
}

View file

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -1,101 +0,0 @@
<template>
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Rechercher un opt-in</h1>
<div class="spinner-border text-dark float-right" role="status" v-show="processing">
<span class="sr-only">Chargement...</span>
</div>
</div>
<div class="form-group form-row">
<label class="col-form-label col text-center">Marque :</label>
<select v-model="selectedBrand" class="custom-select col">
<option value="0" selected>Toutes les marques</option>
<option v-for="item in brands" :key="item.id" :value="item.id">{{ item.name }}</option>
</select>
<label class="col-form-label col text-center">Votre recherche :</label>
<input v-model="search" type="text" class="form-control col" placeholder="Entrer votre recherche ici" v-on:keyup.enter="enterSearch">
</div>
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">Email</th>
<th scope="col">Marque</th>
<th scope="col">Date opt-in</th>
<th scope="col"></th>
</tr>
</thead>
<tbody id="emails">
<tr v-for="item in datas" :key="item.id">
<td>{{ item.email }}</td>
<td>{{ brands.find(element => element.id === item.brand_id).name }}</td>
<td>{{ item.created_at | formatDate }}</td>
<td><a class="btn btn-sm btn-danger" @click="deleteOptin(item.id)">Supprimer</a></td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
mounted() {
console.log('SearchOptin Component mounted.');
this.brands = JSON.parse(this.dbbrands);
},
props: [
'dbbrands',
],
data() {
return {
brands: [],
datas: [],
processing: false,
search: '',
selectedBrand: "0",
}
},
methods: {
enterSearch() {
if (this.search.length < 1)
return false;
this.processing = true;
let chaine = this.search;
if (this.selectedBrand !== "0")
chaine += '/' + this.selectedBrand;
axios.get('/single-optins/filter/' + chaine)
.then(
(response) => {
this.datas = response.data;
this.processing = false;
}
)
.catch(function (error) {
console.log(error);
this.processing = false;
});
},
deleteOptin(id) {
let r = confirm("Veuillez confirmer cette action en cliquant sur OK.");
if (r === true) {
axios.post('/single-optins/' + id + '/delete')
.then(
(response) => {
if (response.data.status === 'OK'){
this.datas = [];
this.search = '';
this.selectedBrand = "0";
this.processing = false;
alert("Suppression terminée.");
}
}
)
.catch(function (error) {
console.log(error);
this.processing = false;
alert("Une erreur est survenue.");
});
}
}
},
}
</script>

View file

@ -1,87 +0,0 @@
<template>
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Rechercher un client</h1>
<div class="spinner-border text-dark float-right" role="status" v-show="processing">
<span class="sr-only">Chargement...</span>
</div>
</div>
<div class="form-group form-row">
<label class="col-form-label col text-center">Marque :</label>
<select v-model="selectedBrand" class="custom-select col">
<option value="0" selected>Toutes les marques</option>
<option v-for="item in brands" :key="item.id" :value="item.id">{{ item.name }}</option>
</select>
<label class="col-form-label col text-center">Votre recherche :</label>
<input v-model="search" type="text" class="form-control col" placeholder="Entrer votre recherche ici" v-on:keyup.enter="enterSearch">
</div>
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">CRM ID</th>
<th scope="col">Email</th>
<th scope="col">Nom</th>
<th scope="col">Prénom</th>
<th scope="col">Opt-In</th>
<th scope="col">Marque</th>
<th scope="col">Dernière connexion</th>
<th scope="col">Voir</th>
</tr>
</thead>
<tbody id="emails">
<tr v-for="item in datas" :key="item.id">
<td>{{ item.customer_id }}</td>
<td>{{ item.email }}</td>
<td>{{ item.lastname }}</td>
<td>{{ item.firstname }}</td>
<td>{{ item.opt_in === 1 ? "Oui" : "Non" }}</td>
<td>{{ brands.find(element => element.id === item.brand_id).name }}</td>
<td>{{ item.last_connection | formatDate }}</td>
<td><a :href="'/customer/' + item.customer_id + '/' + item.brand_id" class="btn btn-sm btn-primary">Détail</a></td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
mounted() {
console.log('SearchUser Component mounted.');
this.brands = JSON.parse(this.dbbrands);
},
props: [
'dbbrands',
],
data() {
return {
brands: [],
datas: [],
processing: false,
search: '',
selectedBrand: "0",
}
},
methods: {
enterSearch() {
if (this.search.length < 1)
return false;
this.processing = true;
let chaine = this.search;
if (this.selectedBrand !== "0")
chaine += '/' + this.selectedBrand;
axios.get('/customers/filter/' + chaine)
.then(
(response) => {
this.datas = response.data;
this.processing = false;
}
)
.catch(function (error) {
console.log(error);
this.processing = false;
});
},
},
}
</script>

17
resources/js/app.js vendored
View file

@ -1,17 +0,0 @@
require('./bootstrap');
import moment from 'moment';
window.Vue = require('vue');
Vue.filter('formatDate', function(value) {
if (value) {
return moment(String(value)).format('DD/MM/YYYY H:mm')
}
});
Vue.component('search-user', require('./Components/SearchUser.vue').default);
Vue.component('search-optin', require('./Components/SearchOptin.vue').default);
const app = new Vue({
el: '#app',
});

5
resources/js/back.js vendored Normal file
View file

@ -0,0 +1,5 @@
require('./bootstrap');
require('./pages/customer.delete')
require('./pages/service.index')
require('./pages/single_optin.index')

34
resources/js/pages/customer.delete.js vendored Normal file
View file

@ -0,0 +1,34 @@
let customerDelete = document.getElementById('customerDelete');
if (customerDelete) {
customerDelete.addEventListener('submit', (e) => {
let self = e.target
e.preventDefault()
Swal.fire({
title: 'Supprimer ce client pour cette marque ?',
text: 'Attention cette action est irréversible, si le client n\'appartient plus à aucune marque il sera complètement retiré des bases',
icon: 'warning',
confirmButtonText: 'Je comprends le danger et suis conscient de ce que je fait',
showCancelButton: true,
cancelButtonText: 'Annuler',
confirmButtonColor: '#dc3545'
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({
title: 'Attention au conséquences',
html: 'La supression de ce compte dans la crm n\'entrainera pas ça supression dans MESBR ni dans le front cache DB (pour les sites WPCRM). <br><h3><b>Une fois le compte supprimé je ne pourrai plus récupérer le hashcode nécessaire pour effectuer ces opérations.</b></h3>',
icon: 'info',
confirmButtonText: 'Je comprends le danger et suis conscient que celà aura des conséquences',
showCancelButton: true,
cancelButtonText: 'Annuler',
confirmButtonColor: '#dc3545'
}).then((result) => {
if (result.isConfirmed) {
self.submit()
}
})
}
})
})
}

36
resources/js/pages/service.index.js vendored Normal file
View file

@ -0,0 +1,36 @@
let genForms = document.getElementsByClassName('genToken');
if (genForms.length) {
for (let form of genForms) {
form.addEventListener('submit', (e) => {
let self = e.target
e.preventDefault()
Swal.fire({
title: 'Regénérer un token ?',
text: 'Attention la regeneration empêchera le service de fonctionner tant que son token n\'aura pas été mis à jour',
icon: 'warning',
confirmButtonText: 'Je comprends le danger et suis conscient de ce que je fait',
showCancelButton: true,
cancelButtonText: 'Annuler',
confirmButtonColor: '#dc3545'
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({
title: 'Vous allez régénérer un token de service en production',
html: '<h3><b>Je suis conscient que celà va avoir des implications et qu\'il me faudra mettre à jour le bearer token du service concerné dans son envirronement actuel</b></h3>',
icon: 'info',
confirmButtonText: 'Oui, feu patate !!',
showCancelButton: true,
cancelButtonText: 'Annuler',
confirmButtonColor: '#dc3545'
}).then((result) => {
if (result.isConfirmed) {
self.submit()
}
})
}
})
})
}
}

View file

@ -0,0 +1,10 @@
let brandSelect = document.getElementById('brandSelect');
if (brandSelect) {
brandSelect.addEventListener('change', function (e) {
if (brandSelect.value !== '0') {
const url = new URL(location.href);
url.searchParams.set('brand', brandSelect.value);
location.assign(url.search);
}
});
}

View file

@ -0,0 +1,27 @@
<?php
return [
'classes' => [
'services' => 'Services',
'brands' => 'Brands',
'brand' => 'Brand',
'customers' => 'Customers',
'questions' => 'Questions',
'answers' => 'Answers',
'singleoptins' => 'Single Opt-ins'
],
'common' => [
'title' => 'Title',
'name' => 'Name',
'email' => 'Email',
'content' => 'Content',
'visibility' => 'Visibility',
'published' => 'Published',
'group' => 'Group',
'default' => 'Default',
'optinDate' => 'Date opt-in',
'question' => 'Question',
'answer' => 'Answer',
'type' => 'Type'
]
];

View file

@ -0,0 +1,27 @@
<?php
return [
'classes' => [
'services' => 'Services',
'brands' => 'Marques',
'brand' => 'Marque',
'questions' => 'Questions',
'answers' => 'Réponses',
'customers' => 'Clients',
'singleoptins' => 'Opt-ins uniques'
],
'common' => [
'title' => 'Titre',
'name' => 'Nom',
'email' => 'E-mail',
'content' => 'Contenu',
'visibility' => 'Visibilitée',
'published' => 'Publié',
'group' => 'Groupe',
'default' => 'Par défaut',
'optinDate' => 'Date opt-in',
'question' => 'Question',
'type' => 'Type',
'answer' => 'Réponse'
]
];

View file

@ -1,103 +0,0 @@
body {
font-size: .875rem;
}
.feather {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
/*
* Sidebar
*/
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100; /* Behind the navbar */
padding: 48px 0 0; /* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
@media (max-width: 767.98px) {
.sidebar {
top: 5rem;
}
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
}
@supports ((position: -webkit-sticky) or (position: sticky)) {
.sidebar-sticky {
position: -webkit-sticky;
position: sticky;
}
}
.sidebar .nav-link {
font-weight: 500;
color: #333;
}
.sidebar .nav-link .feather {
margin-right: 4px;
color: #999;
}
.sidebar .nav-link.active {
color: #007bff;
}
.sidebar .nav-link:hover .feather,
.sidebar .nav-link.active .feather {
color: inherit;
}
.sidebar-heading {
font-size: .75rem;
text-transform: uppercase;
}
/*
* Navbar
*/
.navbar-brand {
padding-top: .75rem;
padding-bottom: .75rem;
font-size: 1rem;
background-color: rgba(0, 0, 0, .25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
}
.navbar .navbar-toggler {
top: .25rem;
right: 1rem;
}
.navbar .form-control {
padding: .75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
color: #fff;
background-color: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .1);
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}

17
resources/sass/_layout.scss vendored Normal file
View file

@ -0,0 +1,17 @@
.bodyFull {
margin: 0 !important;
padding: 0 !important;
min-height: calc(100vh - #{$navBarHeight} - #{$footerHeight});
}
#app {
& > .navbar {
height: $navBarHeight;
}
#footer {
height: $footerHeight;
padding: 0 !important;
color: $body-bg;
background-color: $footerBgColor;
}
}

View file

@ -22,3 +22,7 @@ $theme-colors: (
"primary": #3176C4,
"secondary": #E7374B
);
$navBarHeight: 4rem;
$footerHeight: 3rem;
$footerBgColor: #161616;

View file

@ -1,10 +0,0 @@
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');
// Variables
@import 'variables';
// Bootstrap
@import '~bootstrap/scss/bootstrap';
@import "dashboard";

5
resources/sass/back.scss vendored Normal file
View file

@ -0,0 +1,5 @@
// Variables
@import 'variables';
@import 'layout';
@import "pages/login";

54
resources/sass/pages/login.scss vendored Normal file
View file

@ -0,0 +1,54 @@
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
div.login {
height: 100%;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}

View file

@ -0,0 +1,119 @@
@extends($viewLayout)
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.viewAny')))
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), trans(Str::plural(class_basename($modelClass)))))
@section('metaIndex', 'noindex,nofollow')
@section('content')
<div class="card">
@include('crud-policies::crud.modules.flashMessage')
<div class="card-header">
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
</div>
<div class="card-body">
<div class="row justify-content-center">
<div class="col">
{{ $models->links() }}
</div>
<div class="col">
<form action="{{ url()->current() }}">
<div class="input-group mb-3">
<input type="search" name="search" class="form-control" value="{{ request()->search }}"/>
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}"/>
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}"/>
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset') }}</a>
<button type="submit" class="btn btn-primary">
{{ __('crud-policies::crud.search') }}
</button>
</div>
</form>
</div>
</div>
<div class="row justify-content-center">
<h2>{{ request()->question->question }}</h2>
</div>
<table class="table text-center">
<thead class="thead-dark">
@include('crud-policies::crud.modules.sorter')
</thead>
<tbody>
@if(count($models))
{{-- INIT QUERIES for OPTIMIZING --}}
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if($prop['type']->equals(CrudType::order()))
@php ${"$modelTable$fieldName"} = $modelClass::max($fieldName); @endphp
@endif
@if($prop['type']->equals(CrudType::belongsTo()))
@php try { $models->load(Str::singular((new $prop['belongsTo']())->getTable())); } catch(\Exception $e) {} @endphp
@endif
@if($prop['type']->equals(CrudType::belongsToMany()))
@php $models->load("$fieldName"); @endphp
@endif
@endforeach
{{-- DISPLAY MODELS --}}
@foreach($models as $model)
<tr>
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if(in_array(CrudAction::viewAny(), $prop['actions']))
@switch($prop['type'])
@case(CrudType::belongsToMany() and $prop['belongsToMany'] === \App\Models\Project::class)
<td>
@foreach($model->{$fieldName} as $pSite)
<a href="{{ CrudController::getRoutePrefixed($pSite->getTable().'.show', $pSite) }}">{{ $pSite->{$prop['belongsToManyLabel']} }}</a>
@endforeach
</td>
@break
@case(CrudType::password())
{{-- Ignore --}}
@break
@default
<td>@include('crud-policies::crud.printfield', [
'action' => CrudAction::viewAny(),
'field' => $model->{$fieldName},
'props' => $prop,
"$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null
])</td>
@endswitch
@endif
@endforeach
{{-- MODEL ACTIONS --}}
<td>
<div class="btn-group" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
@can('view', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}" class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
@endcan
@can('update', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model) }}" class="btn btn-sm btn-info">{{ __('crud-policies::crud.edit') }}</a>
@endcan
@can('delete', $model)
<form action="{{ CrudController::getRoutePrefixed("$modelTable.destroy", $model) }}" method="POST" onsubmit="__CRUD.confirmDelete(event)">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger">{{ __('crud-policies::crud.delete') }}</button>
</form>
@endcan
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="{{ count($modelClass::getEditableProperties()) }}">
{{ __('crud-policies::crud.nothingtoshow') }}
</td>
</tr>
@endif
</tbody>
</table>
{{ $models->links() }}
</div>
<div class="row justify-content-center mt-5 mb-5">
<div class="col-1">{{ $models->links() }}</div>
</div>
</div>
@include('crud-policies::crud.modules.scriptConstants')
@endsection

View file

@ -0,0 +1,158 @@
@extends($viewLayout)
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.viewAny')))
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), trans(Str::plural(class_basename($modelClass)))))
@section('metaIndex', 'noindex,nofollow')
@section('content')
<div class="card">
@include('crud-policies::crud.modules.flashMessage')
<div class="card-header">
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
</div>
<div class="card-body">
<div class="row justify-content-end">
<div class="col-6">
<form action="{{ url()->current() }}">
<div class="input-group mb-3">
<input type="search" name="search" class="form-control" value="{{ request()->search }}" placeholder="Recherche.."/>
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}"/>
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}"/>
@if(request()->brand)
<input type="hidden" name="brand" class="d-none" value="{{ request()->brand }}"/>
@endif
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset') }}</a>
<button type="submit" class="btn btn-primary">
{{ __('crud-policies::crud.search') }}
</button>
</div>
</form>
</div>
</div>
<table class="table text-center">
<thead class="thead-dark">
<tr>
@foreach ($modelClass::getEditableProperties() as $colname => $prop)
@if(in_array(CrudAction::viewAny(), $prop['actions']))
@switch($prop['type'])
@case(CrudType::password())
{{-- Ignore --}}
@break
@default
<th scope="col">
{{ $prop['label'] }}
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'desc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead asc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'asc']) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'asc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead desc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'desc']) }}">&nbsp;&uarr;&nbsp;</a>
@endif
</th>
@endswitch
@endif
@endforeach
<th scope="col">
{{ __('Nombre de comptes')}}
</th>
<th scope="col">
{{ __('Nombre d\'optins')}}
</th>
<th scope="col">
{{ __('Date d\'ajout en base')}}
</th>
<th scope="col">
@if(session("crud.$modelTable.sort_way") and session("crud.$modelTable.sort_col"))
<a href="{{ request()->fullUrlWithQuery(['rst' => 'true']) }}" class="btn btn-small btn-danger">&#9003;</a>
@endif
</th>
</tr>
</thead>
<tbody>
@if(count($models))
{{-- INIT QUERIES for OPTIMIZING --}}
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if($prop['type']->equals(CrudType::order()))
@php ${"$modelTable$fieldName"} = $modelClass::max($fieldName); @endphp
@endif
@if($prop['type']->equals(CrudType::belongsTo()))
@php try { $models->load(Str::singular((new $prop['belongsTo']())->getTable())); } catch(\Exception $e) {} @endphp
@endif
@if($prop['type']->equals(CrudType::belongsToMany()))
@php $models->load("$fieldName"); @endphp
@endif
@endforeach
{{-- DISPLAY MODELS --}}
@foreach($models as $model)
<tr>
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if(in_array(CrudAction::viewAny(), $prop['actions']))
@switch($prop['type'])
@case(CrudType::belongsToMany() and $prop['belongsToMany'] === \App\Models\Project::class)
<td>
@foreach($model->{$fieldName} as $pSite)
<a href="{{ CrudController::getRoutePrefixed($pSite->getTable().'.show', $pSite) }}">{{ $pSite->{$prop['belongsToManyLabel']} }}</a>
@endforeach
</td>
@break
@case(CrudType::password())
{{-- Ignore --}}
@break
@default
<td>@include('crud-policies::crud.printfield', [
'action' => CrudAction::viewAny(),
'field' => $model->{$fieldName},
'props' => $prop,
"$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null
])</td>
@endswitch
@endif
@endforeach
{{-- INFO COLS --}}
<td>{{ $model->getAccountsAttribute() }}</td>
<td>{{ $model->getTotalOptinsAttribute() }}</td>
<td>{{ $model->created_at->format('m/d/Y à H:i') }}</td>
{{-- MODEL ACTIONS --}}
<td>
<div class="btn-group" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
@can('view', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}" class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
@endcan
@can('update', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model) }}" class="btn btn-sm btn-info">{{ __('crud-policies::crud.edit') }}</a>
@endcan
@can('delete', $model)
<form action="{{ CrudController::getRoutePrefixed("$modelTable.destroy", $model) }}" method="POST" onsubmit="__CRUD.confirmDelete(event)">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger">{{ __('crud-policies::crud.delete') }}</button>
</form>
@endcan
@can('viewAny', \App\Models\Log::class)
<a href="{{ CrudController::getRoutePrefixed("sites.logs.index", $model) }}" class="btn btn-sm btn-dark">{{ __('Journaux') }}</a>
@endcan
@can('viewAny', \App\Models\File::class)
<a href="{{ CrudController::getRoutePrefixed("sites.files.index", $model) }}" class="btn btn-sm btn-dark">{{ __('Fichiers') }}</a>
@endcan
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="{{ count($modelClass::getEditableProperties()) }}">
{{ __('crud-policies::crud.nothingtoshow') }}
</td>
</tr>
@endif
</tbody>
</table>
{{ $models->links() }}
</div>
</div>
@include('crud-policies::crud.modules.scriptConstants')
@endsection

View file

@ -1,28 +0,0 @@
@extends('layout.app')
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h2>{{ __('Gestion des marques') }} <small class="text-muted">{{ __('Liste') }}</small></h2>
<a href="{{ route('manage-brand') }}" class="btn btn-sm btn-primary float-right">{{ __('Ajouter') }}</a>
</div>
<table class="table">
<thead>
<th>{{ __('Nom de la marque') }}</th>
<th>{{ __('Nombre de comptes') }}</th>
<th>{{ __('Nombre d\'optins') }}</th>
<th>{{ __('Date d\'ajout en base') }}</th>
<th></th>
</thead>
<tbody>
@foreach ($brands as $brand)
<tr>
<td>{{ $brand->name }}</td>
<td>{{ $brand->accounts }}</td>
<td>{{ $brand->total_optins }}</td>
<td>{{ Carbon::parse($brand->created_at)->format('d/m/Y à H:i') }}</td>
<td><a href="{{ route('manage-brand', $brand) }}" class="btn btn-sm btn-success">{{ __('Modifier') }}</a></td>
</tr>
@endforeach
</tbody>
</table>
@endsection

View file

@ -1,30 +0,0 @@
@extends('layout.app')
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h2>{{ __('Gestion des marques') }} <small class="text-muted">{{ __('Gestion') }}</small></h2>
@if ($errors->any())
<ul class="text-danger text-center">
@foreach(session('errors')->all(':message') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
@if (session('success'))
<span class="text-success text-center">{!! session('success') !!}</span>
@endif
<a href="{{ route('brands') }}" class="btn btn-sm btn-primary float-right">{{ __('Retour') }}</a>
</div>
<div class="form-group">
<form action="{{ route('manage-brand', $brand) }}" method="POST">
@csrf
<div class="form-group form-row">
<label class="col-form-label col">{{ __('Nom de la marque') }}</label>
<input type="text" name="name" class="form-control col" value="{{ $brand->name ?? null }}">
</div>
<div class="text-center">
<input type="submit" class="btn btn-success" value="{{ __('Enregistrer') }}">
</div>
</form>
</div>
@endsection

View file

@ -0,0 +1,68 @@
@extends('layouts.app')
@section('title', sprintf('%s - %s', __('Newsletter Contacts'), __('Export')))
@section('description', sprintf('%s - %s', __('Newsletter Contacts'), __('Export')))
@section('metaIndex', 'noindex,nofollow')
@section('content')
<div class="card">
@include('crud-policies::crud.modules.flashMessage')
<div class="card-header">
<nav aria-label="breadcrumb" class="d-inline">
<ol class="breadcrumb d-inline-flex">
<li class="breadcrumb-item">
<a href="{{ route('customers.index') }}">{{ __('models.classes.customers') }}</a>
</li>
<li class="breadcrumb-item active" aria-current="page">{{__('Exporter un liste de client pour une newsletter') }}</li>
</ol>
</nav>
</div>
<div class="card-body">
<form class="mt-5 mb-5" action="{{ route('customers.exportSubmit') }}" method="POST">
@csrf
<div class="row justify-content-center">
<div class="col-lg-6 col-md-10 col-12 mb-5">
<h1>{{ __('Exporter un liste de client pour une newsletter') }}</h1>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-6 col-md-10 col-12">
<fieldset>
<legend>{{ __('La marque concernée') }}</legend>
<div class="input-group mb-3">
<label class="input-group-text" for="exportBrand">{{ __('Les client de la marque') }}</label>
<select class="form-select" id="exportBrand" name="brand" required>
<option selected value="">{{ __('Choissez une marque') }}</option>
@foreach($brands as $i => $brand)
<option value="{{ $brand->id }}">{{ $brand->name }}</option>
@endforeach
</select>
</div>
</fieldset>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-6 col-md-10 col-12">
<fieldset>
<legend>{{ __('Type d\'export') }}</legend>
@foreach(CustomerExportType::toArray() as $i => $exportType)
<div class="form-check">
<input class="form-check-input" type="radio" name="export_type" id="exportType{{ $i }}" value="{{ $exportType->value }}" required>
<label class="form-check-label" for="exportType{{ $i }}">
{{ $exportType->label }}
</label>
</div>
@endforeach
</fieldset>
</div>
</div>
<div class="row justify-content-center mt-4">
<div class="col-lg-6 col-md-10 col-12">
<button type="submit" class="btn btn-primary">{{ __('Exporter') }}</button>
</div>
</div>
</form>
</div>
</div>
@include('crud-policies::crud.modules.scriptConstants')
@endsection

View file

@ -0,0 +1,73 @@
<th scope="col">
@php $colname = 'customers.email' @endphp
{{ __('Email')}}
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'desc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead asc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'asc', 'rst' => null]) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'asc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead desc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'desc', 'rst' => null]) }}">&nbsp;&uarr;&nbsp;</a>
@endif
</th>
<th scope="col">
@php $colname = 'customer_infos.lastname' @endphp
{{ __('Nom')}}
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'desc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead asc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'asc', 'rst' => null]) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'asc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead desc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'desc', 'rst' => null]) }}">&nbsp;&uarr;&nbsp;</a>
@endif
</th>
<th scope="col">
@php $colname = 'customer_infos.firstname' @endphp
{{ __('Prénom')}}
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'desc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead asc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'asc', 'rst' => null]) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'asc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead desc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'desc', 'rst' => null]) }}">&nbsp;&uarr;&nbsp;</a>
@endif
</th>
<th scope="col">
@php $colname = 'customer_infos.opt_in' @endphp
{{ __('Opt-In')}}
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'desc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead asc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'asc', 'rst' => null]) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'asc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead desc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'desc', 'rst' => null]) }}">&nbsp;&uarr;&nbsp;</a>
@endif
</th>
<th scope="col">
@php $colname = 'brands.name' @endphp
{{ __('Marque')}}
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'desc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead asc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'asc', 'rst' => null]) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'asc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead desc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'desc', 'rst' => null]) }}">&nbsp;&uarr;&nbsp;</a>
@endif
</th>
<th scope="col">
@php $colname = 'customer_logins.status' @endphp
{{ __('Activé ?')}}
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'desc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead asc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'asc', 'rst' => null]) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'asc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead desc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'desc', 'rst' => null]) }}">&nbsp;&uarr;&nbsp;</a>
@endif
</th>
<th scope="col">
@php $colname = 'customer_logins.last_connection' @endphp
{{ __('Dernière connexion')}}
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'desc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead asc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'asc', 'rst' => null]) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'asc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead desc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'desc', 'rst' => null]) }}">&nbsp;&uarr;&nbsp;</a>
@endif
</th>
<th scope="col">
{{ __('Voir')}}
</th>

View file

@ -0,0 +1,108 @@
@extends('layouts.app')
@section('title', sprintf('%s - %s', Str::ucfirst(Str::plural(trans('models.classes.customers'))), __('crud-policies::crud.viewAny')))
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), Str::ucfirst(Str::plural(trans('models.classes.customers')))))
@section('metaIndex', 'noindex,nofollow')
@section('content')
<div class="card">
@include('crud-policies::crud.modules.flashMessage')
<div class="card-header">
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
</div>
<div class="card-body">
<div class="row justify-content-center">
<div class="col">
<form action="{{ url()->current() }}">
<div class="input-group mb-3">
<label class="input-group-text" for="brandSelect">{{ __('Filter par Marque') }}</label>
<select class="form-select" id="brandSelect">
<option value="0" @if(!request()->brand) selected @endif>{{ __('Sélectionnez une marque..') }}</option>
@foreach(Brand::all() as $brand)
<option value="{{ $brand->id }}" @if(request()->brand == "$brand->id" or Session::get("crud.$modelTable.brand") == $brand->id) selected @endif>{{ $brand->name }}</option>
@endforeach
</select>
<label class="input-group-text bg-warning" for="searchField">🔍🠚</label>
<input type="search" id="searchField" name="search" class="form-control" value="{{ request()->search ?? Session::get("crud.$modelTable.search") }}" placeholder="Recherche.."/>
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}"/>
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}"/>
@if(request()->brand)
<input type="hidden" name="brand" class="d-none" value="{{ request()->brand }}"/>
@endif
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset') }}</a>
<button type="submit" class="btn btn-primary">
{{ __('crud-policies::crud.search') }}
</button>
</div>
</form>
</div>
</div>
{{ $customers->appends(Request::except('rst'))->links() }}
<table class="table text-center">
<thead class="thead-dark">
<tr>
@include('customer.index-tableHeaders')
<th scope="col">
@if(session("crud.$modelTable.sort_way") and session("crud.$modelTable.sort_col"))
<a href="{{ request()->fullUrlWithQuery(['rst' => 'true', 'sort_col' => null, 'sort_way' => null]) }}" class="btn btn-small btn-danger">&#9003;</a>
@endif
</th>
</tr>
</thead>
<tbody>
@if(count($customers))
{{-- DISPLAY MODELS --}}
@foreach($customers as $customerInfo)
<tr>
<td>
{{ $customerInfo->customer->email }}
</td>
<td>
{{ $customerInfo->firstname }}
</td>
<td>
{{ $customerInfo->lastname }}
</td>
<td>
{{ $customerInfo->opt_in ? 'Oui' :'Non' }}
</td>
<td>
{{ $customerInfo->brand->name }}
</td>
<td>
{{ $customerInfo->status ? 'Oui' :'Non' }}
</td>
<td>
{{ $customerInfo->last_connection }}
</td>
{{-- MODEL ACTIONS --}}
<td>
<div class="btn-group" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
@can('view', $customerInfo->customer)
<a href="{{ CrudController::getRoutePrefixed("customers.show", [
'customer' => $customerInfo->customer_id,
'brand' => $customerInfo->brand_id
]) }}" class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
@endcan
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="5">
{{ __('crud-policies::crud.nothingtoshow') }}
</td>
</tr>
@endif
</tbody>
</table>
{{ $customers->appends(Request::except('rst'))->links() }}
</div>
</div>
@include('crud-policies::crud.modules.scriptConstants')
@endsection

View file

@ -0,0 +1,113 @@
@extends('layouts.app')
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.viewAny')))
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), trans(Str::plural(class_basename($modelClass)))))
@section('metaIndex', 'noindex,nofollow')
@section('content')
<div class="card">
@include('crud-policies::crud.modules.flashMessage')
<div class="card-header">
<nav aria-label="breadcrumb" class="d-inline">
<ol class="breadcrumb d-inline-flex">
<li class="breadcrumb-item">
<a href="{{ route('customers.index') }}">{{ __('models.classes.customers') }}</a>
</li>
<li class="breadcrumb-item active" aria-current="page">{{__('Liste') }}</li>
</ol>
</nav>
</div>
<div class="card-body">
<div class="row justify-content-center">
<div class="col">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">{{ __('Informations du client') }} {{ $customerInfos->firstname }} {{ $customerInfos->lastname }} - {{ Str::singular(__('models.classes.brands')) }} {{ $customerInfos->brand->name }}</h1>
<a href="{{ route('customers.index') }}" class="btn btn-sm btn-primary float-right">Retour</a>
</div>
</div>
</div>
<table class="table table-sm table-bordered table-striped">
<tbody>
<tr>
<td class="w-50"><b>{{ __('Email') }}</b></td>
<td class="w-50">{{ $customerInfos->customer->email }}</td>
</tr>
<tr>
<td><b>Prénom</b></td>
<td>{{ $customerInfos->firstname }}</td>
</tr>
<tr>
<td><b>Nom</b></td>
<td>{{ $customerInfos->lastname }}</td>
</tr>
<tr>
<td><b>Date de naissance</b></td>
<td>{{ \Carbon\Carbon::parse($customerInfos->birth_date)->format('d/m/Y') }}</td>
</tr>
<tr>
<td><b>Téléphone</b></td>
<td>{{ $customerInfos->phone }}</td>
</tr>
<tr>
<td><b>Adresse</b></td>
<td>{{ $customerInfos->address }}</td>
</tr>
<tr>
<td><b>Adresse complémentaire</b></td>
<td>{{ $customerInfos->address2 }}</td>
</tr>
<tr>
<td><b>Pays</b></td>
<td>{{ $customerInfos->country }}</td>
</tr>
<tr>
<td><b>Ville</b></td>
<td>{{ $customerInfos->city }}</td>
</tr>
<tr>
<td><b>Code postal</b></td>
<td>{{ $customerInfos->postcode }}</td>
</tr>
<tr>
<td><b>Abonné à la newsletter</b></td>
<td>{{ $customerInfos->opt_in === 1 ? "Oui" : "Non" }}</td>
</tr>
<tr>
<td><b>Date de la dernière modification de l'abonnement</b></td>
@if($customerInfos->customer->optInHistory()->orderBy('created_at', 'DESC')->exists())
<td>{{ \Carbon\Carbon::parse($customerInfos->customer->optInHistory()->orderBy('created_at', 'DESC')->first()->created_at)->format('d/m/Y à H:i') }}</td>
@else
<td></td>
@endif
</tr>
<tr>
<td><b>Dernière connexion</b></td>
<td>{{ \Carbon\Carbon::parse($customerLogin->last_connection)->format('d/m/Y à H:i') }}</td>
</tr>
<tr>
<td><b>Status du compte</b></td>
<td>{{ $customerLogin->status === 1 ? "Activé" : "Non activé" }}</td>
</tr>
<tr>
<td><b>Hashcode</b></td>
<td>{{ $customerInfos->hashcode }}</td>
</tr>
<tr>
<td><b>Date de création</b></td>
<td>{{ \Carbon\Carbon::parse($customerInfos->created_at)->format('d/m/Y à H:i') }}</td>
</tr>
<tr>
<td><b>Date de modification</b></td>
<td>{{ \Carbon\Carbon::parse($customerInfos->updated_at)->format('d/m/Y à H:i') }}</td>
</tr>
</tbody>
</table>
<form id="customerDelete" action="{{ route('customers.delete', ['customer' => $customerInfos->customer_id, 'brand' => $customerInfos->brand_id]) }}" method="POST">
@csrf
@method('DELETE')
<input type="submit" value="{{ __('Supprimer') }}" class="btn btn-sm btn-danger">
</form>
</div>
</div>
@include('crud-policies::crud.modules.scriptConstants')
@endsection

View file

@ -1,23 +0,0 @@
@extends('layout.app')
@section('content')
@if (session()->has('success'))
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3">
<div class="alert alert-success">
<p>{{ session()->get('success') }}</p>
</div>
</div>
@endif
@if ($errors->any())
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3">
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
</div>
@endif
<search-user dbbrands='@json($brands)'></search-user>
@endsection

View file

@ -1,98 +0,0 @@
@extends('layout.app')
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Informations du client {{ $customerInfos->firstname }} {{ $customerInfos->lastname }} - Marque {{ $customerInfos->brand->name }}</h1>
<a href="{{ route('customers') }}" class="btn btn-sm btn-primary float-right">Retour</a>
</div>
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<table class="table table-sm table-bordered table-striped">
<tbody>
<tr>
<td class="w-50"><b>Email</b></td>
<td class="w-50">{{ $customerInfos->customer->email }}</td>
</tr>
<tr>
<td><b>Prénom</b></td>
<td>{{ $customerInfos->firstname }}</td>
</tr>
<tr>
<td><b>Nom</b></td>
<td>{{ $customerInfos->lastname }}</td>
</tr>
<tr>
<td><b>Date de naissance</b></td>
<td>{{ \Carbon\Carbon::parse($customerInfos->birth_date)->format('d/m/Y') }}</td>
</tr>
<tr>
<td><b>Téléphone</b></td>
<td>{{ $customerInfos->phone }}</td>
</tr>
<tr>
<td><b>Adresse</b></td>
<td>{{ $customerInfos->address }}</td>
</tr>
<tr>
<td><b>Adresse complémentaire</b></td>
<td>{{ $customerInfos->address2 }}</td>
</tr>
<tr>
<td><b>Pays</b></td>
<td>{{ $customerInfos->country }}</td>
</tr>
<tr>
<td><b>Ville</b></td>
<td>{{ $customerInfos->city }}</td>
</tr>
<tr>
<td><b>Code postal</b></td>
<td>{{ $customerInfos->postcode }}</td>
</tr>
<tr>
<td><b>Abonné à la newsletter</b></td>
<td>{{ $customerInfos->opt_in === 1 ? "Oui" : "Non" }}</td>
</tr>
<tr>
<td><b>Date de la dernière modification de l'abonnement</b></td>
@if($customerInfos->customer->optInHistory()->orderBy('created_at', 'DESC')->exists())
<td>{{ \Carbon\Carbon::parse($customerInfos->customer->optInHistory()->orderBy('created_at', 'DESC')->first()->created_at)->format('d/m/Y à H:i') }}</td>
@else
<td></td>
@endif
</tr>
<tr>
<td><b>Dernière connexion</b></td>
<td>{{ \Carbon\Carbon::parse($customerLogin->last_connection)->format('d/m/Y à H:i') }}</td>
</tr>
<tr>
<td><b>Status du compte</b></td>
<td>{{ $customerLogin->status === 1 ? "Activé" : "Non activé" }}</td>
</tr>
<tr>
<td><b>Hashcode</b></td>
<td>{{ $customerInfos->hashcode }}</td>
</tr>
<tr>
<td><b>Date de création</b></td>
<td>{{ \Carbon\Carbon::parse($customerInfos->created_at)->format('d/m/Y à H:i') }}</td>
</tr>
<tr>
<td><b>Date de modification</b></td>
<td>{{ \Carbon\Carbon::parse($customerInfos->updated_at)->format('d/m/Y à H:i') }}</td>
</tr>
</tbody>
</table>
<form action="{{ route('customers.delete', ['customer' => $customerInfos->customer_id, 'brand' => $customerInfos->brand->id]) }}" method="POST" onsubmit="return confirm('{{ __('Êtes-vous sûr de vouloir supprimer cet utilisateur ?') }}');">
@csrf
@method('DELETE')
<input type="submit" value="{{ __('Supprimer') }}" class="btn btn-sm btn-danger">
</form>
@endsection

View file

@ -1,68 +0,0 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Tableau de bord</title>
<script src="https://cdn.ckeditor.com/ckeditor5/23.0.0/classic/ckeditor.js"></script>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<div id="app">
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">{{ config('app.name') }}</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div class="container-fluid">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>CLIENTS</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item"><a class="nav-link" href="{{route('customers')}}">Rechercher un client</a></li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>OPT-INS UNIQUES</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item"><a class="nav-link" href="{{route('single-optins')}}">Rechercher un opt-in</a></li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>MARQUES</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item"><a class="nav-link" href="{{route('brands')}}">Gestion des marques</a></li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>SERVICES</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item"><a class="nav-link" href="{{route('services')}}">Gestion des services</a></li>
</ul>
<ul class="nav flex-column mb-2" style="position: absolute; bottom: 5px;">
<li class="nav-item"><a class="nav-link" href="{{route('logout')}}">Se déconnecter</a></li>
</ul>
</div>
</nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
@yield('content')
</main>
</div>
</div>
</div>
<script src="/js/app.js"></script>
@yield('scripts')
</body>
</html>

View file

@ -0,0 +1,35 @@
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
@include('layouts.backend.head')
@stack('styles')
@crudPoliciesCSS()
</head>
<body>
<div id="app">
@include('layouts.backend.navbar')
<div class="container-fluid">
<div class="row">
@if(isset($modelsClasses))
@include('layouts.backend.sidenav')
<main role="main" class="col-md-9 mt-5 mb-5 ml-sm-auto col-lg-10 px-md-4 bodyFull">
@else
<main role="main" class="mt-5 mb-5 bodyFull">
@endif
@yield('content')
</main>
</div>
<div class="row">
@include('layouts.backend.footer')
</div>
</div>
</div>
@stack('scriptsConstants')
@include('crud-policies::crud.modules.scriptConstants')
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha256-edRDsV9ULIqKzKjpN/KjyQ7Lp4vUn9usbJuHjH8Sk+k=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.16/dist/sweetalert2.all.min.js" integrity="sha256-551HBsteMvKOSqjUXSmRy/EOF0bBNbWB96b5L3Deh8A=" crossorigin="anonymous"></script>
<script src="{{ asset('js/preload-back.js') }}"></script>
@crudPoliciesJS()
@stack('scripts')
</body>
</html>

View file

@ -0,0 +1,10 @@
<footer id="footer" class="text-center text-lg-start">
<div class="text-center p-2" style="background-color: rgba(0, 0, 0, 0.05);">
<span class="text-reset">{{ $license }}</span>
-
<span class="text-reset">{{ $name }}&nbsp;-&nbsp;{{ $version }}</span>
-
<span class="text-reset fw-bold">LDC Web Services</span>
© {{ date('Y') }}
</div>
</footer>

View file

@ -0,0 +1,32 @@
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>{{ config('app.name') }}@hasSection('title') - @yield('title')@endif</title>
<meta name="description" content="@yield('description')">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
@hasSection('canonical')
<link rel="canonical" href="@yield('canonical')" />
@else
<link rel="canonical" href="{{ url()->current() }}" />
@endif
<!-- META INDEX, FOLLOW -->
@hasSection('metaIndex')
<meta name="robots" content="@yield('metaIndex')">
@else
<meta name="robots" content="index, follow">
@endif
<!-- Favicon -->
{{-- <link rel="icon" href="{{ asset('assets/images/favicon.png') }}" type="image/png">
<link rel="icon" sizes="16x16" href="{{ asset('assets/images/LG-favicons_16x16.png') }}" type="image/png">
<link rel="icon" sizes="32x32" href="{{ asset('assets/images/LG-favicons_32x32.png') }}" type="image/png">
<link rel="icon" sizes="48x48" href="{{ asset('assets/images/LG-favicons_48x48.png') }}" type="image/png"> --}}
@preload('mix-manifest.json')
<!-- STYLES -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" integrity="sha256-hHKA3d/HttC8OW3Sl093W8DoZudhHJDj++kZYo6MLzA=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.16/dist/sweetalert2.min.css" integrity="sha256-qpXwccFYncH5gTqXFe9EXZ8QeDw1Re68bbNVfFZzIzg=" crossorigin="anonymous">
<link rel="stylesheet" href="{{ asset('css/preload-back.css') }}">

View file

@ -0,0 +1,15 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
<div class="container-fluid">
<a class="navbar-brand" href="{{ route('home') }}">{{ config('app.name') }}</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarScroll" aria-controls="navbarScroll" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarScroll">
<ul class="navbar-nav ms-auto my-2 my-lg-0 navbar-nav-scroll " style="--bs-scroll-height: 100px;">
@auth()
<li class="nav-item"><span class="nav-link text-success">{{ auth()->user()->name }}</span></li>
@endauth
</ul>
</div>
</div>
</nav>

View file

@ -0,0 +1,70 @@
<nav id="sidebarMenu" class="col-md-3 col-lg-2 min-height-100vh d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3 d-flex flex-column justify-content-between align-items-center">
<div class="col-12 mt-4">
<h6 class="sidebar-heading px-3 mb-1 text-muted">
<span>{{ __(sprintf('models.classes.%s', strtolower(Str::plural(class_basename(Customer::class))))) }}</span>
</h6>
@php
$crudModel = Customer::class;
$crudModel = new $crudModel();
@endphp
<ul class="nav flex-column mb-2">
@can('viewAny', $crudModel)
<li class="nav-item"><a class="nav-link" href="{{ route(sprintf('%s%s.index', $prefix ?? '', $crudModel->getTable())) }}">{{ __('Liste') }}</a></li>
@endcan
@can('create', $crudModel)
<li class="nav-item"><a class="nav-link" href="{{ route(sprintf('%s%s.create', $prefix ?? '', $crudModel->getTable())) }}">{{ __('Ajouter') }}</a></li>
@endcan
</ul>
<hr>
@foreach($modelsClasses as $crudModel)
@php if($crudModel === \App\Models\Answer::class) continue; @endphp
@php if($crudModel === \App\Models\Customer::class) continue; @endphp
@if(
auth()->user()->can('viewAny', $crudModel) or
auth()->user()->can('create', $crudModel)
)
<h6 class="sidebar-heading px-3 mb-1 text-muted">
<span>{{ __(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($crudModel))))) }}</span>
</h6>
@php
$crudModel = new $crudModel();
@endphp
<ul class="nav flex-column mb-2">
@can('viewAny', $crudModel)
<li class="nav-item"><a class="nav-link" href="{{ route(sprintf('%s%s.index', $prefix ?? '', $crudModel->getTable())) }}">{{ __('Liste') }}</a></li>
@endcan
@can('create', $crudModel)
<li class="nav-item"><a class="nav-link" href="{{ route(sprintf('%s%s.create', $prefix ?? '', $crudModel->getTable())) }}">{{ __('Ajouter') }}</a></li>
@endcan
</ul>
<hr>
@endcan
@endforeach
<h6 class="sidebar-heading px-3 mb-1 text-muted">
<span>{{ __('Export de segmentation') }}</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item"><a class="nav-link" href="{{ route('questions.export') }}">{{ __('Exporter des questions') }}</a></li>
</ul>
<hr>
<h6 class="sidebar-heading px-3 mb-1 text-muted">
<span>{{ __('Export pour newsletter') }}</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item"><a class="nav-link" href="{{ route('customers.export') }}">{{ __('Exporter un fichier CSV') }}</a></li>
</ul>
<hr>
</div>
<div class="col-12">
<ul class="nav flex-column mb-2">
<li class="nav-item">
<form action="{{ route('logout') }}" method="POST">
@csrf
<button class="btn btn-primary">{{ __('Se déconnecter') }}</button>
</form>
</li>
</ul>
</div>
</div>
</nav>

View file

@ -1,78 +1,10 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Tableau de bord</title>
@extends('layouts.app')
<script src="https://cdn.ckeditor.com/ckeditor5/23.0.0/classic/ckeditor.js"></script>
<link rel="stylesheet" href="/css/app.css">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
html,
body {
height: 100%;
}
body {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
</style>
</head>
<body class="text-center">
@section('content')
<div class="login text-center">
<form class="form-signin" method="POST" action="{{ route('authenticate') }}">
@csrf
<img class="mb-4" src="{{ asset('images/logo_ldc.png') }}" alt="LDC Services">
<img class="mb-4" src="{{ asset('assets/images/logo_ldc.png') }}" alt="LDC Services">
<h1 class="h3 mb-3 font-weight-normal">LDC CRM</h1>
@if ($errors->any())
<div class="alert alert-danger">
@ -83,12 +15,12 @@
</ul>
</div>
@endif
<label for="inputEmail" class="sr-only">Adresse email</label>
<label for="inputEmail" class="visually-hidden">Adresse email</label>
<input type="email" id="inputEmail" class="form-control" placeholder="Adresse email" name="email" value="{{ old('email') }}" required autofocus>
<label for="inputPassword" class="sr-only">Mot de passe</label>
<label for="inputPassword" class="visually-hidden">Mot de passe</label>
<input type="password" id="inputPassword" class="form-control" placeholder="Mot de passe" name="password" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">Connexion</button>
<p class="mt-5 mb-3 text-muted">&copy; LDC Services {{ date('Y') }}</p>
</form>
</body>
</html>
</div>
@endsection

View file

@ -0,0 +1,8 @@
{{ config('app.name') }} Service Warning
Un service vient d'être régénéré sur {{ $appName }}
{{ $service->name }} (marque : {{ $service->brand->name }})
Régénéré le {{ $service->updated_at->format('D/m/Y H:i') }}
par {{ $by }}
Celà va empêcher le service de fonctionner tant que les crédentials d'api n'auront pas été mis à jour sur le service concerné, veuillez faire au plus vite !

View file

@ -0,0 +1,13 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{ config('app.name') }} Service Warning</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>Un service vient d'être régénéré sur {{ $appName }}</h1>
<h2>{{ $service->name }} (marque : {{ $service->brand->name }})</h2>
<h3>Régénéré le {{ $service->updated_at->format('D d/m/Y H:i') }} <br> par {{ $by }}</h3>
<p><b>Celà va empêcher le service de fonctionner</b> tant que les crédentials d'api n'auront pas été mis à jour sur le service concerné, veuillez faire au plus vite !</p>
</body>
</html>

View file

@ -0,0 +1,4 @@
@if(request()->question)
<a href="{{ route('questions.show', request()->question) }}" class="btn btn-success float-end">{{ Str::ucfirst(__('models.common.question')) }}</a>
<a href="{{ route('questions.answers.index', request()->question) }}" class="btn btn-dark float-end">{{ Str::ucfirst(__('models.classes.answers')) }}</a>
@endif

View file

@ -0,0 +1,44 @@
@if ($message = Session::get('success'))
<div class="alert alert-success alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<strong>{!! nl2br(e($message)) !!}</strong>
</div>
@endif
@if ($message = Session::get('error'))
<div class="alert alert-danger alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<strong>{!! nl2br(e($message)) !!}</strong>
</div>
@endif
@if ($message = Session::get('warning'))
<div class="alert alert-warning alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<strong>{!! nl2br(e($message)) !!}</strong>
</div>
@endif
@if ($message = Session::get('info'))
<div class="alert alert-info alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<strong>{!! nl2br(e($message)) !!}</strong>
</div>
@endif
@if ($errors->any())
<div class="alert alert-danger alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
@if ($errors->any())
<ul class="list-unstyled">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
</div>
@endif

View file

@ -0,0 +1,53 @@
@extends('layouts.app')
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('Export')))
@section('description', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('Export')))
@section('metaIndex', 'noindex,nofollow')
@section('content')
<div class="card">
@include('crud-policies::crud.modules.flashMessage')
<div class="card-header">
<nav aria-label="breadcrumb" class="d-inline">
<ol class="breadcrumb d-inline-flex">
<li class="breadcrumb-item">
<a href="{{ route('customers.index') }}">{{ __('models.classes.customers') }}</a>
</li>
<li class="breadcrumb-item active" aria-current="page">{{ __('Exporter les réponses au questions') }}</li>
</ol>
</nav>
</div>
<div class="card-body">
<form class="mt-5 mb-5" action="{{ route('questions.exportSubmit') }}" method="POST">
@csrf
<div class="row justify-content-center">
<div class="col-lg-6 col-md-10 col-12 mb-5">
<h1>{{ __('Exporter les réponses au questions') }}</h1>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-6 col-md-10 col-12">
<fieldset>
<legend>{{ __('La marque concernée') }}</legend>
<div class="input-group mb-3">
<label class="input-group-text" for="exportBrand">{{ __('Les client de la marque') }}</label>
<select class="form-select" id="exportBrand" name="brand" required>
<option selected value="">{{ __('Choissez une marque') }}</option>
@foreach($brands as $i => $brand)
<option value="{{ $brand->id }}">{{ $brand->name }}</option>
@endforeach
</select>
</div>
</fieldset>
</div>
</div>
<div class="row justify-content-center mt-4">
<div class="col-lg-6 col-md-10 col-12">
<button type="submit" class="btn btn-primary">{{ __('Exporter') }}</button>
</div>
</div>
</form>
</div>
</div>
@include('crud-policies::crud.modules.scriptConstants')
@endsection

View file

@ -0,0 +1,125 @@
@extends($viewLayout)
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.viewAny')))
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), trans(Str::plural(class_basename($modelClass)))))
@section('metaIndex', 'noindex,nofollow')
@section('content')
<div class="card">
@include('crud-policies::crud.modules.flashMessage')
<div class="card-header">
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
</div>
<div class="card-body">
<div class="row justify-content-center">
<div class="col">
<form action="{{ url()->current() }}">
<div class="input-group mb-3">
<label class="input-group-text" for="brandSelect">{{ __('Filter par Marque') }}</label>
<select class="form-select" id="brandSelect">
<option value="0" @if(!request()->brand) selected @endif>{{ __('Sélectionnez une marque..') }}</option>
@foreach(Brand::all() as $brand)
<option value="{{ $brand->id }}" @if(request()->brand == "$brand->id") selected @endif>{{ $brand->name }}</option>
@endforeach
</select>
<label class="input-group-text bg-warning" for="searchField">🔍🠚</label>
<input type="search" id="searchField" name="search" class="form-control" value="{{ request()->search }}" placeholder="Recherche.."/>
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}"/>
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}"/>
@if(request()->brand)
<input type="hidden" name="brand" class="d-none" value="{{ request()->brand }}"/>
@endif
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset') }}</a>
<button type="submit" class="btn btn-primary">
{{ __('crud-policies::crud.search') }}
</button>
</div>
</form>
</div>
</div>
{{ $models->appends(Request::except('rst'))->links() }}
<table class="table text-center">
<thead class="thead-dark">
@include('crud-policies::crud.modules.sorter')
</thead>
<tbody>
@if(count($models))
{{-- INIT QUERIES for OPTIMIZING --}}
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if($prop['type']->equals(CrudType::order()))
@php ${"$modelTable$fieldName"} = $modelClass::max($fieldName); @endphp
@endif
@if($prop['type']->equals(CrudType::belongsTo()))
@php try { $models->load(Str::singular((new $prop['belongsTo']())->getTable())); } catch(\Exception $e) {} @endphp
@endif
@if($prop['type']->equals(CrudType::belongsToMany()))
@php $models->load("$fieldName"); @endphp
@endif
@endforeach
{{-- DISPLAY MODELS --}}
@foreach($models as $model)
<tr>
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if(in_array(CrudAction::viewAny(), $prop['actions']))
@switch($prop['type'])
@case(CrudType::belongsToMany() and $prop['belongsToMany'] === \App\Models\Project::class)
<td>
@foreach($model->{$fieldName} as $pSite)
<a href="{{ CrudController::getRoutePrefixed($pSite->getTable().'.show', $pSite) }}">{{ $pSite->{$prop['belongsToManyLabel']} }}</a>
@endforeach
</td>
@break
@case(CrudType::password())
{{-- Ignore --}}
@break
@default
<td>@include('crud-policies::crud.printfield', [
'action' => CrudAction::viewAny(),
'field' => $model->{$fieldName},
'props' => $prop,
"$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null
])</td>
@endswitch
@endif
@endforeach
{{-- MODEL ACTIONS --}}
<td>
<div class="btn-group" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
@can('view', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}" class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
@endcan
@can('update', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model) }}" class="btn btn-sm btn-info">{{ __('crud-policies::crud.edit') }}</a>
@endcan
@can('delete', $model)
<form action="{{ CrudController::getRoutePrefixed("$modelTable.destroy", $model) }}" method="POST" onsubmit="__CRUD.confirmDelete(event)">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger">{{ __('crud-policies::crud.delete') }}</button>
</form>
@endcan
@can('viewAny', \App\Models\Answer::class)
<a href="{{ CrudController::getRoutePrefixed("questions.answers.index", $model) }}" class="btn btn-sm btn-dark">{{ Str::ucfirst(__('models.classes.answers')) }}</a>
@endcan
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="{{ count($modelClass::getEditableProperties()) }}">
{{ __('crud-policies::crud.nothingtoshow') }}
</td>
</tr>
@endif
</tbody>
</table>
{{ $models->links() }}
</div>
</div>
@include('crud-policies::crud.modules.scriptConstants')
@endsection

View file

@ -0,0 +1,158 @@
@extends($viewLayout)
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.viewAny')))
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), trans(Str::plural(class_basename($modelClass)))))
@section('metaIndex', 'noindex,nofollow')
@section('content')
<div class="card">
@include('crud-policies::crud.modules.flashMessage')
<div class="card-header">
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
</div>
<div class="card-body">
<div class="row justify-content-end">
<div class="col-6">
<form action="{{ url()->current() }}">
<div class="input-group mb-3">
<input type="search" name="search" class="form-control" value="{{ request()->search }}" placeholder="Recherche.."/>
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}"/>
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}"/>
@if(request()->brand)
<input type="hidden" name="brand" class="d-none" value="{{ request()->brand }}"/>
@endif
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset') }}</a>
<button type="submit" class="btn btn-primary">
{{ __('crud-policies::crud.search') }}
</button>
</div>
</form>
</div>
</div>
<table class="table text-center">
<thead class="thead-dark">
<tr>
@foreach ($modelClass::getEditableProperties() as $colname => $prop)
@if(in_array(CrudAction::viewAny(), $prop['actions']))
@switch($prop['type'])
@case(CrudType::password())
{{-- Ignore --}}
@break
@default
<th scope="col">
{{ $prop['label'] }}
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'desc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead asc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'asc']) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!session("crud.$modelTable.sort_way") or session("crud.$modelTable.sort_col") !== $colname or (session("crud.$modelTable.sort_way") === 'asc' and session("crud.$modelTable.sort_col") === $colname))
<a class="col_sort text-secondary lead desc" href="{{ request()->fullUrlWithQuery(['sort_col' => $colname, 'sort_way' => 'desc']) }}">&nbsp;&uarr;&nbsp;</a>
@endif
</th>
@endswitch
@endif
@endforeach
<th scope="col">
{{ __('Date du Bearer token')}}
</th>
<th scope="col">
{{ __('Régénerer un token')}}
</th>
<th scope="col">
{{ __('Date de création')}}
</th>
<th scope="col">
@if(session("crud.$modelTable.sort_way") and session("crud.$modelTable.sort_col"))
<a href="{{ request()->fullUrlWithQuery(['rst' => 'true']) }}" class="btn btn-small btn-danger">&#9003;</a>
@endif
</th>
</tr>
</thead>
<tbody>
@if(count($models))
{{-- INIT QUERIES for OPTIMIZING --}}
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if($prop['type']->equals(CrudType::order()))
@php ${"$modelTable$fieldName"} = $modelClass::max($fieldName); @endphp
@endif
@if($prop['type']->equals(CrudType::belongsTo()))
@php try { $models->load(Str::singular((new $prop['belongsTo']())->getTable())); } catch(\Exception $e) {} @endphp
@endif
@if($prop['type']->equals(CrudType::belongsToMany()))
@php $models->load("$fieldName"); @endphp
@endif
@endforeach
{{-- DISPLAY MODELS --}}
@foreach($models as $model)
<tr>
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if(in_array(CrudAction::viewAny(), $prop['actions']))
@switch($prop['type'])
@case(CrudType::belongsToMany() and $prop['belongsToMany'] === \App\Models\Project::class)
<td>
@foreach($model->{$fieldName} as $pSite)
<a href="{{ CrudController::getRoutePrefixed($pSite->getTable().'.show', $pSite) }}">{{ $pSite->{$prop['belongsToManyLabel']} }}</a>
@endforeach
</td>
@break
@case(CrudType::password())
{{-- Ignore --}}
@break
@default
<td>@include('crud-policies::crud.printfield', [
'action' => CrudAction::viewAny(),
'field' => $model->{$fieldName},
'props' => $prop,
"$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null
])</td>
@endswitch
@endif
@endforeach
{{-- INFO COLS --}}
<td>{{ Carbon::parse($model->bearer_token_date)->format('m/d/Y à H:i') }}</td>
<td><form class="genToken" action="{{ route('generate-token-service', $model) }}" method="POST">@csrf<button class="btn btn-success">Générer</button></form></td>
<td>{{ $model->created_at->format('m/d/Y à H:i') }}</td>
{{-- MODEL ACTIONS --}}
<td>
<div class="btn-group" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
@can('view', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}" class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
@endcan
@can('update', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model) }}" class="btn btn-sm btn-info">{{ __('crud-policies::crud.edit') }}</a>
@endcan
@can('delete', $model)
<form action="{{ CrudController::getRoutePrefixed("$modelTable.destroy", $model) }}" method="POST" onsubmit="__CRUD.confirmDelete(event)">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger">{{ __('crud-policies::crud.delete') }}</button>
</form>
@endcan
@can('viewAny', \App\Models\Log::class)
<a href="{{ CrudController::getRoutePrefixed("sites.logs.index", $model) }}" class="btn btn-sm btn-dark">{{ __('Journaux') }}</a>
@endcan
@can('viewAny', \App\Models\File::class)
<a href="{{ CrudController::getRoutePrefixed("sites.files.index", $model) }}" class="btn btn-sm btn-dark">{{ __('Fichiers') }}</a>
@endcan
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="{{ count($modelClass::getEditableProperties()) }}">
{{ __('crud-policies::crud.nothingtoshow') }}
</td>
</tr>
@endif
</tbody>
</table>
{{ $models->links() }}
</div>
</div>
@include('crud-policies::crud.modules.scriptConstants')
@endsection

View file

@ -1,36 +0,0 @@
@extends('layout.app')
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h2>{{ __('Gestion des services') }} <small class="text-muted">{{ __('Liste') }}</small></h2>
@if (session('success'))
<span class="text-success text-center">{!! session('success') !!}</span>
@endif
@if (session('error'))
<span class="text-danger text-center">{!! session('error') !!}</span>
@endif
<a href="{{ route('manage-service') }}" class="btn btn-sm btn-primary float-right">{{ __('Ajouter') }}</a>
</div>
<table class="table">
<thead>
<th>{{ __('Nom') }}</th>
<th>{{ __('Marque') }}</th>
<th>{{ __('Date du token') }}</th>
<th>{{ __('Regénérer un token') }}</th>
<th>{{ __('Date de création') }}</th>
<th></th>
</thead>
<tbody>
@foreach ($services as $service)
<tr>
<td>{{ $service->name }}</td>
<td>{{ $service->brand->name }}</td>
<td>{{ Carbon::parse($service->bearer_token_date)->format('d/m/Y à H:i') }}</td>
<td><a href="{{ route('generate-token-service', $service) }}" class="btn btn-sm btn-success">{{ __('Générer') }}</a></td>
<td>{{ Carbon::parse($service->created_at)->format('d/m/Y à H:i') }}</td>
<td><a href="{{ route('manage-service', $service) }}" class="btn btn-sm btn-success">{{ __('Modifier') }}</a></td>
</tr>
@endforeach
</tbody>
</table>
@endsection

View file

@ -1,42 +0,0 @@
@extends('layout.app')
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h2>{{ __('Gestion des services') }} <small class="text-muted">{{ __('Gestion') }}</small></h2>
@if ($errors->any())
<ul class="text-danger text-center">
@foreach(session('errors')->all(':message') as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
@if (session('success'))
<span class="text-success text-center">{!! session('success') !!}</span>
@endif
<a href="{{ route('services') }}" class="btn btn-sm btn-primary float-right">{{ __('Retour') }}</a>
</div>
<div class="form-group">
<form action="{{ route('manage-service', $service ? $service->id : null) }}" method="POST">
@csrf
<div class="form-group form-row">
<label class="col-form-label col" for="name">{{ __('Nom du service') }}</label>
<input type="text" id="name" name="name" class="form-control col" value="{{ $service ? $service->name : null }}">
</div>
@if (!$edit)
<div class="form-group form-row">
<label class="col-form-label col" for="brand_id">{{ __('Marque associée') }}</label>
<select class="custom-select col" id="brand_id" name="brand_id">
@foreach($brands as $brand)
<option value="{{ $brand->id }}">{{ $brand->name }}</option>
@endforeach
</select>
</div>
@else
<input type="hidden" value="{{ $service->brand->id }}" name="brand_id">
@endif
<div class="text-center">
<input type="submit" class="btn btn-success" value="{{ __('Enregistrer') }}">
</div>
</form>
</div>
@endsection

View file

@ -1,5 +0,0 @@
@extends('layout.app')
@section('content')
<search-optin dbbrands='@json($brands)'></search-optin>
@endsection

View file

@ -0,0 +1,121 @@
@extends($viewLayout)
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.viewAny')))
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'), trans(Str::plural(class_basename($modelClass)))))
@section('metaIndex', 'noindex,nofollow')
@section('content')
<div class="card">
@include('crud-policies::crud.modules.flashMessage')
<div class="card-header">
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
</div>
<div class="card-body">
<div class="row justify-content-center">
<div class="col">
<form action="{{ url()->current() }}">
<div class="input-group mb-3">
<label class="input-group-text" for="brandSelect">Filter par Marque</label>
<select class="form-select" id="brandSelect">
<option value="0" @if(!request()->brand) selected @endif>Sélectionnez une marque..</option>
@foreach(Brand::all() as $brand)
<option value="{{ $brand->id }}" @if(request()->brand == "$brand->id") selected @endif>{{ $brand->name }}</option>
@endforeach
</select>
<label class="input-group-text bg-warning" for="searchField">🔍🠚</label>
<input type="search" id="searchField" name="search" class="form-control" value="{{ request()->search }}" placeholder="Recherche.."/>
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}"/>
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}"/>
@if(request()->brand)
<input type="hidden" name="brand" class="d-none" value="{{ request()->brand }}"/>
@endif
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset') }}</a>
<button type="submit" class="btn btn-primary">
{{ __('crud-policies::crud.search') }}
</button>
</div>
</form>
</div>
</div>
<table class="table text-center">
<thead class="thead-dark">
@include('crud-policies::crud.modules.sorter')
</thead>
<tbody>
@if(count($models))
{{-- INIT QUERIES for OPTIMIZING --}}
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if($prop['type']->equals(CrudType::order()))
@php ${"$modelTable$fieldName"} = $modelClass::max($fieldName); @endphp
@endif
@if($prop['type']->equals(CrudType::belongsTo()))
@php try { $models->load(Str::singular((new $prop['belongsTo']())->getTable())); } catch(\Exception $e) {} @endphp
@endif
@if($prop['type']->equals(CrudType::belongsToMany()))
@php $models->load("$fieldName"); @endphp
@endif
@endforeach
{{-- DISPLAY MODELS --}}
@foreach($models as $model)
<tr>
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
@if(in_array(CrudAction::viewAny(), $prop['actions']))
@switch($prop['type'])
@case(CrudType::belongsToMany() and $prop['belongsToMany'] === \App\Models\Project::class)
<td>
@foreach($model->{$fieldName} as $pSite)
<a href="{{ CrudController::getRoutePrefixed($pSite->getTable().'.show', $pSite) }}">{{ $pSite->{$prop['belongsToManyLabel']} }}</a>
@endforeach
</td>
@break
@case(CrudType::password())
{{-- Ignore --}}
@break
@default
<td>@include('crud-policies::crud.printfield', [
'action' => CrudAction::viewAny(),
'field' => $model->{$fieldName},
'props' => $prop,
"$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null
])</td>
@endswitch
@endif
@endforeach
{{-- MODEL ACTIONS --}}
<td>
<div class="btn-group" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
@can('view', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}" class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
@endcan
@can('update', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model) }}" class="btn btn-sm btn-info">{{ __('crud-policies::crud.edit') }}</a>
@endcan
@can('delete', $model)
<form action="{{ CrudController::getRoutePrefixed("$modelTable.destroy", $model) }}" method="POST" onsubmit="__CRUD.confirmDelete(event)">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger">{{ __('crud-policies::crud.delete') }}</button>
</form>
@endcan
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="{{ count($modelClass::getEditableProperties()) }}">
{{ __('crud-policies::crud.nothingtoshow') }}
</td>
</tr>
@endif
</tbody>
</table>
{{ $models->links() }}
</div>
</div>
@include('crud-policies::crud.modules.scriptConstants')
@endsection

View file

@ -0,0 +1,21 @@
<div class="btn-group float-end" role="group" aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
@switch($action)
@case(CrudAction::viewAny())
@include('modules.crudAction')
@include('crud-policies::crud.modules.actionbar.create')
@break
@case(CrudAction::view())
@include('modules.crudAction')
@include('crud-policies::crud.modules.actionbar.update')
@include('crud-policies::crud.modules.actionbar.delete')
@break
@case(CrudAction::create())
@include('modules.crudAction')
@break
@case(CrudAction::update())
@include('modules.crudAction')
@include('crud-policies::crud.modules.actionbar.view')
@include('crud-policies::crud.modules.actionbar.delete')
@break
@endswitch
</div>

View file

@ -0,0 +1,3 @@
@can('create', $modelClass)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.create") }}" class="btn btn-primary float-end">{{ __('crud-policies::crud.create') }}</a>
@endcan

View file

@ -0,0 +1,7 @@
@can('delete', $model)
<form action="{{ CrudController::getRoutePrefixed("$modelTable.destroy", $model)}}" method="POST" onsubmit="__CRUD.confirmDelete(event)">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">{{ __('crud-policies::crud.delete') }}</button>
</form>
@endcan

View file

@ -0,0 +1,3 @@
@can('update', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model)}}" class="btn btn-info ">{{ __('crud-policies::crud.update') }}</a>
@endcan

View file

@ -0,0 +1,3 @@
@can('view', $model)
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}" class="btn btn-primary ">{{ __('crud-policies::crud.view') }}</a>
@endcan

View file

@ -1,10 +1,12 @@
<?php
use App\Http\Controllers\AnswerController;
use App\Http\Controllers\BrandController;
use App\Http\Controllers\CustomerController;
use App\Http\Controllers\LoginController;
use App\Http\Controllers\QuestionController;
use App\Http\Controllers\ServiceController;
use App\Http\Controllers\SingleOptinsController;
use App\Http\Controllers\SingleOptinController;
use Illuminate\Support\Facades\Route;
/*
@ -22,30 +24,38 @@ Route::get('/login', [LoginController::class, 'index'])->name('login');
Route::post('/login', [LoginController::class, 'authenticate'])->name('authenticate');
Route::middleware(['auth'])->group(function () {
Route::get('/', function () {
return redirect()->route('customers');
});
Route::get('/logout', [LoginController::class, 'logout'])->name('logout');
return redirect()->route('brands.index');
})->name('home');
Route::post('/logout', [LoginController::class, 'logout'])->name('logout');
// Marques
Route::get('/brands', [BrandController::class, 'index'])->name('brands');
Route::match(['get', 'post'], '/brand/manage/{brand?}', [BrandController::class, 'manage'])->name('manage-brand');
Route::resource('brands', BrandController::class)->except([
'delete'
]);
Route::match(['get', 'post'], '/brand/manage/{brand?}', [BrandController::class, 'manage'])
->name('manage-brand');
// Services
Route::get('/services', [ServiceController::class, 'index'])->name('services');
Route::match(['get', 'post'], '/service/manage/{service?}', [ServiceController::class, 'manage'])
->name('manage-service');
Route::get('/services/{service}/token/generate', [ServiceController::class, 'generateApiToken'])
Route::resource('services', ServiceController::class)->except([
'delete'
]);
Route::post('/services/{service}/token/generate', [ServiceController::class, 'generateApiToken'])
->name('generate-token-service');
// Clients
Route::get('/customers', [CustomerController::class, 'index'])->name('customers');
Route::get('/customers/filter/{texte}/{brand?}', [CustomerController::class, 'filter'])->name('filter-customer');
Route::get('/customer/{customer}/{brand}', [CustomerController::class, 'show'])->name('show-customer');
Route::delete('/customer/{customer}/{brand}', [CustomerController::class, 'delete'])->name('customers.delete');
Route::get('/customers', [CustomerController::class, 'index'])->name('customers.index');
Route::get('/customers/{customer}/{brand}', [CustomerController::class, 'show'])->name('customers.show');
Route::delete('/customers/{customer}/{brand}', [CustomerController::class, 'delete'])->name('customers.delete');
Route::get('/customers/export', [CustomerController::class, 'export'])->name('customers.export');
Route::post('/customers/export', [CustomerController::class, 'exportSubmit'])->name('customers.exportSubmit');
// Single optins
Route::get('/single-optins', [SingleOptinsController::class, 'index'])->name('single-optins');
Route::get('/single-optins/filter/{texte}/{brandId?}', [SingleOptinsController::class, 'filter'])
->name('filter-single-optins');
Route::post('/single-optins/{id}/delete', [SingleOptinsController::class, 'delete'])->name('single-optins-delete');
Route::resource('single_optins', SingleOptinController::class);
Route::post('/single_optins/{id}/delete', [SingleOptinController::class, 'delete'])->name('single-optins-delete');
// Questions
Route::get('/questions/export', [QuestionController::class, 'export'])->name('questions.export');
Route::post('/questions/export', [QuestionController::class, 'exportSubmit'])->name('questions.exportSubmit');
Route::resource('questions', QuestionController::class);
Route::resource('questions.answers', AnswerController::class);
});

View file

@ -20,7 +20,7 @@ class CrmClientV2RouterTest extends CrmClientTester
$this->assertEquals($route->methods(), $routes[$route->getName()]['methods']);
$this->assertEquals($route->uri(), $routes[$route->getName()]['uri']);
} else {
$this->assertArrayNotHasKey($route->getName(), $routes);
$this->assertArrayNotHasKey($route->getName() ?? '', $routes);
}
}
}

58
webpack.mix.js vendored
View file

@ -1,15 +1,49 @@
const path = require('path');
const mix = require('laravel-mix');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
require('laravel-mix-polyfill');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel applications. By default, we are compiling the CSS
| file for the application as well as bundling up all the JS files.
|
*/
mix.webpackConfig({
resolve: {
alias: {
'@': path.resolve('resources/assets')
}
},
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [
'css/*',
'js/*',
'images/*',
'fonts/*',
'assets/*'
]
})
],
});
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
let assetDir = 'assets';
mix.options({
fileLoaderDirs: {
images: `${assetDir}/images`,
fonts: `${assetDir}/fonts`
},
postCss: [
require('postcss-discard-comments')({
removeAll: true
})
]
});
// mix Backend
mix.js("resources/js/back.js", "public/js/preload-back.js")
.sass("resources/sass/back.scss", "public/css/preload-back.css")
// .vue()
.polyfill({
enabled: true,
useBuiltIns: "usage",
targets: { "firefox": "50", "ie": 11 }
});
// Copy Blade Images Assets
mix.copyDirectory('resources/assets', 'public/assets');