diff --git a/app/GrupoDeCompra.php b/app/GrupoDeCompra.php index 3a0fe18..c744346 100644 --- a/app/GrupoDeCompra.php +++ b/app/GrupoDeCompra.php @@ -14,10 +14,8 @@ use Illuminate\Support\Facades\Log; class GrupoDeCompra extends Model { - public $timestamps = false; - protected $fillable = ["nombre", "region", "telefono", "correo", "referente_finanzas", "cantidad_de_nucleos", "fila", "devoluciones_habilitadas"]; + protected $fillable = ["nombre", "region", "devoluciones_habilitadas"]; protected $table = 'grupos_de_compra'; - protected $hidden = ['password']; public function subpedidos(): HasMany { diff --git a/app/Helpers/CanastaHelper.php b/app/Helpers/CanastaHelper.php index 7a80fee..7b4c37d 100644 --- a/app/Helpers/CanastaHelper.php +++ b/app/Helpers/CanastaHelper.php @@ -3,14 +3,12 @@ namespace App\Helpers; use App\Producto; -use App\Proveedor; use App\CanastaLog; use DatabaseSeeder; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; - class CanastaHelper { const TIPO = "Tipo"; @@ -39,58 +37,38 @@ class CanastaHelper $categoria = ''; foreach($registros as $i => $registro) { - // saltear filas que no tienen tipo - if (self::noTieneTipo($registro)) { - var_dump("no hay tipo en la fila " . $i); + // saltear bono de transporte y filas que no tienen tipo + if (self::noTieneTipo($registro) || $registro[self::TIPO] == "T") continue; - } - - // saltear bono de transporte - if ($registro[self::TIPO] == "T"){ - continue; - } // obtener categoria si no hay producto if ($registro[self::PRODUCTO] == '') { // no es la pregunta de la copa? if (!Str::contains($registro[self::TIPO],"¿")) $categoria = $registro[self::TIPO]; - continue; + continue; // saltear si es la pregunta de la copa } // completar producto - $toInsert[] = [ + $toInsert[] = DatabaseSeeder::addTimestamps([ 'fila' => $i, 'categoria' => $categoria, 'nombre' => trim(str_replace('*', '',$registro[self::PRODUCTO])), 'precio' => $registro[self::PRECIO], - 'proveedor_id' => self::obtenerProveedor($registro[self::PRODUCTO]), + 'es_solidario' => Str::contains($registro[self::PRODUCTO],"*"), 'bono' => preg_match(self::REGEX_BONO, $registro[self::TIPO]), 'requiere_notas'=> $registro[self::TIPO] == self::PRODUCTO_TALLE_COLOR, - ]; + ]); } - foreach (array_chunk($toInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) { - DB::table('productos')->insert($chunk); - } + foreach (array_chunk($toInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) + Producto::insert($chunk); self::agregarBonoBarrial(); self::log($archivo, self::CANASTA_CARGADA); } - private static function obtenerProveedor($nombre) { - $result = null; - if (Str::contains($nombre,"*")){ - $result = Proveedor::firstOrCreate([ - 'nombre' => 'Proveedor de economía solidaria', - 'economia_solidaria' => 1, - 'nacional' => 1 - ])->id; - } - return $result; - } - /** * @param $path * @param $descripcion @@ -122,13 +100,13 @@ class CanastaHelper return Str::contains($c, 'BONO'); }); - DB::table('productos')->insert([ + Producto::create([ 'fila' => 420, 'nombre' => "Bono barrial", 'precio' => 20, 'categoria' => $categoria, 'bono' => 1, - 'proveedor_id' => null, + 'es_solidario' => 0, 'requiere_notas'=> false, ]); } diff --git a/app/Http/Controllers/Api/GrupoDeCompraController.php b/app/Http/Controllers/Api/GrupoDeCompraController.php index 58f88d5..aad8c13 100644 --- a/app/Http/Controllers/Api/GrupoDeCompraController.php +++ b/app/Http/Controllers/Api/GrupoDeCompraController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api; use App\GrupoDeCompra; use App\Http\Controllers\Controller; +use App\Http\Resources\GrupoDeCompraReducido; use App\Http\Resources\GrupoDeCompraResource; class GrupoDeCompraController extends Controller @@ -16,4 +17,8 @@ class GrupoDeCompraController extends Controller { return new GrupoDeCompraResource($grupoDeCompra); } + public function reducido(GrupoDeCompra $grupoDeCompra) + { + return new GrupoDeCompraReducido($grupoDeCompra); + } } diff --git a/app/Http/Controllers/Api/SubpedidoController.php b/app/Http/Controllers/Api/SubpedidoController.php index d17d571..7e6b266 100644 --- a/app/Http/Controllers/Api/SubpedidoController.php +++ b/app/Http/Controllers/Api/SubpedidoController.php @@ -17,7 +17,7 @@ class SubpedidoController extends Controller { public function index(FiltroDeSubpedido $filtros, Request $request) { - return Subpedido::filtrar($filtros)->get(); + return Subpedido::filtrar($filtros)->select('id','nombre')->get(); } public function indexResources(FiltroDeSubpedido $filtros, Request $request) @@ -35,7 +35,7 @@ class SubpedidoController extends Controller $s->nombre = $validado["nombre"]; $s->grupo_de_compra_id = $validado["grupo_de_compra_id"]; $s->save(); - return $s; + return $this->show($s); } protected function validateSubpedido(): array @@ -57,7 +57,7 @@ class SubpedidoController extends Controller // recibe request, saca producto y cantidad, valida, y pasa a syncProducto en Subpedido public function syncProductos(Subpedido $subpedido) { if ($subpedido->aprobado) - return new SubpedidoResource($subpedido); + abort(400, "No se puede modificar un pedido aprobado."); $valid = request()->validate([ 'cantidad' => ['integer','required','min:0'], @@ -80,11 +80,12 @@ class SubpedidoController extends Controller 'aprobacion' => 'required | boolean' ]); $subpedido->toggleAprobacion($valid['aprobacion']); - return new SubpedidoResource($subpedido); + return response()->noContent(); } public function syncDevoluciones(Subpedido $subpedido) { - if ($subpedido->aprobado) return new SubpedidoResource($subpedido); + if ($subpedido->aprobado) + abort(400, "No se puede modificar un pedido aprobado."); $valid = request()->validate([ 'total' => 'required|min:0', diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index c4092fa..6d96912 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -6,7 +6,6 @@ use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; class LoginController extends Controller { @@ -31,14 +30,7 @@ class LoginController extends Controller protected function authenticated(Request $request, $user) { - if ($user->is_compras) { - return redirect('compras/pedidos'); - } else if ($user->is_admin) { - session(['admin_gdc' => $user->grupo_de_compra_id]); - return redirect('admin/pedidos'); - } else { - return redirect('/'); - } + return redirect('/'); } /** diff --git a/app/Http/Controllers/RouteController.php b/app/Http/Controllers/RouteController.php new file mode 100644 index 0000000..3d29cdc --- /dev/null +++ b/app/Http/Controllers/RouteController.php @@ -0,0 +1,34 @@ +first(); + $admin = UserRole::where('nombre', 'admin_barrio')->first(); + $comision = UserRole::where('nombre', 'comision')->first(); + + switch ($request->user()->role_id) { + case $barrio->id: + return redirect('/pedido'); + case $admin->id: + return redirect('/admin'); + case $comision->id: + return redirect('/compras'); + default: + abort(400, 'Rol de usuario invalido'); + } + } + + function main(Request $request) { + return view('main'); + } +} diff --git a/app/Http/Controllers/SessionController.php b/app/Http/Controllers/SessionController.php new file mode 100644 index 0000000..94b8552 --- /dev/null +++ b/app/Http/Controllers/SessionController.php @@ -0,0 +1,25 @@ +grupo_de_compra_id; + $validated = $request->validate([ + 'id' => 'required', + Rule::in(Subpedido::where('grupo_de_compra_id', $grupo_de_compra_id)->pluck('id')), + ]); + session()->put('pedido_id', $validated["id"]); + return response()->noContent(); + } + + public function fetch() { + return session('pedido_id'); + } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..d85e473 --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,37 @@ + UserRole::find($request->user()->role_id)->nombre]; + } + + public function grupoDeCompra(Request $request) + { + $user = Auth::user(); + $result = [ 'grupo_de_compra' => null, ]; + $grupo_de_compra = GrupoDeCompra::find($user->grupo_de_compra_id); + switch (UserRole::findOrFail($user->role_id)->nombre) { + case 'barrio': + $result['grupo_de_compra'] = new GrupoDeCompraReducido($grupo_de_compra); + break; + case 'admin_barrio': + $result['grupo_de_compra'] = new GrupoDeCompraResource($grupo_de_compra); + break; + case 'comision': + break; + default: + abort(400, 'Rol invalido.'); + } + return $result; + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 243f1f2..ddef983 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,6 +2,7 @@ namespace App\Http; +use App\Http\Middleware\CheckRole; use Illuminate\Foundation\Http\Kernel as HttpKernel; use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; @@ -58,6 +59,7 @@ class Kernel extends HttpKernel 'auth' => \App\Http\Middleware\Authenticate::class, 'admin' => \App\Http\Middleware\Admin::class, 'compras' => \App\Http\Middleware\Compras::class, + 'role' => \App\Http\Middleware\CheckRole::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, diff --git a/app/Http/Middleware/Admin.php b/app/Http/Middleware/Admin.php deleted file mode 100644 index df3fdb1..0000000 --- a/app/Http/Middleware/Admin.php +++ /dev/null @@ -1,20 +0,0 @@ -is_admin) { - return $next($request); - } else { - return response('Necesitás ser admin para hacer esto', 403); - } - } -} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 704089a..265a157 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -14,7 +14,12 @@ class Authenticate extends Middleware */ protected function redirectTo($request) { - if (! $request->expectsJson()) { + if (!$request->expectsJson()) { + $path = $request->path(); + if (preg_match('~^admin.*~i', $path)) + return route('admin.login'); + if (preg_match('~^compras.*~i', $path)) + return route('compras.login'); return route('login'); } } diff --git a/app/Http/Middleware/CheckRole.php b/app/Http/Middleware/CheckRole.php new file mode 100644 index 0000000..daaf84c --- /dev/null +++ b/app/Http/Middleware/CheckRole.php @@ -0,0 +1,25 @@ +first()->id; + return $request->user()->role_id == $role_id ? $next($request) + : response('No tenés permiso para esto.', 403); + } +} diff --git a/app/Http/Middleware/Compras.php b/app/Http/Middleware/Compras.php deleted file mode 100644 index a9d9f6b..0000000 --- a/app/Http/Middleware/Compras.php +++ /dev/null @@ -1,29 +0,0 @@ -route('compras_login.show'); - - if (Auth::user()->is_compras) { - return $next($request); - } else { - return response('Necesitás ser de comisión compras para hacer esto', 403); - } - } -} diff --git a/app/Http/Resources/GrupoDeCompraReducido.php b/app/Http/Resources/GrupoDeCompraReducido.php new file mode 100644 index 0000000..b81ac02 --- /dev/null +++ b/app/Http/Resources/GrupoDeCompraReducido.php @@ -0,0 +1,22 @@ + $this->id, + 'nombre' => $this->nombre, + 'devoluciones_habilitadas' => $this->devoluciones_habilitadas, + ]; + } +} diff --git a/app/Http/Resources/ProductoResource.php b/app/Http/Resources/ProductoResource.php index 677af54..bf6eb8f 100644 --- a/app/Http/Resources/ProductoResource.php +++ b/app/Http/Resources/ProductoResource.php @@ -20,13 +20,8 @@ class ProductoResource extends JsonResource 'nombre' => $this->nombre, 'precio' => $this->precio, 'categoria' => $this->categoria, - 'proveedor' => optional($this->proveedor)->nombre, - 'economia_solidaria' => optional($this->proveedor)->economia_solidaria, - 'nacional' => optional($this->proveedor)->nacional, - 'imagen' => optional($this->poster)->url(), - 'descripcion' => $this->descripcion, - 'apto_veganxs' => $this->apto_veganxs, - 'apto_celiacxs' => $this->apto_celiacxs, + 'economia_solidaria' => $this->es_solidario, + 'nacional' => $this->es_solidario, 'requiere_notas' => $this->requiere_notas, ]; } diff --git a/app/Http/Resources/SubpedidoResource.php b/app/Http/Resources/SubpedidoResource.php index 98fd733..89b911e 100644 --- a/app/Http/Resources/SubpedidoResource.php +++ b/app/Http/Resources/SubpedidoResource.php @@ -15,11 +15,14 @@ class SubpedidoResource extends JsonResource */ public function toArray($request): array { + $productos = $this->productos; + foreach ($productos as $producto) { + $producto['pivot']['total'] = number_format($producto->pivot->cantidad * $producto->precio, 2); + } return [ 'id' => $this->id, 'nombre' => $this->nombre, - 'grupo_de_compra' => $this->grupoDeCompra, - 'productos' => $this->productos, + 'productos' => $productos, 'aprobado' => (bool) $this->aprobado, 'total' => number_format($this->total(),2), 'total_transporte' => number_format($this->totalTransporte()), diff --git a/app/Producto.php b/app/Producto.php index 9b412c3..6b55318 100644 --- a/app/Producto.php +++ b/app/Producto.php @@ -7,7 +7,6 @@ use App\Helpers\CsvHelper; use App\Helpers\TransporteHelper; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Http\Request; use Illuminate\Support\Collection; @@ -15,21 +14,14 @@ use Illuminate\Support\Facades\DB; class Producto extends Model { - public $timestamps = false; - protected $fillable = ["nombre", "precio", "presentacion", "stock", "categoria"]; - static int $paginarPorDefecto = 10; + protected $fillable = ["nombre", "precio", "categoria"]; public function subpedidos(): BelongsToMany { - return $this->belongsToMany('App\Subpedido', 'productos_subpedidos')->withPivot(["cantidad", "notas"]); + return $this->belongsToMany(Subpedido::class, 'productos_subpedidos')->withPivot(["cantidad", "notas"]); } - public function proveedor(): BelongsTo - { - return $this->belongsTo('App\Proveedor'); - } - - //Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda) + // Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda) public function scopeFiltrar($query, FiltroDeProducto $filtros): Builder { return $filtros->aplicar($query); @@ -37,7 +29,7 @@ class Producto extends Model public static function getPaginar(Request $request): int { - return $request->has('paginar') && intval($request->input('paginar')) ? intval($request->input('paginar')) : self::$paginarPorDefecto; + return $request->has('paginar') && intval($request->input('paginar')) ? intval($request->input('paginar')) : self::all()->count(); } public static function productosFilaID() diff --git a/app/Proveedor.php b/app/Proveedor.php deleted file mode 100644 index 07dbc40..0000000 --- a/app/Proveedor.php +++ /dev/null @@ -1,18 +0,0 @@ -hasMany('App\Producto'); - } -} diff --git a/app/Subpedido.php b/app/Subpedido.php index 0370020..26e38e4 100644 --- a/app/Subpedido.php +++ b/app/Subpedido.php @@ -12,17 +12,16 @@ use App\Filtros\FiltroDeSubpedido; class Subpedido extends Model { - public $timestamps = false; protected $fillable = ['grupo_de_compra_id', 'aprobado', 'nombre', 'devoluciones_total', 'devoluciones_notas']; public function productos(): BelongsToMany { - return $this->belongsToMany('App\Producto')->withPivot(["cantidad", "total", "notas"]); + return $this->belongsToMany(Producto::class)->withPivot(["cantidad", "notas"]); } public function grupoDeCompra(): BelongsTo { - return $this->belongsTo('App\GrupoDeCompra'); + return $this->belongsTo(GrupoDeCompra::class); } // Permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda) @@ -92,7 +91,7 @@ class Subpedido extends Model return TransporteHelper::cantidadTransporte($this->totalCentralesQuePaganTransporte()); } - //Actualiza el pedido, agregando o quitando del subpedido según sea necesario. Debe ser llamado desde el controlador de subpedidos, luego de validar que los parámetros $producto y $cantidad son correctos. También calcula el subtotal por producto. + // Actualiza el pedido, agregando o quitando del subpedido según sea necesario. Debe ser llamado desde el controlador de subpedidos, luego de validar que los parámetros $producto y $cantidad son correctos. También calcula el subtotal por producto. public function syncProducto(Producto $producto, int $cantidad, string $notas) { if ($cantidad) { @@ -100,7 +99,6 @@ class Subpedido extends Model $this->productos()->syncWithoutDetaching([ $producto->id => [ 'cantidad' => $cantidad, - 'total' => $cantidad * $producto->precio, 'notas' => $notas, ] ]); @@ -108,11 +106,15 @@ class Subpedido extends Model //si la cantidad es 0, se elimina el producto del subpedido $this->productos()->detach($producto->id); } + + $this->updated_at = now(); + $this->save(); } public function toggleAprobacion(bool $aprobacion) { $this->aprobado = $aprobacion; + $this->update(['aprobado' => $aprobacion]); $this->save(); } diff --git a/app/User.php b/app/User.php index 5f45a74..1dac38b 100644 --- a/app/User.php +++ b/app/User.php @@ -16,7 +16,7 @@ class User extends Authenticatable * @var array */ protected $fillable = [ - 'name', 'email', 'password', + 'name', 'email', 'password', 'role_id', ]; /** @@ -40,6 +40,6 @@ class User extends Authenticatable public function grupoDeCompra(): BelongsTo { - return $this->belongsTo('App\GrupoDeCompra'); + return $this->belongsTo(GrupoDeCompra::class); } } diff --git a/app/Admin.php b/app/UserRole.php similarity index 50% rename from app/Admin.php rename to app/UserRole.php index 9745b32..67f69f6 100644 --- a/app/Admin.php +++ b/app/UserRole.php @@ -4,7 +4,7 @@ namespace App; use Illuminate\Database\Eloquent\Model; -class Admin extends Model +class UserRole extends Model { - // + protected $fillable = ["nombre"]; } diff --git a/database/migrations/2025_05_15_021941_create_user_roles_table.php b/database/migrations/2025_05_15_021941_create_user_roles_table.php new file mode 100644 index 0000000..e4de28c --- /dev/null +++ b/database/migrations/2025_05_15_021941_create_user_roles_table.php @@ -0,0 +1,41 @@ +id(); + $table->string('nombre'); + $table->timestamps(); + }); + + $tipos = ["barrio", "admin_barrio", "comision"]; + foreach ($tipos as $tipo) { + UserRole::create([ + "nombre" => $tipo, + ]); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_roles'); + } +} diff --git a/database/migrations/2025_05_15_023829_agregar_rol_a_user.php b/database/migrations/2025_05_15_023829_agregar_rol_a_user.php new file mode 100644 index 0000000..d6e0673 --- /dev/null +++ b/database/migrations/2025_05_15_023829_agregar_rol_a_user.php @@ -0,0 +1,43 @@ +foreignId('role_id'); + }); + + $barrio = UserRole::where('nombre', 'barrio')->first(); + $admin_barrio = UserRole::where('nombre', 'admin_barrio')->first(); + $comision = UserRole::where('nombre', 'comision')->first(); + User::all()->each(function($user) use ($barrio, $comision, $admin_barrio) { + $user->role_id = $user->is_admin ? $admin_barrio->id : + ($user->is_compras ? $comision->id : $barrio->id); + $user->save(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('user', function (Blueprint $table) { + $table->dropForeign('role_id'); + }); + } +} diff --git a/database/migrations/2025_05_15_033316_simplificar_users.php b/database/migrations/2025_05_15_033316_simplificar_users.php new file mode 100644 index 0000000..649a912 --- /dev/null +++ b/database/migrations/2025_05_15_033316_simplificar_users.php @@ -0,0 +1,43 @@ +dropColumn(['is_admin', 'is_compras']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->boolean('is_admin')->default(false); + $table->boolean('is_compras')->default(false); + }); + + $admin_barrio = UserRole::where('nombre', 'admin_barrio')->first(); + $comision = UserRole::where('nombre', 'comision')->first(); + foreach (User::all() as $user) { + $user->is_admin = $user->role_id == $admin_barrio->id; + $user->is_compras = $user->role_id == $comision->id; + $user->save(); + } + } +} diff --git a/database/migrations/2025_05_15_033711_simplificar_barrios.php b/database/migrations/2025_05_15_033711_simplificar_barrios.php new file mode 100644 index 0000000..f7d4e6e --- /dev/null +++ b/database/migrations/2025_05_15_033711_simplificar_barrios.php @@ -0,0 +1,40 @@ +dropColumn([ + 'cantidad_de_nucleos', + 'telefono', + 'correo', + 'referente_finanzas', + ]); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('grupos_de_compra', function (Blueprint $table) { + $table->double('cantidad_de_nucleos'); + $table->string('telefono'); + $table->string('correo'); + $table->string('referente_finanzas'); + }); + } +} diff --git a/database/migrations/2025_05_15_034325_simplificar_productos.php b/database/migrations/2025_05_15_034325_simplificar_productos.php new file mode 100644 index 0000000..079b389 --- /dev/null +++ b/database/migrations/2025_05_15_034325_simplificar_productos.php @@ -0,0 +1,44 @@ +dropColumn([ + 'presentacion', + 'stock', + 'imagen_id', + 'descripcion', + 'apto_veganxs', + 'apto_celiacxs', + ]); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('productos', function (Blueprint $table) { + $table->integer('presentacion')->nullable(); + $table->integer('stock')->nullable(); + $table->foreignId('imagen_id')->nullable(); + $table->string('descripcion')->nullable(); + $table->boolean('apto_veganxs')->nullable(); + $table->boolean('apto_celiacxs')->nullable(); + }); + } +} diff --git a/database/migrations/2025_05_15_034910_simplificar_producto_subpedido.php b/database/migrations/2025_05_15_034910_simplificar_producto_subpedido.php new file mode 100644 index 0000000..ce6245f --- /dev/null +++ b/database/migrations/2025_05_15_034910_simplificar_producto_subpedido.php @@ -0,0 +1,32 @@ +dropColumn('total'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('producto_subpedido', function (Blueprint $table) { + $table->double('total'); + }); + } +} diff --git a/database/migrations/2025_05_15_035305_agregar_es_solidario.php b/database/migrations/2025_05_15_035305_agregar_es_solidario.php new file mode 100644 index 0000000..e4797c4 --- /dev/null +++ b/database/migrations/2025_05_15_035305_agregar_es_solidario.php @@ -0,0 +1,38 @@ +boolean('es_solidario')->default(false); + }); + + foreach (Producto::all() as $producto) { + $producto->es_solidario = $producto->proveedor_id != null; + $producto->save(); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('productos', function (Blueprint $table) { + $table->dropColumn('es_solidario'); + }); + } +} diff --git a/database/migrations/2025_05_15_035807_eliminar_proveedor.php b/database/migrations/2025_05_15_035807_eliminar_proveedor.php new file mode 100644 index 0000000..52c4210 --- /dev/null +++ b/database/migrations/2025_05_15_035807_eliminar_proveedor.php @@ -0,0 +1,59 @@ +dropColumn('proveedor_id'); + }); + Schema::dropIfExists('proveedores'); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::create('proveedores', function (Blueprint $table) { + $table->id(); + $table->string('nombre'); + $table->string('direccion')->nullable(); + $table->string('telefono')->nullable(); + $table->string('correo')->nullable(); + $table->boolean('economia_solidaria')->nullable(); + $table->boolean('nacional')->nullable(); + $table->text('detalles_de_pago')->nullable(); + $table->text('comentario')->nullable(); + $table->timestamps(); + }); + + Schema::table('productos', function (Blueprint $table) { + $table->foreignId('proveedor_id')->nullable(); + }); + + $proveedor_id = DB::table('proveedores')->insertGetId([ + ['nombre' => 'Proveedor de economía solidaria', + 'economia_solidaria' => 1, + 'nacional' => 1] + ]); + + foreach (Producto::all() as $producto) { + $producto->proveedor_id = $producto->es_solidario ? $proveedor_id : null; + $producto->save(); + } + } +} diff --git a/database/migrations/2025_05_15_041404_eliminar_admin.php b/database/migrations/2025_05_15_041404_eliminar_admin.php new file mode 100644 index 0000000..cee52ed --- /dev/null +++ b/database/migrations/2025_05_15_041404_eliminar_admin.php @@ -0,0 +1,35 @@ +id(); + $table->string('nombre'); + $table->foreignId('grupo_de_compra_id'); + $table->string('email'); + $table->string('contrasena'); + $table->timestamps(); + }); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 845fa72..57f6ac4 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -1,10 +1,19 @@ orWhere('nombre', 'admin_barrio')->get(); foreach($registros as $key => $registro){ - $gdcToInsert[] = [ + $gdcToInsert[] = DatabaseSeeder::addTimestamps([ 'nombre' => $registro['barrio'], 'region' => $registro['region'], - 'telefono' => $registro['telefono'], - 'correo' => $registro['correo'], - 'referente_finanzas' => $registro['referente'] - ]; + ]); - $usersToInsert[] = [ - 'name' => $registro['barrio'], - 'password' => Hash::make("123"), - "is_admin" => 0, - 'grupo_de_compra_id' => $key - ]; - - $usersToInsert[] = [ - 'name' => $registro['barrio'] . "_admin", - 'password' => Hash::make("123"), - "is_admin" => 1, - 'grupo_de_compra_id' => $key - ]; + foreach($roles as $role) { + $nombre = $registro['barrio'] . ($role->nombre == 'barrio' ? '' : '_admin'); + $usersToInsert[] = DatabaseSeeder::addTimestamps([ + 'name' => $nombre, + 'password' => Hash::make("123"), + 'role_id' => $role->id, + 'grupo_de_compra_id' => $key, + ]); + } } foreach (array_chunk($gdcToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) - { - DB::table('grupos_de_compra')->insert($chunk); - } + GrupoDeCompra::insert($chunk); foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) - { - DB::table('users')->insert($chunk); - } + User::insert($chunk); } } diff --git a/database/seeds/UserSeeder.php b/database/seeds/UserSeeder.php index f2164c1..35bc3b9 100644 --- a/database/seeds/UserSeeder.php +++ b/database/seeds/UserSeeder.php @@ -1,7 +1,8 @@ 'compras', + $usersToInsert[] = DatabaseSeeder::addTimestamps([ + 'name' => 'comi', 'password' => Hash::make("123"), - 'is_admin' => 0, - 'is_compras' => 1 - ]; + 'role_id' => UserRole::where('nombre', 'comision')->first()->id, + ]); foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) - { - DB::table('users')->insert($chunk); - } + User::insert($chunk); } } diff --git a/package-lock.json b/package-lock.json index c28b5a6..0f97722 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,8 @@ "animate.css": "^4.1.1", "bulma": "^0.9.4", "bulma-switch": "^2.0.4", - "bulma-toast": "^2.4.1" + "bulma-toast": "^2.4.1", + "vuex": "^3.6.2" }, "devDependencies": { "axios": "^0.19.2", @@ -350,7 +351,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -359,7 +359,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -404,7 +403,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", - "dev": true, "dependencies": { "@babel/types": "^7.27.1" }, @@ -1568,7 +1566,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -2060,7 +2057,6 @@ "version": "2.7.16", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz", "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==", - "dev": true, "dependencies": { "@babel/parser": "^7.23.5", "postcss": "^8.4.14", @@ -2075,7 +2071,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4609,8 +4604,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cyclist": { "version": "1.0.2", @@ -8717,7 +8711,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -9555,8 +9548,7 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -10557,7 +10549,6 @@ "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, "optional": true, "bin": { "prettier": "bin-prettier.js" @@ -12085,7 +12076,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12094,7 +12084,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -13345,7 +13334,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz", "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.", - "dev": true, "dependencies": { "@vue/compiler-sfc": "2.7.16", "csstype": "^3.1.0" @@ -13463,6 +13451,14 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "node_modules/vuex": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz", + "integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==", + "peerDependencies": { + "vue": "^2.0.0" + } + }, "node_modules/watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", @@ -15238,14 +15234,12 @@ "@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" }, "@babel/helper-validator-identifier": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==" }, "@babel/helper-validator-option": { "version": "7.27.1", @@ -15278,7 +15272,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", - "dev": true, "requires": { "@babel/types": "^7.27.1" } @@ -16040,7 +16033,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", - "dev": true, "requires": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -16309,7 +16301,6 @@ "version": "2.7.16", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz", "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==", - "dev": true, "requires": { "@babel/parser": "^7.23.5", "postcss": "^8.4.14", @@ -16321,7 +16312,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "requires": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -18344,8 +18334,7 @@ "csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "cyclist": { "version": "1.0.2", @@ -21555,8 +21544,7 @@ "nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==" }, "nanomatch": { "version": "1.2.13", @@ -22207,8 +22195,7 @@ "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { "version": "2.3.1", @@ -23060,7 +23047,6 @@ "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, "optional": true }, "private": { @@ -24290,14 +24276,12 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, "source-map-resolve": { "version": "0.5.3", @@ -25287,7 +25271,6 @@ "version": "2.7.16", "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz", "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", - "dev": true, "requires": { "@vue/compiler-sfc": "2.7.16", "csstype": "^3.1.0" @@ -25382,6 +25365,12 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vuex": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz", + "integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==", + "requires": {} + }, "watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", diff --git a/package.json b/package.json index cc4c17c..5964ef3 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "animate.css": "^4.1.1", "bulma": "^0.9.4", "bulma-switch": "^2.0.4", - "bulma-toast": "^2.4.1" + "bulma-toast": "^2.4.1", + "vuex": "^3.6.2" } } diff --git a/resources/js/app.js b/resources/js/app.js index c2bf1a6..40a3961 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -5,6 +5,7 @@ */ import axios from 'axios'; import Vue from 'vue'; + window.Vue = require('vue'); window.Event = new Vue(); window.axios = axios; @@ -18,33 +19,18 @@ window.bulmaToast = require('bulma-toast'); * Eg. ./components/ExampleComponent.vue -> */ import './components'; +import store from "./store"; -/** - * Constants - */ -Vue.prototype.$rootMiga = { - nombre: "Categorías", - href: "/productos" -} /** * Global methods */ -Vue.prototype.$settearProducto = function(cantidad, id) { - Event.$emit("sync-subpedido", this.cant, this.producto.id) -} -Vue.prototype.$toast = function(mensaje, duration = 2000) { - return window.bulmaToast.toast({ - message: mensaje, - duration: duration, - type: 'is-danger', - position: 'bottom-center', - }); -} -Vue.prototype.$limpiarFloat = function(unFloat) { - return parseFloat(unFloat.replace(/,/g, '')) -} -Vue.prototype.$limpiarInt = function(unInt) { - return parseInt(unInt.replace(/,/g, '')) +Vue.prototype.$toast = function (mensaje, duration = 2000) { + return window.bulmaToast.toast({ + message: mensaje, + duration: duration, + type: 'is-danger', + position: 'bottom-center', + }); } /** @@ -52,98 +38,8 @@ Vue.prototype.$limpiarInt = function(unInt) { * the page. Then, you may begin adding components to this application * or customize the JavaScript scaffolding to fit your unique needs. */ -const app = new Vue({ +new Vue({ el: '#root', - data() { - return { - gdc: null, - pedido: null, - devoluciones: null, - } - }, - computed: { - productos: function() { - return this.pedido.productos - } - }, - methods: { - cantidad(producto) { - let pedido = this.productos.some(p => p.id == producto.id) - return pedido ? this.productos.find(p => p.id == producto.id).pivot.cantidad : 0 - }, - notas(producto) { - let pedido = this.productos.some(p => p.id == producto.id); - return pedido ? this.productos.find(p => p.id == producto.id).pivot.notas : ""; - }, - settearDevoluciones() { - axios.get(`/api/grupos-de-compra/${this.gdc}/devoluciones`) - .then(response => { - this.devoluciones = response.data.devoluciones; - }); - } - }, - mounted() { - Event.$on('obtener-sesion', () => { - axios.get('/subpedidos/obtener_sesion') - .then(response => { - if (response.data.subpedido.id) { - this.gdc = response.data.gdc; - this.settearDevoluciones(); - this.pedido = response.data.subpedido.id; - axios.get('/api/subpedidos/' + this.pedido) - .then(response => { - this.pedido = response.data.data; - Event.$emit("pedido-actualizado"); - }); - } else { - axios.get('/admin/obtener_sesion') - .then(response => { - this.gdc = response.data.gdc - }); - } - }) - }) - Event.$on('sync-subpedido', (cantidad, id, notas) => { - if (this.pedido.aprobado) { - this.$toast('No se puede modificar un pedido ya aprobado', 2000); - return; - } - axios.post("/api/subpedidos/" + this.pedido.id + "/sync", { - cantidad: cantidad, - producto_id: id, - notas: notas, - }).then((response) => { - this.pedido = response.data.data - this.$toast('Pedido actualizado exitosamente') - Event.$emit("pedido-actualizado"); - }); - }); - // Actualizar monto y notas de devoluciones - Event.$on('sync-devoluciones', (total, notas) => { - if (this.pedido.aprobado) { - this.$toast('No se puede modificar un pedido ya aprobado', 2000); - return; - } - - axios.post("api/subpedidos/" + this.pedido.id + "/sync_devoluciones", { - total: total, - notas: notas, - }).then((response) => { - this.pedido = response.data.data; - this.$toast('Pedido actualizado'); - Event.$emit("pedido-actualizado"); - }); - }); - - Event.$on('aprobacion-subpedido', (subpedidoId, aprobado) => { - axios.post("/api/admin/subpedidos/" + subpedidoId + "/aprobacion", { - aprobacion: aprobado - }).then((response) => { - Event.$emit('sync-aprobacion', response.data.data); - this.$toast('Pedido ' + (aprobado ? 'aprobado' : 'desaprobado') + ' exitosamente') - }) - }) - Event.$emit('obtener-sesion') - }, + store, }); diff --git a/resources/js/components/AppMain.vue b/resources/js/components/AppMain.vue new file mode 100644 index 0000000..ffd0e0e --- /dev/null +++ b/resources/js/components/AppMain.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/resources/js/components/admin/Body.vue b/resources/js/components/admin/Body.vue index 2083305..25be16c 100644 --- a/resources/js/components/admin/Body.vue +++ b/resources/js/components/admin/Body.vue @@ -1,15 +1,11 @@ @@ -29,7 +24,7 @@ import TabsSecciones from "../comunes/TabsSecciones.vue"; import DropdownDescargar from "./DropdownDescargar.vue"; import TablaPedidos from "./TablaPedidos.vue"; import TablaBonos from "./TablaBonos.vue"; -import axios from "axios"; +import { mapActions, mapGetters } from "vuex"; export default { components: { CaracteristicasOpcionales, @@ -40,7 +35,6 @@ export default { }, data() { return { - gdc: undefined, tabs: [{ id: "pedidos", nombre: "Pedidos" }, { id: "caracteristicas", nombre: "Caracteristicas opcionales" }], tabActiva: "pedidos", @@ -48,32 +42,17 @@ export default { } }, computed: { - hayPedidos: function() { - return this.gdc && this.gdc.pedidos.length !== 0 - }, - hayAprobados: function() { - return this.gdc && this.gdc.pedidos.filter(p => p.aprobado).length > 0 - } + ...mapGetters('admin', ['hayPedidos']), }, methods: { + ...mapActions('admin', ['getGrupoDeCompra']), setSeccionActiva(tabId) { this.tabActiva = tabId; this.seccionActiva = tabId + "-seccion"; }, - actualizar() { - axios.get('/api/grupos-de-compra/' + this.$root.gdc) - .then(response => { - this.gdc = response.data.data; - console.log(this.gdc); - }) - } }, async mounted() { - Event.$on('sync-aprobacion', (_) => { - this.actualizar(); - }); - await new Promise(r => setTimeout(r, 1000)); - this.actualizar(); + await this.getGrupoDeCompra(); }, } diff --git a/resources/js/components/admin/CaracteristicasOpcionales.vue b/resources/js/components/admin/CaracteristicasOpcionales.vue index 242dfb2..26bd5f4 100644 --- a/resources/js/components/admin/CaracteristicasOpcionales.vue +++ b/resources/js/components/admin/CaracteristicasOpcionales.vue @@ -8,8 +8,7 @@ export default { caracteristicas: [ { id: "devoluciones", - nombre: "Devoluciones", - habilitada: false + nombre: "Devoluciones" }, ] } @@ -27,16 +26,15 @@ export default { - - + diff --git a/resources/js/components/admin/DropdownDescargar.vue b/resources/js/components/admin/DropdownDescargar.vue index 0b95488..08ccd4a 100644 --- a/resources/js/components/admin/DropdownDescargar.vue +++ b/resources/js/components/admin/DropdownDescargar.vue @@ -14,13 +14,13 @@ @@ -51,19 +52,25 @@ - + diff --git a/resources/js/components/comunes/BarrioSelect.vue b/resources/js/components/comunes/BarrioSelect.vue index 6743f4b..7d3ae10 100644 --- a/resources/js/components/comunes/BarrioSelect.vue +++ b/resources/js/components/comunes/BarrioSelect.vue @@ -1,13 +1,19 @@ diff --git a/resources/js/components/comunes/Login.vue b/resources/js/components/comunes/Login.vue new file mode 100644 index 0000000..5071ddb --- /dev/null +++ b/resources/js/components/comunes/Login.vue @@ -0,0 +1,57 @@ + + + + + + diff --git a/resources/js/components/comunes/LoginForm.vue b/resources/js/components/comunes/LoginForm.vue new file mode 100644 index 0000000..2773371 --- /dev/null +++ b/resources/js/components/comunes/LoginForm.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/resources/js/components/comunes/NavBar.vue b/resources/js/components/comunes/NavBar.vue index bb5a6d4..ec65835 100644 --- a/resources/js/components/comunes/NavBar.vue +++ b/resources/js/components/comunes/NavBar.vue @@ -1,91 +1,110 @@ diff --git a/resources/js/components/comunes/RegionSelect.vue b/resources/js/components/comunes/RegionSelect.vue index 55d6c8f..a6100b7 100644 --- a/resources/js/components/comunes/RegionSelect.vue +++ b/resources/js/components/comunes/RegionSelect.vue @@ -1,12 +1,20 @@ diff --git a/resources/js/components/pedidos/Body.vue b/resources/js/components/pedidos/Body.vue index c1fbd44..c4b0628 100644 --- a/resources/js/components/pedidos/Body.vue +++ b/resources/js/components/pedidos/Body.vue @@ -1,22 +1,29 @@ diff --git a/resources/js/components/pedidos/CartelPedidoAprobado.vue b/resources/js/components/pedidos/CartelPedidoAprobado.vue index be6f8a4..b719219 100644 --- a/resources/js/components/pedidos/CartelPedidoAprobado.vue +++ b/resources/js/components/pedidos/CartelPedidoAprobado.vue @@ -1,32 +1,19 @@ \ No newline at end of file + diff --git a/resources/js/components/pedidos/CategoriasContainer.vue b/resources/js/components/pedidos/CategoriasContainer.vue index 62fd96b..c31fd86 100644 --- a/resources/js/components/pedidos/CategoriasContainer.vue +++ b/resources/js/components/pedidos/CategoriasContainer.vue @@ -1,41 +1,39 @@ diff --git a/resources/js/components/pedidos/Chismosa.vue b/resources/js/components/pedidos/Chismosa.vue index bcab963..9aa0a98 100644 --- a/resources/js/components/pedidos/Chismosa.vue +++ b/resources/js/components/pedidos/Chismosa.vue @@ -12,20 +12,20 @@ B. Transporte - {{ cantidad_bonos_transporte }} - {{ total_bonos_transporte }} + {{ cantidad_transporte }} + {{ total_transporte }} - +

Devoluciones

- {{ notas_devoluciones_abbr }} - - -{{ devoluciones }} + -{{ devoluciones_total }} Total total @@ -34,7 +34,7 @@ - +

@@ -45,58 +45,43 @@ diff --git a/resources/js/components/pedidos/ChismosaDropdown.vue b/resources/js/components/pedidos/ChismosaDropdown.vue index a6fe5de..cb91a74 100644 --- a/resources/js/components/pedidos/ChismosaDropdown.vue +++ b/resources/js/components/pedidos/ChismosaDropdown.vue @@ -1,7 +1,7 @@ \ No newline at end of file +.contador { + min-width: 178px; +} + +.is-danger { + background-color: #fca697; +} + +.is-danger::placeholder { + color: #fff; + opacity: 1; /* Firefox */ +} + diff --git a/resources/js/components/pedidos/ProductoCard.vue b/resources/js/components/pedidos/ProductoCard.vue index 45eff38..fa3a978 100644 --- a/resources/js/components/pedidos/ProductoCard.vue +++ b/resources/js/components/pedidos/ProductoCard.vue @@ -1,50 +1,25 @@ @@ -56,8 +31,7 @@ export default {

{{ producto.nombre }}

-

- {{ enChismosa }} en chismosa + {{ cantidadEnChismosa }}

@@ -71,13 +45,16 @@ export default {

- + diff --git a/resources/js/components/pedidos/ProductoRow.vue b/resources/js/components/pedidos/ProductoRow.vue index 5020d9c..9b30001 100644 --- a/resources/js/components/pedidos/ProductoRow.vue +++ b/resources/js/components/pedidos/ProductoRow.vue @@ -2,15 +2,25 @@ {{ this.producto.nombre }} - + + - {{ Math.ceil(this.producto.pivot.total) }} + {{ cantidad(producto.id) }} diff --git a/resources/js/components/pedidos/ProductosContainer.vue b/resources/js/components/pedidos/ProductosContainer.vue index 3719d7d..70c8772 100644 --- a/resources/js/components/pedidos/ProductosContainer.vue +++ b/resources/js/components/pedidos/ProductosContainer.vue @@ -1,55 +1,32 @@ diff --git a/resources/js/components/pedidos/SubpedidoSelect.vue b/resources/js/components/pedidos/SubpedidoSelect.vue index 9031c7a..af64fe3 100644 --- a/resources/js/components/pedidos/SubpedidoSelect.vue +++ b/resources/js/components/pedidos/SubpedidoSelect.vue @@ -1,28 +1,32 @@ diff --git a/resources/js/store/index.js b/resources/js/store/index.js new file mode 100644 index 0000000..245553a --- /dev/null +++ b/resources/js/store/index.js @@ -0,0 +1,21 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import admin from "./modules/admin"; +import login from "./modules/login"; +import pedido from "./modules/pedido"; +import barrio from "./modules/barrio"; +import productos from "./modules/productos"; +import ui from "./modules/ui"; + +Vue.use(Vuex); + +export default new Vuex.Store({ + modules: { + admin, + login, + pedido, + barrio, + productos, + ui, + }, +}); diff --git a/resources/js/store/modules/admin.js b/resources/js/store/modules/admin.js new file mode 100644 index 0000000..230bbf3 --- /dev/null +++ b/resources/js/store/modules/admin.js @@ -0,0 +1,81 @@ +import axios from "axios"; + +const state = { + lastFetch: null, + grupo_de_compra_id: null, + nombre: null, + devoluciones_habilitadas: null, + pedidos: null, + total_a_recaudar: null, + total_barrial: null, + total_devoluciones: null, + total_a_transferir: null, + total_transporte: null, + cantidad_transporte: null, +}; + +const mutations = { + setState(state, { grupo_de_compra }) { + state.lastFetch = new Date(); + state.grupo_de_compra_id = grupo_de_compra.id; + state.nombre = grupo_de_compra.nombre; + state.devoluciones_habilitadas = grupo_de_compra.devoluciones_habilitadas; + state.pedidos = grupo_de_compra.pedidos; + state.total_a_recaudar = grupo_de_compra.total_a_recaudar; + state.total_barrial = grupo_de_compra.total_barrial; + state.total_devoluciones = grupo_de_compra.total_devoluciones; + state.total_a_transferir = grupo_de_compra.total_a_transferir; + state.total_transporte = grupo_de_compra.total_transporte; + state.cantidad_transporte = grupo_de_compra.cantidad_transporte; + }, + toggleCaracteristica(state, { caracteristica_id }) { + state[`${caracteristica_id}_habilitadas`] = !state[`${caracteristica_id}_habilitadas`]; + } +}; + +const actions = { + async getGrupoDeCompra({ commit }) { + const response = await axios.get('/user/grupo_de_compra'); + commit('setState', response.data); + }, + async setAprobacionPedido({ commit, dispatch }, { pedido_id, aprobacion }){ + await axios.post("/api/admin/subpedidos/" + pedido_id + "/aprobacion", { aprobacion: aprobacion }); + await actions.getGrupoDeCompra({ commit }); + dispatch("ui/toast", + { mensaje: `Pedido ${aprobacion ? '' : 'des' }aprobado con éxito.` }, + { root: true }); + }, + async toggleCaracteristica({ commit }, { caracteristica_id }) { + await axios.post(`/api/grupos-de-compra/${state.grupo_de_compra_id}/${caracteristica_id}`); + commit('toggleCaracteristica', { caracteristica_id: caracteristica_id }) + }, +}; + +const getters = { + grupoDeCompraDefinido() { + return state.lastFetch !== null; + }, + hayPedidos() { + return getters.grupoDeCompraDefinido() && state.pedidos.length > 0; + }, + pedidosAprobados() { + return getters.grupoDeCompraDefinido() ? state.pedidos.filter(p => p.aprobado) : []; + }, + hayAprobados() { + return getters.pedidosAprobados().length !== 0; + }, + getPedido() { + return (pedido_id) => state.pedidos.find(p => p.id === pedido_id); + }, + getCaracteristica() { + return (caracteristica) => state[`${caracteristica}_habilitadas`]; + } +}; + +export default { + namespaced: true, + state, + mutations, + actions, + getters, +}; diff --git a/resources/js/store/modules/barrio.js b/resources/js/store/modules/barrio.js new file mode 100644 index 0000000..ae2d624 --- /dev/null +++ b/resources/js/store/modules/barrio.js @@ -0,0 +1,42 @@ +import axios from "axios"; + +const state = { + grupo_de_compra_id: null, + grupo_de_compra: null, + devoluciones_habilitadas: null, + pedidos: [], +}; + +const mutations = { + setGrupoDeCompra(state, { grupo_de_compra }) { + state.grupo_de_compra_id = grupo_de_compra.id; + state.grupo_de_compra = grupo_de_compra.nombre; + state.devoluciones_habilitadas = grupo_de_compra.devoluciones_habilitadas; + }, + setPedidos(state, pedidos) { + state.pedidos = pedidos; + }, +}; + +const actions = { + async getGrupoDeCompra({ commit }) { + const response = await axios.get('/user/grupo_de_compra'); + commit('setGrupoDeCompra', response.data); + }, + async getPedidos({ commit }, nombre) { + const response = await axios.get('/api/subpedidos/',{ + params: { + nombre: nombre, + grupo_de_compra: state.grupo_de_compra_id + } + }); + commit('setPedidos', response.data); + } +}; + +export default { + namespaced: true, + state, + mutations, + actions, +}; diff --git a/resources/js/store/modules/login.js b/resources/js/store/modules/login.js new file mode 100644 index 0000000..bd9b224 --- /dev/null +++ b/resources/js/store/modules/login.js @@ -0,0 +1,60 @@ +import axios from "axios"; + +const state = { + regiones: null, + grupos_de_compra: null, + region_elegida: null, + grupo_de_compra_elegido: null, + rol: null, +}; + +const mutations = { + setRegiones(state, { regiones }) { + state.regiones = regiones; + }, + setRegionYBarrios(state, { region, grupos_de_compra }) { + state.region_elegida = region; + state.grupos_de_compra = grupos_de_compra; + }, + selectGrupoDeCompra(state, { grupo_de_compra }) { + state.grupo_de_compra_elegido = grupo_de_compra; + }, + setRol(state, { rol }) { + state.rol = rol; + }, +}; + +const actions = { + async getRegiones({ commit }) { + const response = await axios.get("/api/regiones"); + commit('setRegiones', { regiones: response.data }); + }, + async selectRegion({ commit }, { region }) { + const response = await axios.get("/api/grupos-de-compra"); + commit('setRegionYBarrios', { region: region, grupos_de_compra: response.data[region] }); + }, + async getRol({ commit }) { + const response = await axios.get("/user/rol"); + commit('setRol', { rol: response.data.rol }); + } +}; + +const getters = { + adminUrl() { + return window.location.pathname.startsWith('/admin'); + }, + mensajes() { + return { + mensaje: `Contraseña de ${getters.adminUrl() ? 'administración ' : ''}del barrio`, + ayuda: `Si no la sabés, consultá a ${getters.adminUrl() ? 'la comisión informática ' : 'tus compañerxs'}.` + }; + }, +}; + +export default { + namespaced: true, + state, + mutations, + actions, + getters, +}; diff --git a/resources/js/store/modules/pedido.js b/resources/js/store/modules/pedido.js new file mode 100644 index 0000000..cccc93b --- /dev/null +++ b/resources/js/store/modules/pedido.js @@ -0,0 +1,105 @@ +import axios from "axios"; + +const state = { + lastFetch: null, + pedido_id: null, + nombre: null, + productos: null, + aprobado: null, + total: null, + total_transporte: null, + cantidad_transporte: null, + total_sin_devoluciones: null, + devoluciones_total: null, + devoluciones_notas: null, +}; + +const mutations = { + setState(state, pedido) { + state.lastFetch = new Date(); + state.pedido_id = pedido.id; + state.nombre = pedido.nombre; + state.productos = pedido.productos; + state.aprobado = pedido.aprobado; + state.total = pedido.total; + state.total_transporte = pedido.total_transporte; + state.cantidad_transporte = pedido.cantidad_transporte; + state.total_sin_devoluciones = pedido.total_sin_devoluciones; + state.devoluciones_total = pedido.devoluciones_total; + state.devoluciones_notas = pedido.devoluciones_notas; + }, +}; + +const actions = { + async guardarSesion(_, { pedido_id }) { + await axios.post("/subpedidos/sesion", { id: pedido_id }); + }, + async getSesion({ commit }) { + const sesion = await axios.get("/subpedidos/sesion"); + if (sesion.data) { + const response = await axios.get(`/api/subpedidos/${sesion.data}`); + commit('setState', response.data.data); + } + }, + async crearPedido({ commit, dispatch }, { nombre, grupo_de_compra_id }) { + const response = await axios.post("/api/subpedidos", { + nombre: nombre, + grupo_de_compra_id: grupo_de_compra_id + }); + dispatch("guardarSesion", { pedido_id: response.data.data.id}); + commit('setState', response.data.data); + }, + async elegirPedido({ commit, dispatch }, { pedido }) { + const response = await axios.get(`/api/subpedidos/${pedido.id}`); + dispatch("guardarSesion", { pedido_id: response.data.data.id}) + commit('setState', response.data.data); + }, + async modificarChismosa({ commit, dispatch }, { producto_id, cantidad, notas }) { + try { + const response = await axios.post("/api/subpedidos/" + state.pedido_id + "/sync", { + cantidad: cantidad, + producto_id: producto_id, + notas: notas, + }); + commit('setState', response.data.data); + dispatch("ui/toast", { mensaje: 'Pedido modificado con éxito' }, { root: true }); + } catch (error) { + dispatch("ui/error", { error: error }, { root: true }); + } + }, + async modificarDevoluciones({ commit, dispatch }, { monto, notas }) { + try { + const response = await axios.post("api/subpedidos/" + state.pedido_id + "/sync_devoluciones", { + total: monto, + notas: notas, + }); + commit('setState', response.data.data); + dispatch("ui/toast", { mensaje: 'Devoluciones modificadas con éxito' }, { root: true }); + } catch (error) { + dispatch("ui/error", { error: error }, { root: true }); + } + }, +}; + +const getters = { + pedidoDefinido() { + return state.lastFetch !== null; + }, + enChismosa() { + return ((producto_id) => state.productos.some(p => p.id === producto_id)); + }, + cantidad() { + return ((producto_id) => state.productos.find(p => p.id === producto_id)?.pivot.cantidad ?? 0); + }, + notas() { + return ((producto_id) => state.productos.find(p => p.id === producto_id)?.pivot.notas ?? ""); + } +} + +export default { + namespaced: true, + state, + mutations, + actions, + getters, +}; diff --git a/resources/js/store/modules/productos.js b/resources/js/store/modules/productos.js new file mode 100644 index 0000000..27794c1 --- /dev/null +++ b/resources/js/store/modules/productos.js @@ -0,0 +1,56 @@ +import axios from "axios"; + +const state = { + lastFetch: null, + categorias: [], + productos: [], + filtro: null, +}; + +const mutations = { + setCategorias(state, categorias) { + state.lastFetch = new Date(); + state.categorias = categorias; + }, + setProductos(state, productos) { + state.lastFetch = new Date(); + state.productos = productos; + }, + setFiltro(state, filtro) { + state.lastFetch = new Date(); + state.filtro = filtro; + }, +}; + +const actions = { + async init({ dispatch }) { + dispatch('getCategorias'); + dispatch('getProductos'); + }, + async getCategorias({ commit }) { + const response = await axios.get('api/categorias'); + commit('setCategorias', response.data); + }, + async getProductos({ commit }) { + const response = await axios.get("/api/productos"); + commit('setFiltro', null); + commit('setProductos', response.data.data); + }, + async seleccionarCategoria({ dispatch }, { categoria }) { + dispatch('filtrarProductos', { filtro: "categoria", valor: categoria }); + }, + async filtrarProductos({ commit }, { filtro, valor }) { + const response = await axios.get("/api/productos", { + params: { [filtro]: valor } + }); + commit('setFiltro', { clave: filtro, valor: valor }); + commit('setProductos', response.data.data); + } +}; + +export default { + namespaced: true, + state, + mutations, + actions, +}; diff --git a/resources/js/store/modules/ui.js b/resources/js/store/modules/ui.js new file mode 100644 index 0000000..0fbb257 --- /dev/null +++ b/resources/js/store/modules/ui.js @@ -0,0 +1,48 @@ +import { dropWhile } from "lodash/array"; + +const state = { + show_chismosa: false, + show_devoluciones: false, + miga_inicial: { nombre: 'Categorias', action: 'productos/getProductos' }, + migas: [{ nombre: 'Categorias', action: 'productos/getProductos' }], +}; + +const mutations = { + toggleChismosa(state) { + state.show_chismosa = !state.show_chismosa; + }, + toggleDevoluciones(state) { + state.show_devoluciones = !state.show_devoluciones; + }, + addMiga(state, miga) { + state.migas.push(miga); + }, +}; + +const actions = { + clickMiga({ dispatch }, { miga }) { + dispatch(miga.action, null, { root: true }); + state.migas = dropWhile(state.migas.reverse(),(m => m.nombre !== miga.nombre)).reverse(); + }, + toast(_, { mensaje }) { + return window.bulmaToast.toast({ + message: mensaje, + duration: 2000, + type: 'is-danger', + position: 'bottom-center', + }); + }, + error({ dispatch }, { error }) { + const errorMsg = error.response && error.response.data && error.response.data.message + ? error.response.data.message + : error.message; + dispatch("toast", { mensaje: errorMsg }); + }, +}; + +export default { + namespaced: true, + state, + mutations, + actions, +}; diff --git a/resources/views/auth/admin_login.blade.php b/resources/views/auth/admin_login.blade.php index f1d4a07..4810c55 100644 --- a/resources/views/auth/admin_login.blade.php +++ b/resources/views/auth/admin_login.blade.php @@ -21,11 +21,10 @@ Contraseña incorrecta, intentalo nuevamente. @enderror - -
+ + @csrf - - +
diff --git a/resources/views/auth/compras_login.blade.php b/resources/views/auth/compras_login.blade.php index 6217038..ba28780 100644 --- a/resources/views/auth/compras_login.blade.php +++ b/resources/views/auth/compras_login.blade.php @@ -22,7 +22,7 @@ Contraseña incorrecta, intentalo nuevamente. @enderror -
+ @csrf
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 822042a..0962900 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -24,11 +24,10 @@ @enderror -
- @csrf - - -
+
+ @csrf + +
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index a06c090..7a5d66f 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -7,31 +7,25 @@ - {{ session("subpedido_nombre") ? "Pedido de " . session("subpedido_nombre") . " - " . config('app.name', 'Pedidos del MPS') : config('app.name', 'Pedidos del MPS')}} + {{ config('app.name', 'Pedidos del MPS') }} @yield('stylesheets') - -
- - -