atualizacao

This commit is contained in:
2026-02-22 18:16:47 +00:00
parent 7136d3e061
commit 05b7d0a82b
45 changed files with 3881 additions and 246 deletions

View File

@@ -2,7 +2,7 @@
namespace App\Http\Controllers;
use App\Models\alunos;
use App\Models\Aluno;
use App\Models\turmas;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -11,20 +11,45 @@ class AlunosController extends Controller
{
public function index()
{
$escolaId = Auth::user()->id_escola;
if ($escolaId == 0) {
$alunos = alunos::all();
} else {
$alunos = [];
$turmas = turmas::where('id_escola', $escolaId)->get();
foreach ($turmas as $turma) {
$alunosget = alunos::where('id_turma', $turma->id)->get();
$user = Auth()->user();
foreach ($alunosget as $alunosg) {
$alunos[] = $alunosg;
}
}
// coordenação/secretaria (ou quem tiver)
if ($user->can('alunos.ver_todos')) {
$alunos = Aluno::query()->get();
return view('escolas.alunos', compact('alunos'));
}
return view('escolas.alunos', compact('alunos'));
// aluno/responsável
if ($user->can('alunos.ver_meus')) {
$alunos = Aluno::query()
->where(function ($q) use ($user) {
$q->where('user_id', $user->id)
->orWhereHas('responsaveis', fn($r) => $r->where('users.id', $user->id));
})
->get();
return view('alunos.index', compact('alunos'));
}
abort(403);
}
public function updateOrCreate(Request $request)
{
$aluno = Aluno::updateOrCreate(
['cpf' => $request->cpf],
[
'nome' => $request->nome,
'id_turma' => $request->id_turma,
'id_escola' => $request->id_escola,
'data_nascimento' => $request->data_nascimento,
'data_inscricao' => $request->data_inscricao,
'cpf' => $request->cpf,
'user_id' => $request->user_id,
'responsavel_user_id' => $request->responsavel_user_id,
]
);
return redirect()->route('alunos')->with('success', 'Aluno Criado|Atualizado com sucesso.');
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ConfigsController extends Controller
{
//
}

View File

@@ -2,36 +2,186 @@
namespace App\Http\Controllers;
use App\Models\escolas;
use App\Models\Escola;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\PermissionRegistrar;
class EscolasController extends Controller
{
public function index()
{
if (Auth::user()->id_escola == 0) {
$escolas = escolas::all();
} else {
$escolas = escolas::where('id', Auth::user()->id_escola)->get();
}
$escolas = Escola::all();
return view('escolas.index', compact('escolas'));
}
public function createOrUpdate(Request $request)
{
try {
escolas::updateOrCreate([
'cnpj' => $request->cnpj,
],[
{
DB::beginTransaction();
try {
$escola = Escola::updateOrCreate(
['cnpj' => $request->cnpj],
[
'nome' => $request->razaosocial,
'endereco' => json_decode($request->address, true),
]);
return redirect()->route('escolas')->with('success', 'Escola Criada|Atualizada com sucesso');
} catch (\Throwable $th) {
dd($th);
]
);
if ($escola->wasRecentlyCreated) {
// 🔥 Limpa cache ANTES
app(PermissionRegistrar::class)->forgetCachedPermissions();
// Define tenant (team)
setPermissionsTeamId($escola->id);
/*
|--------------------------------------------------------------------------
| ROLES (POR ESCOLA)
|--------------------------------------------------------------------------
*/
$roles = [
'coordenacao',
'secretaria',
'responsavel',
'aluno',
'representante_de_turma',
];
foreach ($roles as $r) {
Role::firstOrCreate([
'name' => $r,
'guard_name' => 'web',
'id_escola' => $escola->id,
]);
}
/*
|--------------------------------------------------------------------------
| PERMISSIONS
|--------------------------------------------------------------------------
*/
$permissions = [
// escolas
'escolas.ver',
'escolas.criar',
'escolas.editar',
'escolas.deletar',
// turmas
'turmas.ver',
'turmas.detalhes',
'turmas.criar',
'turmas.editar',
'turmas.deletar',
// alunos
'alunos.ver_todos',
'alunos.ver_meus',
'alunos.criar',
'alunos.editar',
'alunos.deletar',
// whatsapp
'wpp.enviar',
];
foreach ($permissions as $p) {
Permission::firstOrCreate([
'name' => $p,
'guard_name' => 'web',
]);
}
/*
|--------------------------------------------------------------------------
| MAPEAR ROLE -> PERMISSIONS (CORRIGIDO)
|--------------------------------------------------------------------------
*/
$rolePermissions = [
// Coordenação: vê todos os alunos
'coordenacao' => [
'turmas.ver',
'turmas.detalhes',
'turmas.criar',
'turmas.editar',
'turmas.deletar',
'alunos.ver_todos',
'alunos.criar',
'alunos.editar',
'alunos.deletar',
'wpp.enviar',
],
// Secretaria: vê todos os alunos
'secretaria' => [
'turmas.ver',
'turmas.detalhes',
'alunos.ver_todos',
'alunos.criar',
'alunos.editar',
'wpp.enviar',
],
// Responsável: vê somente os "meus"
'responsavel' => [
'alunos.ver_meus',
],
// Aluno: vê somente os "meus"
'aluno' => [
'alunos.ver_meus',
],
// Representante: vê os "meus" + detalhes da turma (e wpp se você quiser)
'representante_de_turma' => [
'alunos.ver_meus',
'turmas.detalhes',
'wpp.enviar',
],
];
foreach ($rolePermissions as $roleName => $perms) {
$role = Role::query()
->where('name', $roleName)
->where('guard_name', 'web')
->where('id_escola', $escola->id)
->firstOrFail();
$role->syncPermissions($perms);
}
// 🔥 Limpa cache DEPOIS
app(PermissionRegistrar::class)->forgetCachedPermissions();
// opcional: volta contexto global
setPermissionsTeamId(null);
}
DB::commit();
return redirect()
->route('escolas')
->with('success', 'Escola Criada|Atualizada com sucesso');
} catch (\Throwable $th) {
DB::rollBack();
throw $th;
}
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Http\Controllers;
use App\Models\turmas;
use App\Models\Turma;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -11,21 +11,17 @@ class TurmasController extends Controller
public function index()
{
if (Auth::user()->id_escola == 0) {
$turmas = turmas::all();
} else {
$turmas = turmas::where('id_escola', Auth::user()->id_escola)->get();
}
$turmas = Turma::all();
return view('escolas.turmas', compact('turmas'));
}
public function createOrUpdate(Request $request)
{
$turma = turmas::where('nome', '=', $request->turma_nome)->count();
$turma = Turma::where('nome', '=', $request->turma_nome)->count();
if ($turma == 0) {
try {
$turma = new turmas();
$turma = new Turma();
$turma->id_escola = $request->id_escola;
$turma->nome = $request->turma_nome;
$turma->descricao = $request->turma_descricao;
@@ -37,7 +33,7 @@ class TurmasController extends Controller
}
} else {
try {
turmas::where('nome', $request->turma_nome)->update([
Turma::where('nome', $request->turma_nome)->update([
'nome' => $request->turma_nome,
'descricao' => $request->turma_descricao,
'id_escola' => $request->id_escola,
@@ -50,8 +46,31 @@ class TurmasController extends Controller
}
}
public function detalhes ($id) {
$turma = turmas::findOrFail($id);
return view('escolas.turmas_detalhes', compact('turma'));
public function detalhes($id)
{
$turma = Turma::findOrFail($id);
return view('escolas.Turma_detalhes', compact('turma'));
}
public function byEscola(Request $request, $escolaId)
{
// Se você tem multi-tenant e o usuário NÃO é super admin,
// é boa prática garantir que ele só consulte escola permitida.
$user = auth()->user();
if (!$user->is_super_admin) {
// se você usa pivot user_escolas:
$allowed = $user->escolas()->where('escolas.id', $escolaId)->exists();
if (!$allowed) {
abort(403);
}
}
$turmas = Turma::query()
->where('id_escola', $escolaId)
->orderBy('nome')
->get(['id', 'nome']);
return response()->json($turmas);
}
}

View File

@@ -0,0 +1,157 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Models\Escola;
use App\Models\Aluno;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
class UsersController extends Controller
{
public function index()
{
$usuarios = User::all();
return view('usuarios.index', compact('usuarios'));
}
public function rolesByEscola(Request $request, $escolaId)
{
$user = auth()->user();
if (!$user->is_super_admin) {
$allowed = $user->escolas()->where('escolas.id', $escolaId)->exists();
abort_if(!$allowed, 403);
}
// roles por escola
$roles = Role::query()
->where('id_escola', $escolaId)
->orderBy('name')
->get(['id', 'name']);
return response()->json($roles);
}
public function alunosByEscola(Request $request, $escolaId)
{
$user = auth()->user();
if (!$user->is_super_admin) {
$allowed = $user->escolas()->where('escolas.id', $escolaId)->exists();
abort_if(!$allowed, 403);
}
// alunos por escola (sem global scope, se necessário)
$alunos = Aluno::query()
->withoutGlobalScope('escola') // se seu Aluno usa BelongsToEscola, isso evita filtrar errado
->where('id_escola', $escolaId)
->orderBy('nome')
->get(['id', 'nome']);
return response()->json($alunos);
}
public function store(Request $request)
{
// validação base
$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
'password' => ['required', 'string', 'min:6', 'confirmed'],
'escola_id' => ['nullable', 'integer', 'exists:escolas,id'],
'role_name' => ['nullable', 'string'], // coordenacao/secretaria/responsavel/aluno/representante_de_turma
'is_super_admin' => ['nullable', 'boolean'],
// usado quando role for aluno/responsavel
'aluno_id' => ['nullable', 'integer', 'exists:alunos,id'],
]);
// super admin não precisa escola/role
$isSuper = (bool)($data['is_super_admin'] ?? false);
if ($isSuper) {
$data['escola_id'] = null;
$data['role_name'] = null;
$data['aluno_id'] = null;
} else {
// usuário comum precisa escola + role
if (empty($data['escola_id']) || empty($data['role_name'])) {
return back()->withErrors(['escola_id' => 'Selecione escola e cargo.'])->withInput();
}
}
// Se for responsável/aluno, aluno_id é obrigatório
if (!$isSuper && in_array($data['role_name'], ['responsavel', 'aluno'], true)) {
if (empty($data['aluno_id'])) {
return back()->withErrors(['aluno_id' => 'Selecione o aluno para vincular.'])->withInput();
}
// valida se o aluno pertence à escola selecionada
$ok = Aluno::query()
->withoutGlobalScope('escola')
->where('id', $data['aluno_id'])
->where('id_escola', $data['escola_id'])
->exists();
if (!$ok) {
return back()->withErrors(['aluno_id' => 'Aluno não pertence à escola selecionada.'])->withInput();
}
}
app(PermissionRegistrar::class)->forgetCachedPermissions();
// cria usuário
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'is_super_admin' => $isSuper,
'current_escola_id' => $data['escola_id'] ?? null,
]);
// vincula à escola (pivot)
if (!$isSuper && $user->current_escola_id) {
$user->escolas()->syncWithoutDetaching([$user->current_escola_id]);
// seta team e atribui role da escola
setPermissionsTeamId($user->current_escola_id);
$role = Role::query()
->where('name', $data['role_name'])
->where('guard_name', 'web')
->where('id_escola', $user->current_escola_id)
->firstOrFail();
$user->syncRoles([$role]);
// associações automáticas com aluno
if ($data['role_name'] === 'responsavel') {
Aluno::query()
->withoutGlobalScope('escola')
->where('id', $data['aluno_id'])
->update(['responsavel_user_id' => $user->id]);
}
if ($data['role_name'] === 'aluno') {
Aluno::query()
->withoutGlobalScope('escola')
->where('id', $data['aluno_id'])
->update(['user_id' => $user->id]);
}
}
app(PermissionRegistrar::class)->forgetCachedPermissions();
setPermissionsTeamId(null);
return back()->with('success', 'Usuário criado com sucesso!');
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class SetTenantMiddleware
{
public function handle(Request $request, Closure $next)
{
if (auth()->check()) {
setPermissionsTeamId(auth()->user()->current_escola_id);
} else {
setPermissionsTeamId(null);
}
return $next($request);
}
}

View File

@@ -1,8 +1,9 @@
<?php
namespace App\Jobs;
use App\Models\configs;
use Illuminate\Support\Facades\Log;
use App\Models\turmas;
use App\Models\Turma;
use App\Models\envios_wpp;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -38,7 +39,8 @@ class Envio_Mensagem_Whatsapp implements ShouldQueue
public function handle(): void
{
$turma = turmas::findOrFail($this->idTurma);
$turma = Turma::findOrFail($this->idTurma);
$token = configs::where('nome', '=', 'wuzapi_token')->first();
$detalhes = [];
@@ -99,7 +101,7 @@ class Envio_Mensagem_Whatsapp implements ShouldQueue
case 'texto':
$response = Http::withHeaders([
'accept' => 'application/json',
'token' => '3aCSVE4jS9h233mNBr1awz3DF0In8CFk',
'token' => $token->valor,
])
->post('https://waha.cae.app.br/chat/send/text', [
'phone' => $turma->id_whatsapp,
@@ -177,7 +179,7 @@ class Envio_Mensagem_Whatsapp implements ShouldQueue
case 'imagem';
$response = Http::withHeaders([
'accept' => 'application/json',
'token' => '3aCSVE4jS9h233mNBr1awz3DF0In8CFk',
'token' => $token->valor,
])
->post('https://waha.cae.app.br/chat/send/image', [
'phone' => $turma->id_whatsapp,

62
app/Models/Aluno.php Normal file
View File

@@ -0,0 +1,62 @@
<?php
namespace App\Models;
use App\Models\Traits\BelongsToEscola;
use Illuminate\Database\Eloquent\Model;
class Aluno extends Model
{
use BelongsToEscola;
protected $fillable = [
'id_escola',
'id_turma',
'nome',
'cpf',
'data_nascimento',
'data_inscricao',
'user_id', // usuário do aluno (portal), opcional
// 'responsavel_user_id' // ❌ removido (agora é pivot)
];
public function turma()
{
return $this->belongsTo(
Turma::class,
'id_turma'
);
}
public function escola()
{
return $this->belongsTo(
Escola::class,
'id_escola'
);
}
/**
* Usuário do aluno (se o aluno tiver login no portal)
*/
public function user()
{
return $this->belongsTo(
User::class,
'user_id'
);
}
/**
* Responsáveis do aluno (N:N via pivot aluno_responsaveis)
*/
public function responsaveis()
{
return $this->belongsToMany(
User::class,
'aluno_responsaveis',
'aluno_id',
'user_id'
)->withTimestamps();
}
}

66
app/Models/Escola.php Normal file
View File

@@ -0,0 +1,66 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
class Escola extends Model
{
protected $casts = [
'endereco' => 'array',
];
protected $fillable = [
'cnpj',
'endereco',
'nome',
];
/*
|--------------------------------------------------------------------------
| GLOBAL SCOPE MULTI-TENANT
|--------------------------------------------------------------------------
*/
protected static function booted()
{
static::addGlobalScope('user_escolas', function (Builder $builder) {
if (Auth::check() && !Auth::user()->is_super_admin) {
$builder->whereIn('id', function ($query) {
$query->select('escola_id')
->from('user_escolas')
->where('user_id', Auth::id());
});
}
});
}
/*
|--------------------------------------------------------------------------
| RELACIONAMENTOS
|--------------------------------------------------------------------------
*/
public function turmas()
{
return $this->hasMany(Turma::class, 'id_escola');
}
public function users()
{
return $this->belongsToMany(
User::class,
'user_escolas',
'escola_id',
'user_id'
);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\Builder;
trait BelongsToEscola
{
protected static function bootBelongsToEscola()
{
static::addGlobalScope('escola', function (Builder $builder) {
$user = auth()->user();
// Sem usuário logado: não filtra
if (!$user) {
return;
}
// Super admin: vê tudo (não filtra por escola)
if ($user->is_super_admin) {
return;
}
// Usuário comum: filtra pela escola atual
if ($user->current_escola_id) {
$builder->where('id_escola', $user->current_escola_id);
}
});
static::creating(function ($model) {
$user = auth()->user();
if (!$user) {
return;
}
// Super admin: não força id_escola automaticamente
if ($user->is_super_admin) {
return;
}
// Se não veio id_escola, seta pela escola atual do usuário
if (!$model->id_escola) {
$model->id_escola = $user->current_escola_id;
}
});
}
}

24
app/Models/Turma.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use App\Models\Traits\BelongsToEscola;
use Illuminate\Database\Eloquent\Model;
class Turma extends Model
{
use BelongsToEscola;
protected $fillable = [
'id_escola',
'nome'
];
public function escola()
{
return $this->belongsTo(
Escola::class,
'id_escola'
);
}
}

View File

@@ -2,47 +2,131 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
use HasRoles;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
'is_super_admin',
'current_escola_id',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
/*
|--------------------------------------------------------------------------
| RELACIONAMENTOS
|--------------------------------------------------------------------------
*/
public function escolas()
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
return $this->belongsToMany(
Escola::class,
'user_escolas',
'user_id',
'escola_id'
);
}
public function escolaAtual()
{
return $this->belongsTo(
Escola::class,
'current_escola_id'
);
}
/**
* Alunos pelos quais este usuário é responsável (N:N via pivot)
*/
public function alunosComoResponsavel()
{
return $this->belongsToMany(
Aluno::class,
'aluno_responsaveis',
'user_id',
'aluno_id'
)->withTimestamps();
}
/**
* Se você quiser acessar o "aluno" do próprio usuário (portal do aluno),
* dá pra ter 1:1 (um usuário pode ser o aluno).
*/
public function aluno()
{
return $this->hasOne(Aluno::class, 'user_id');
}
/*
|--------------------------------------------------------------------------
| SUPER ADMIN
|--------------------------------------------------------------------------
*/
public function isSuperAdmin(): bool
{
return (bool) $this->is_super_admin;
}
/*
|--------------------------------------------------------------------------
| FUNÇÃO / CARGO DO USUÁRIO (ROLE)
|--------------------------------------------------------------------------
*/
public function funcao(): ?string
{
if ($this->isSuperAdmin()) {
return 'Super Admin';
}
if (!$this->current_escola_id) {
return null;
}
setPermissionsTeamId($this->current_escola_id);
$role = $this->roles()->value('name');
if (!$role) {
return null;
}
$map = [
'coordenacao' => 'Coordenação',
'secretaria' => 'Secretaria',
'responsavel' => 'Responsável',
'aluno' => 'Aluno',
'representante_de_turma' => 'Representante de Turma',
'super_admin' => 'Super Admin',
];
return $map[$role] ?? ucfirst(str_replace('_', ' ', $role));
}
public function hasFuncao(string $funcao): bool
{
if ($funcao === 'super_admin') {
return $this->isSuperAdmin();
}
if (!$this->current_escola_id) {
return false;
}
setPermissionsTeamId($this->current_escola_id);
return $this->hasRole($funcao);
}
}

View File

@@ -1,17 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class alunos extends Model
{
public function turma()
{
return $this->belongsTo(turmas::class, 'id_turma', 'id');
}
public function responsaveis() {
return $this->hasMany(User::class, 'id', 'id_responsavel');
}
}

10
app/Models/configs.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class configs extends Model
{
//
}

View File

@@ -1,23 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class escolas extends Model
{
protected $casts = [
'endereco' => 'array',
];
protected $fillable = [
'cnpj',
'endereco',
'nome',
];
public function turmas()
{
return $this->hasMany(turmas::class, 'id_escola', 'id');
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class turmas extends Model
{
protected $fillable = [
'nome',
'descricao',
];
public function escola()
{
return $this->hasOne(escolas::class, 'id', 'id_escola');
}
public function alunos()
{
return $this->hasMany(alunos::class, 'id', 'id_turma');
}
public function enviosWpp() {
return $this->hasMany(envios_wpp::class, 'id_turma', 'id');
}
}

View File

@@ -3,22 +3,20 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
// 🔥 Super Admin ignora todas as permissões
Gate::before(function ($user, $ability) {
return $user->is_super_admin ? true : null;
});
}
}
}

View File

@@ -3,6 +3,7 @@
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Sentry\Laravel\Integration;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
@@ -11,8 +12,17 @@ return Application::configure(basePath: dirname(__DIR__))
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
// Aliases do Spatie (atenção: namespace é Middleware, singular)
$middleware->alias([
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
]);
// ✅ Setar o "team" (id_escola) automaticamente em toda request autenticada
$middleware->append(\App\Http\Middleware\SetTenantMiddleware::class);
})
->withExceptions(function (Exceptions $exceptions): void {
//
Integration::handles($exceptions);
})->create();

View File

@@ -10,7 +10,10 @@
"laravel/breeze": "^2.3",
"laravel/framework": "^12.0",
"laravel/horizon": "^5.44",
"laravel/tinker": "^2.10.1"
"laravel/tinker": "^2.10.1",
"league/flysystem-aws-s3-v3": "^3.31",
"sentry/sentry-laravel": "^4.20",
"spatie/laravel-permission": "^7.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",

967
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -41,7 +41,7 @@ return [
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage',
'url' => rtrim(env('APP_URL', 'http://localhost'), '/') . '/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
@@ -51,13 +51,12 @@ return [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', true),
'throw' => false,
'report' => false,
],
],

View File

@@ -51,6 +51,12 @@ return [
*/
'channels' => [
'sentry_logs' => [
'driver' => 'sentry_logs',
// The minimum logging level at which this handler will be triggered
// Available levels: debug, info, notice, warning, error, critical, alert, emergency
'level' => env('LOG_LEVEL', 'info'), // defaults to `debug` if not set
],
'stack' => [
'driver' => 'stack',
@@ -89,7 +95,7 @@ return [
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],

202
config/permission.php Normal file
View File

@@ -0,0 +1,202 @@
<?php
return [
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to know which
* Eloquent model should be used to retrieve your permissions. Of course, it
* is often just the "Permission" model but you may use whatever you like.
*
* The model you want to use as a Permission model needs to implement the
* `Spatie\Permission\Contracts\Permission` contract.
*/
'permission' => Spatie\Permission\Models\Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to know which
* Eloquent model should be used to retrieve your roles. Of course, it
* is often just the "Role" model but you may use whatever you like.
*
* The model you want to use as a Role model needs to implement the
* `Spatie\Permission\Contracts\Role` contract.
*/
'role' => Spatie\Permission\Models\Role::class,
],
'table_names' => [
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'roles' => 'roles',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your permissions. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'permissions' => 'permissions',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your models permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_permissions' => 'model_has_permissions',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your models roles. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_roles' => 'model_has_roles',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
/*
* Change this if you want to name the related pivots other than defaults
*/
'role_pivot_key' => null, // default 'role_id',
'permission_pivot_key' => null, // default 'permission_id',
/*
* Change this if you want to name the related model primary key other than
* `model_id`.
*
* For example, this would be nice if your primary keys are all UUIDs. In
* that case, name this `model_uuid`.
*/
'model_morph_key' => 'model_id',
/*
* Change this if you want to use the teams feature and your related model's
* foreign key is other than `team_id`.
*/
'team_foreign_key' => 'id_escola',
],
/*
* When set to true, the method for checking permissions will be registered on the gate.
* Set this to false if you want to implement custom logic for checking permissions.
*/
'register_permission_check_method' => true,
/*
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
*/
'register_octane_reset_listener' => false,
/*
* Events will fire when a role or permission is assigned/unassigned:
* \Spatie\Permission\Events\RoleAttached
* \Spatie\Permission\Events\RoleDetached
* \Spatie\Permission\Events\PermissionAttached
* \Spatie\Permission\Events\PermissionDetached
*
* To enable, set to true, and then create listeners to watch these events.
*/
'events_enabled' => false,
/*
* Teams Feature.
* When set to true the package implements teams using the 'team_foreign_key'.
* If you want the migrations to register the 'team_foreign_key', you must
* set this to true before doing the migration.
* If you already did the migration then you must make a new migration to also
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
* (view the latest version of this package's migration file)
*/
'teams' => true,
/*
* The class to use to resolve the permissions team id
*/
'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class,
/*
* Passport Client Credentials Grant
* When set to true the package will use Passports Client to check permissions
*/
'use_passport_client_credentials' => false,
/*
* When set to true, the required permission names are added to exception messages.
* This could be considered an information leak in some contexts, so the default
* setting is false here for optimum safety.
*/
'display_permission_in_exception' => false,
/*
* When set to true, the required role names are added to exception messages.
* This could be considered an information leak in some contexts, so the default
* setting is false here for optimum safety.
*/
'display_role_in_exception' => false,
/*
* By default wildcard permission lookups are disabled.
* See documentation to understand supported syntax.
*/
'enable_wildcard_permission' => false,
/*
* The class to use for interpreting wildcard permissions.
* If you need to modify delimiters, override the class and specify its name here.
*/
// 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
/* Cache-specific settings */
'cache' => [
/*
* By default all permissions are cached for 24 hours to speed up performance.
* When permissions or roles are updated the cache is flushed automatically.
*/
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
/*
* The cache key used to store all permissions.
*/
'key' => 'spatie.permission.cache',
/*
* You may optionally indicate a specific cache driver to use for permission and
* role caching using any of the `store` drivers listed in the cache.php config
* file. Using 'default' here means to use the `default` set in cache.php.
*/
'store' => 'default',
],
];

135
config/sentry.php Normal file
View File

@@ -0,0 +1,135 @@
<?php
/**
* Sentry Laravel SDK configuration file.
*
* @see https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/
*/
return [
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
// @see https://spotlightjs.com/
// 'spotlight' => env('SENTRY_SPOTLIGHT', false),
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#logger
// 'logger' => Sentry\Logger\DebugFileLogger::class, // By default this will log to `storage_path('logs/sentry.log')`
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => env('SENTRY_RELEASE'),
// When left empty or `null` the Laravel environment will be used (usually discovered from `APP_ENV` in your `.env`)
'environment' => env('SENTRY_ENVIRONMENT'),
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#sample_rate
'sample_rate' => env('SENTRY_SAMPLE_RATE') === null ? 1.0 : (float) env('SENTRY_SAMPLE_RATE'),
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#traces_sample_rate
'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_TRACES_SAMPLE_RATE'),
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#profiles-sample-rate
'profiles_sample_rate' => env('SENTRY_PROFILES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_PROFILES_SAMPLE_RATE'),
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#enable_logs
'enable_logs' => env('SENTRY_ENABLE_LOGS', false),
// The minimum log level that will be sent to Sentry as logs using the `sentry_logs` logging channel
'logs_channel_level' => env('SENTRY_LOG_LEVEL', env('SENTRY_LOGS_LEVEL', env('LOG_LEVEL', 'debug'))),
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#send_default_pii
'send_default_pii' => env('SENTRY_SEND_DEFAULT_PII', false),
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#ignore_exceptions
// 'ignore_exceptions' => [],
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#ignore_transactions
'ignore_transactions' => [
// Ignore Laravel's default health URL
'/up',
],
// Breadcrumb specific configuration
'breadcrumbs' => [
// Capture Laravel logs as breadcrumbs
'logs' => env('SENTRY_BREADCRUMBS_LOGS_ENABLED', true),
// Capture Laravel cache events (hits, writes etc.) as breadcrumbs
'cache' => env('SENTRY_BREADCRUMBS_CACHE_ENABLED', true),
// Capture Livewire components like routes as breadcrumbs
'livewire' => env('SENTRY_BREADCRUMBS_LIVEWIRE_ENABLED', true),
// Capture SQL queries as breadcrumbs
'sql_queries' => env('SENTRY_BREADCRUMBS_SQL_QUERIES_ENABLED', true),
// Capture SQL query bindings (parameters) in SQL query breadcrumbs
'sql_bindings' => env('SENTRY_BREADCRUMBS_SQL_BINDINGS_ENABLED', false),
// Capture queue job information as breadcrumbs
'queue_info' => env('SENTRY_BREADCRUMBS_QUEUE_INFO_ENABLED', true),
// Capture command information as breadcrumbs
'command_info' => env('SENTRY_BREADCRUMBS_COMMAND_JOBS_ENABLED', true),
// Capture HTTP client request information as breadcrumbs
'http_client_requests' => env('SENTRY_BREADCRUMBS_HTTP_CLIENT_REQUESTS_ENABLED', true),
// Capture send notifications as breadcrumbs
'notifications' => env('SENTRY_BREADCRUMBS_NOTIFICATIONS_ENABLED', true),
],
// Performance monitoring specific configuration
'tracing' => [
// Trace queue jobs as their own transactions (this enables tracing for queue jobs)
'queue_job_transactions' => env('SENTRY_TRACE_QUEUE_ENABLED', true),
// Capture queue jobs as spans when executed on the sync driver
'queue_jobs' => env('SENTRY_TRACE_QUEUE_JOBS_ENABLED', true),
// Capture SQL queries as spans
'sql_queries' => env('SENTRY_TRACE_SQL_QUERIES_ENABLED', true),
// Capture SQL query bindings (parameters) in SQL query spans
'sql_bindings' => env('SENTRY_TRACE_SQL_BINDINGS_ENABLED', false),
// Capture where the SQL query originated from on the SQL query spans
'sql_origin' => env('SENTRY_TRACE_SQL_ORIGIN_ENABLED', true),
// Define a threshold in milliseconds for SQL queries to resolve their origin
'sql_origin_threshold_ms' => env('SENTRY_TRACE_SQL_ORIGIN_THRESHOLD_MS', 100),
// Capture views rendered as spans
'views' => env('SENTRY_TRACE_VIEWS_ENABLED', true),
// Capture Livewire components as spans
'livewire' => env('SENTRY_TRACE_LIVEWIRE_ENABLED', true),
// Capture HTTP client requests as spans
'http_client_requests' => env('SENTRY_TRACE_HTTP_CLIENT_REQUESTS_ENABLED', true),
// Capture Laravel cache events (hits, writes etc.) as spans
'cache' => env('SENTRY_TRACE_CACHE_ENABLED', true),
// Capture Redis operations as spans (this enables Redis events in Laravel)
'redis_commands' => env('SENTRY_TRACE_REDIS_COMMANDS', false),
// Capture where the Redis command originated from on the Redis command spans
'redis_origin' => env('SENTRY_TRACE_REDIS_ORIGIN_ENABLED', true),
// Capture send notifications as spans
'notifications' => env('SENTRY_TRACE_NOTIFICATIONS_ENABLED', true),
// Enable tracing for requests without a matching route (404's)
'missing_routes' => env('SENTRY_TRACE_MISSING_ROUTES_ENABLED', false),
// Configures if the performance trace should continue after the response has been sent to the user until the application terminates
// This is required to capture any spans that are created after the response has been sent like queue jobs dispatched using `dispatch(...)->afterResponse()` for example
'continue_after_response' => env('SENTRY_TRACE_CONTINUE_AFTER_RESPONSE', true),
// Enable the tracing integrations supplied by Sentry (recommended)
'default_integrations' => env('SENTRY_TRACE_DEFAULT_INTEGRATIONS_ENABLED', true),
],
];

View File

@@ -0,0 +1,117 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// apagar tabelas antigas
Schema::dropIfExists('model_has_permissions');
Schema::dropIfExists('model_has_roles');
Schema::dropIfExists('role_has_permissions');
Schema::dropIfExists('permissions');
Schema::dropIfExists('roles');
Schema::dropIfExists('alunos');
Schema::dropIfExists('turmas');
Schema::dropIfExists('users');
Schema::dropIfExists('escolas');
// escolas
Schema::create('escolas', function (Blueprint $table) {
$table->id();
$table->string('cnpj', 18)->unique();
$table->string('nome');
$table->string('endereco')->nullable();
$table->timestamps();
});
// users
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->foreignId('id_escola')
->nullable()
->constrained('escolas')
->nullOnDelete();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->boolean('is_super_admin')->default(false);
$table->rememberToken();
$table->timestamps();
});
// turmas
Schema::create('turmas', function (Blueprint $table) {
$table->id();
$table->foreignId('id_escola')
->constrained('escolas')
->cascadeOnDelete();
$table->string('nome');
$table->text('descricao')->nullable();
$table->timestamps();
});
// alunos
Schema::create('alunos', function (Blueprint $table) {
$table->id();
$table->foreignId('id_escola')
->constrained('escolas')
->cascadeOnDelete();
$table->foreignId('id_turma')
->constrained('turmas')
->cascadeOnDelete();
$table->string('nome');
$table->string('cpf', 14)->nullable();
$table->date('data_nascimento')->nullable();
$table->date('data_inscricao')->nullable();
$table->date('data_encerramento')->nullable();
$table->string('endereco')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('alunos');
Schema::dropIfExists('turmas');
Schema::dropIfExists('users');
Schema::dropIfExists('escolas');
}
};

View File

@@ -0,0 +1,207 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
$teams = config('permission.teams');
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
$teamKey = $columnNames['team_foreign_key'];
/*
|--------------------------------------------------------------------------
| PERMISSIONS
|--------------------------------------------------------------------------
*/
Schema::create($tableNames['permissions'], function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('guard_name');
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
/*
|--------------------------------------------------------------------------
| ROLES
|--------------------------------------------------------------------------
*/
Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $teamKey) {
$table->id();
if ($teams) {
$table->unsignedBigInteger($teamKey)
->nullable();
$table->index($teamKey);
}
$table->string('name');
$table->string('guard_name');
$table->timestamps();
if ($teams) {
$table->unique([$teamKey, 'name', 'guard_name']);
} else {
$table->unique(['name', 'guard_name']);
}
});
/*
|--------------------------------------------------------------------------
| MODEL HAS PERMISSIONS
|--------------------------------------------------------------------------
*/
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table)
use ($tableNames, $columnNames, $pivotPermission, $teams, $teamKey)
{
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
if ($teams) {
$table->unsignedBigInteger($teamKey)
->nullable();
$table->index($teamKey);
}
$table->foreign($pivotPermission)
->references('id')
->on($tableNames['permissions'])
->cascadeOnDelete();
// primary SEM team_key
$table->primary([
$pivotPermission,
$columnNames['model_morph_key'],
'model_type'
]);
});
/*
|--------------------------------------------------------------------------
| MODEL HAS ROLES
|--------------------------------------------------------------------------
*/
Schema::create($tableNames['model_has_roles'], function (Blueprint $table)
use ($tableNames, $columnNames, $pivotRole, $teams, $teamKey)
{
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
if ($teams) {
$table->unsignedBigInteger($teamKey)
->nullable();
$table->index($teamKey);
}
$table->foreign($pivotRole)
->references('id')
->on($tableNames['roles'])
->cascadeOnDelete();
// primary SEM team_key
$table->primary([
$pivotRole,
$columnNames['model_morph_key'],
'model_type'
]);
});
/*
|--------------------------------------------------------------------------
| ROLE HAS PERMISSIONS
|--------------------------------------------------------------------------
*/
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table)
use ($tableNames, $pivotRole, $pivotPermission)
{
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
->references('id')
->on($tableNames['permissions'])
->cascadeOnDelete();
$table->foreign($pivotRole)
->references('id')
->on($tableNames['roles'])
->cascadeOnDelete();
$table->primary([
$pivotPermission,
$pivotRole
]);
});
app('cache')
->store(config('permission.cache.store') != 'default'
? config('permission.cache.store')
: null)
->forget(config('permission.cache.key'));
}
public function down(): void
{
$tableNames = config('permission.table_names');
Schema::dropIfExists($tableNames['role_has_permissions']);
Schema::dropIfExists($tableNames['model_has_roles']);
Schema::dropIfExists($tableNames['model_has_permissions']);
Schema::dropIfExists($tableNames['roles']);
Schema::dropIfExists($tableNames['permissions']);
}
};

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_escolas', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete();
$table->foreignId('escola_id')
->constrained()
->cascadeOnDelete();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('user_escolas');
}
};

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropConstrainedForeignId('id_escola');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
};

View File

@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->foreignId('current_escola_id')
->nullable()
->constrained('escolas');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
};

View File

@@ -0,0 +1,183 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
/*
|--------------------------------------------------------------------------
| APAGAR TODAS AS TABELAS
|--------------------------------------------------------------------------
*/
Schema::disableForeignKeyConstraints();
Schema::dropIfExists('alunos');
Schema::dropIfExists('turmas');
Schema::dropIfExists('user_escolas');
Schema::dropIfExists('escolas');
Schema::dropIfExists('users');
Schema::enableForeignKeyConstraints();
/*
|--------------------------------------------------------------------------
| CRIAR TABELA ESCOLAS
|--------------------------------------------------------------------------
*/
Schema::create('escolas', function (Blueprint $table) {
$table->id();
$table->string('cnpj')->unique();
$table->string('nome');
$table->json('endereco')->nullable();
$table->timestamps();
});
/*
|--------------------------------------------------------------------------
| CRIAR USERS
|--------------------------------------------------------------------------
*/
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->boolean('is_super_admin')
->default(false);
$table->foreignId('current_escola_id')
->nullable()
->constrained('escolas')
->nullOnDelete();
$table->rememberToken();
$table->timestamps();
});
/*
|--------------------------------------------------------------------------
| TABELA PIVOT USER_ESCOLAS
|--------------------------------------------------------------------------
*/
Schema::create('user_escolas', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete();
$table->foreignId('escola_id')
->constrained()
->cascadeOnDelete();
$table->timestamps();
});
/*
|--------------------------------------------------------------------------
| TABELA TURMAS
|--------------------------------------------------------------------------
*/
Schema::create('turmas', function (Blueprint $table) {
$table->id();
$table->foreignId('id_escola')
->constrained('escolas')
->cascadeOnDelete();
$table->string('nome');
$table->text('descricao')
->nullable();
$table->timestamps();
});
/*
|--------------------------------------------------------------------------
| TABELA ALUNOS
|--------------------------------------------------------------------------
*/
Schema::create('alunos', function (Blueprint $table) {
$table->id();
$table->foreignId('id_escola')
->constrained('escolas')
->cascadeOnDelete();
$table->foreignId('id_turma')
->constrained('turmas')
->cascadeOnDelete();
$table->string('nome');
$table->string('cpf')
->nullable();
$table->date('data_nascimento')
->nullable();
$table->date('data_inscricao')
->nullable();
$table->date('data_encerramento')
->nullable();
$table->json('endereco')
->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::disableForeignKeyConstraints();
Schema::dropIfExists('alunos');
Schema::dropIfExists('turmas');
Schema::dropIfExists('user_escolas');
Schema::dropIfExists('users');
Schema::dropIfExists('escolas');
Schema::enableForeignKeyConstraints();
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('turmas', function (Blueprint $table) {
$table->string('id_whatsapp')->unique()->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('turmas', function (Blueprint $table) {
//
});
}
};

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('configs', function (Blueprint $table) {
$table->id();
$table->string('nome')->unique();
$table->string('valor')->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('configs');
}
};

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('alunos', function (Blueprint $table) {
$table->foreignId('user_id')
->nullable()
->after('id_turma')
->constrained('users')
->nullOnDelete();
$table->foreignId('responsavel_user_id')
->nullable()
->after('user_id')
->constrained('users')
->nullOnDelete();
});
}
public function down(): void
{
Schema::table('alunos', function (Blueprint $table) {
$table->dropConstrainedForeignId('responsavel_user_id');
$table->dropConstrainedForeignId('user_id');
});
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('aluno_responsaveis', function (Blueprint $table) {
$table->id();
$table->foreignId('aluno_id')
->constrained('alunos')
->cascadeOnDelete();
$table->foreignId('user_id') // responsável (users)
->constrained('users')
->cascadeOnDelete();
$table->timestamps();
// impede duplicar o mesmo responsável no mesmo aluno
$table->unique(['aluno_id', 'user_id']);
});
}
public function down(): void
{
Schema::dropIfExists('aluno_responsaveis');
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('alunos', function (Blueprint $table) {
$table->dropConstrainedForeignId('responsavel_user_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('alunos', function (Blueprint $table) {
//
});
}
};

View File

@@ -0,0 +1,57 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
class InitialSeeder extends Seeder
{
public function run(): void
{
// Limpa cache do Spatie
app(PermissionRegistrar::class)->forgetCachedPermissions();
// Garante contexto GLOBAL (team = null)
setPermissionsTeamId(null);
/**
* 1) CRIA ROLE GLOBAL super_admin (id_escola = NULL)
* OBS: como você está com teams/id_escola, deixamos explícito.
*/
$role = Role::firstOrCreate([
'name' => 'super_admin',
'guard_name' => 'web',
'id_escola' => null,
]);
/**
* 2) CRIA USUÁRIO SUPER ADMIN
*/
$user = User::firstOrCreate(
['email' => 'admin@admin.com'],
[
'name' => 'Super Admin',
'password' => bcrypt('123456'),
'is_super_admin' => true,
'current_escola_id' => null,
]
);
/**
* 3) ATRIBUI A ROLE (GLOBAL) AO USUÁRIO
* Garante team = null novamente antes de gravar pivot.
*/
setPermissionsTeamId(null);
$user->syncRoles([$role]);
// Limpa cache de novo por segurança
app(PermissionRegistrar::class)->forgetCachedPermissions();
$this->command->info('Super Admin + role super_admin criados e associados com sucesso!');
$this->command->info('Email: admin@admin.com');
$this->command->info('Senha: 123456');
}
}

BIN
dump.rdb

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

215
python.py Normal file
View File

@@ -0,0 +1,215 @@
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak
from reportlab.lib.enums import TA_CENTER, TA_LEFT
# 1. Configuração do Arquivo
pdf_filename = "Apresentacao_Executiva_FGCOOP.pdf"
doc = SimpleDocTemplate(pdf_filename, pagesize=landscape(A4),
rightMargin=1*cm, leftMargin=1*cm,
topMargin=3.5*cm, bottomMargin=1.5*cm)
# 2. Estilos
styles = getSampleStyleSheet()
# Estilo de Título do Slide
style_slide_title = ParagraphStyle(
'SlideTitle',
parent=styles['Heading1'],
fontSize=24,
textColor=colors.HexColor("#003366"), # Azul Corporativo
spaceAfter=20,
alignment=TA_LEFT
)
# Estilo de Corpo do Slide
style_body = ParagraphStyle(
'SlideBody',
parent=styles['Normal'],
fontSize=14,
leading=18,
spaceAfter=12,
textColor=colors.HexColor("#333333")
)
# Estilo de Bullet Point
style_bullet = ParagraphStyle(
'SlideBullet',
parent=style_body,
leftIndent=20,
bulletIndent=10,
firstLineIndent=0,
spaceAfter=8
)
# Estilo da Capa (Título Gigante)
style_cover_title = ParagraphStyle(
'CoverTitle',
parent=styles['Title'],
fontSize=36,
textColor=colors.white,
alignment=TA_CENTER,
spaceAfter=20,
leading=40
)
style_cover_sub = ParagraphStyle(
'CoverSub',
parent=styles['Normal'],
fontSize=18,
textColor=colors.lightgrey,
alignment=TA_CENTER
)
# 3. Funções de Layout (Background)
def draw_background(canvas, doc):
""" Fundo padrão dos slides (Barra azul no topo e rodapé) """
width, height = landscape(A4)
# Faixa Azul no Topo
canvas.saveState()
canvas.setFillColor(colors.HexColor("#003366"))
canvas.rect(0, height - 2.5*cm, width, 2.5*cm, fill=1, stroke=0)
# Texto do Header (na faixa azul)
canvas.setFont("Helvetica-Bold", 16)
canvas.setFillColor(colors.white)
canvas.drawString(1*cm, height - 1.8*cm, "CLOUD ESSENTIAL | Assessment de Segurança")
# Rodapé
canvas.setStrokeColor(colors.HexColor("#CCCCCC"))
canvas.setLineWidth(1)
canvas.line(1*cm, 1*cm, width-1*cm, 1*cm)
# Texto do Rodapé
canvas.setFont("Helvetica", 10)
canvas.setFillColor(colors.gray)
canvas.drawString(1*cm, 0.5*cm, "Cliente: FGCOOP")
page_num = canvas.getPageNumber()
canvas.drawRightString(width-1*cm, 0.5*cm, f"Slide {page_num}")
canvas.restoreState()
def draw_cover_background(canvas, doc):
""" Fundo exclusivo da capa (Azul Total) """
width, height = landscape(A4)
canvas.saveState()
# Fundo Azul Sólido
canvas.setFillColor(colors.HexColor("#003366"))
canvas.rect(0, 0, width, height, fill=1, stroke=0)
# Elemento decorativo (Círculo sutil)
canvas.setFillColor(colors.HexColor("#004080"))
canvas.circle(width/2, height/2, 12*cm, fill=1, stroke=0)
# Logo simulado no centro inferior
canvas.setFont("Helvetica-Bold", 14)
canvas.setFillColor(colors.white)
canvas.drawCentredString(width/2, 2*cm, "CLOUD ESSENTIAL TECH")
canvas.restoreState()
# 4. Construção do Conteúdo (Story)
story = []
# --- SLIDE 1: CAPA ---
story.append(Spacer(1, 4*cm))
story.append(Paragraph("Assessment de Infraestrutura<br/>& Segurança Microsoft", style_cover_title))
story.append(Spacer(1, 1.5*cm))
story.append(Paragraph("<b>Cliente:</b> FGCOOP", style_cover_sub))
story.append(Paragraph("Janeiro 2026", style_cover_sub))
story.append(PageBreak())
# --- SLIDE 2: RESUMO EXECUTIVO ---
story.append(Paragraph("Resumo Executivo", style_slide_title))
# Texto com destaque
story.append(Paragraph("<b>1. Visão Geral:</b> Avaliação técnica dos ambientes On-Premises, Azure e M365 realizada em 19/01/2026.", style_body))
story.append(Paragraph("<b>2. Ponto Forte (Cloud):</b> O ambiente possui um <b>Microsoft Secure Score de 82,13%</b>, superior à média de mercado (43%), indicando excelente maturidade em nuvem.", style_body))
story.append(Paragraph("<b>3. Ponto de Atenção (On-Prem):</b> A infraestrutura local (AD e Servidores) apresenta riscos críticos que podem comprometer a segurança da nuvem se não tratados.", style_body))
story.append(PageBreak())
# --- SLIDE 3: OTIMIZAÇÃO FINANCEIRA ---
story.append(Paragraph("Otimização de Custos (Licenciamento)", style_slide_title))
story.append(Paragraph("Identificamos licenças ativas sem uso ou subutilizadas. Ação imediata recomendada:", style_body))
data_cost = [
['Produto / Licença', 'Situação Identificada', 'Recomendação'],
['Microsoft 365 Business Basic', '44 Licenças não utilizadas', 'Cancelar ou realocar'],
['Power BI Premium', 'Subutilização de recursos', 'Downgrade para versão Pro'],
['Licenças F1', 'Baixo consumo', 'Revisar necessidade']
]
t_cost = Table(data_cost, colWidths=[7*cm, 8*cm, 8*cm])
t_cost.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor("#004080")),
('TEXTCOLOR', (0,0), (-1,0), colors.white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('BOTTOMPADDING', (0,0), (-1,0), 12),
('BACKGROUND', (0,1), (-1,-1), colors.whitesmoke),
('GRID', (0,0), (-1,-1), 1, colors.white),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('PADDING', (0,0), (-1,-1), 8),
]))
story.append(t_cost)
story.append(PageBreak())
# --- SLIDE 4: PONTOS FORTES ---
story.append(Paragraph("Destaques de Segurança (Pontos Fortes)", style_slide_title))
story.append(Paragraph("A FGCOOP já implementou controles avançados de identidade:", style_body))
story.append(Paragraph("• <b>Autenticação Passwordless:</b> Uso de FIDO2 e Windows Hello, eliminando vetores de ataque baseados em senha.", style_bullet))
story.append(Paragraph("• <b>Proteção de Identidade:</b> Bloqueio de senhas fracas no AD Local e proteção contra força bruta ativos.", style_bullet))
story.append(Paragraph("• <b>Acesso Condicional:</b> Políticas maduras que bloqueiam autenticação legada e validam o risco do login.", style_bullet))
story.append(PageBreak())
# --- SLIDE 5: RISCOS CRÍTICOS ---
story.append(Paragraph("Riscos Críticos Identificados", style_slide_title))
story.append(Paragraph("Ativos que requerem intervenção imediata para evitar incidentes:", style_body))
data_risk = [
['Ativo', 'Nível', 'Impacto Técnico'],
['Servidor SVM42000', 'CRÍTICO 🔴', 'Falha em Replicação, Sites e Certificados KDC'],
['SVMAZDC01 / SVMAWSAD1', 'ALTO 🟠', 'Instabilidade de conectividade e sub-redes'],
['Conta KRBTGT', 'ALTO 🟠', 'Senha nunca rotacionada (Risco de Golden Ticket)'],
['Protocolo NTLM', 'MÉDIO 🟡', 'Protocolo vulnerável ativo na rede interna']
]
t_risk = Table(data_risk, colWidths=[6*cm, 3*cm, 14*cm])
t_risk.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor("#CC0000")),
('TEXTCOLOR', (0,0), (-1,0), colors.white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('ALIGN', (0,0), (-1,-1), 'LEFT'),
('GRID', (0,0), (-1,-1), 0.5, colors.grey),
('PADDING', (0,0), (-1,-1), 10),
('BACKGROUND', (0,1), (-1,-1), colors.white),
]))
story.append(t_risk)
story.append(PageBreak())
# --- SLIDE 6: PLANO DE AÇÃO ---
story.append(Paragraph("Plano de Ação: Quick Wins (0-30 Dias)", style_slide_title))
story.append(Paragraph("Ações prioritárias de baixo esforço e alto impacto:", style_body))
story.append(Paragraph("1. <b>Saneamento do AD:</b> Rotacionar senha da conta KRBTGT e corrigir delegações Kerberos.", style_bullet))
story.append(Paragraph("2. <b>Hardening de Servidores:</b> Resolver erros de replicação no SVM42000 e habilitar assinatura LDAP.", style_bullet))
story.append(Paragraph("3. <b>Proteção de Endpoints:</b> Aplicar regras de redução de superfície de ataque (ASR) via Intune.", style_bullet))
story.append(Paragraph("4. <b>Limpeza Financeira:</b> Remover licenças Business Basic ociosas identificadas.", style_bullet))
story.append(PageBreak())
# --- SLIDE 7: CONCLUSÃO ---
story.append(Paragraph("Conclusão e Próximos Passos", style_slide_title))
story.append(Paragraph("A FGCOOP está bem posicionada na nuvem (Score 82%), mas possui um 'débito técnico' perigoso na infraestrutura base (On-Premises).", style_body))
story.append(Spacer(1, 1*cm))
story.append(Paragraph("<b>Recomendação Final:</b> Priorizar a estabilização dos servidores de identidade (SVM42000) antes de iniciar novos projetos de inovação.", style_body))
story.append(Spacer(1, 2*cm))
story.append(Paragraph("Documento gerado por Cloud Essential Tech", style_body))
# 5. Gerar PDF
doc.build(story, onFirstPage=draw_cover_background, onLaterPages=draw_background)

View File

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="viho admin is super flexible, powerful, clean &amp; modern responsive bootstrap 4 admin template with unlimited possibilities.">
<meta name="keywords" content="admin template, viho admin template, dashboard template, flat admin template, responsive admin template, web app">
<meta name="author" content="pixelstrap">
<link rel="icon" href="{{ asset('assets/images/favicon.png" type="image/x-icon')}}">
<link rel="shortcut icon" href="{{ asset('assets/images/favicon.png')}}" type="image/x-icon">
<title>{{env('APP_NAME')}}</title>
<!-- Google font-->
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&amp;display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
<!-- Font Awesome-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/fontawesome.css')}}">
<!-- ico-font-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/icofont.css')}}">
<!-- Themify icon-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/themify.css')}}">
<!-- Flag icon-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/flag-icon.css')}}">
<!-- Feather icon-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/feather-icon.css')}}">
<!-- Plugins css start-->
<!-- Plugins css Ends-->
<!-- Bootstrap css-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/bootstrap.css')}}">
<!-- App css-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/style.css')}}">
<link id="color" rel="stylesheet" href="{{ asset('assets/css/color-1.css" media="screen')}}">
<!-- Responsive css-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/responsive.css')}}">
</head>
<body>
<!-- Loader starts-->
<div class="loader-wrapper">
<div class="theme-loader">
<div class="loader-p"></div>
</div>
</div>
<!-- Loader ends-->
<!-- error page start //-->
<div class="page-wrapper" id="pageWrapper">
<div class="error-wrapper">
<div class="container">
<div class="error-page1">
<div class="svg-wrraper mb-0">
<svg class="svg-60" viewbox="0 0 1920 1080" xmlns="http://www.w3.org/2000/svg">
<g>
<path class="warning-color" d="M600.87,872H156a4,4,0,0,0-3.78,4.19h0a4,4,0,0,0,3.78,4.19H600.87a4,4,0,0,0,3.78-4.19h0A4,4,0,0,0,600.87,872Z"></path>
<rect class="warning-color" height="8.39" rx="4.19" ry="4.19" width="513.38" x="680.91" y="871.98"></rect>
<path class="warning-color" d="M1480,876.17h0c0,2.32,2.37,4.19,5.3,4.19h350.61c2.93,0,5.3-1.88,5.3-4.19h0c0-2.32-2.37-4.19-5.3-4.19H1485.26C1482.33,872,1480,873.86,1480,876.17Z"></path>
<rect class="warning-color" height="8.39" rx="4.19" ry="4.19" width="249.8" x="492.21" y="920.64"></rect>
<path class="warning-color" d="M1549.14,924.84h0a4.19,4.19,0,0,0-4.19-4.19H1067.46a14.66,14.66,0,0,1,.35,3.21v1A4.19,4.19,0,0,0,1072,929h472.94A4.19,4.19,0,0,0,1549.14,924.84Z"></path>
<path class="warning-color" d="M865.5,924.84h0a4.19,4.19,0,0,0,4.19,4.19h82.37a12.28,12.28,0,0,1-.19-2v-2.17a4.19,4.19,0,0,0-4.19-4.19h-78A4.19,4.19,0,0,0,865.5,924.84Z"></path>
<rect class="warning-color" height="8.39" rx="4.19" ry="4.19" width="54.72" x="915.6" y="981.47"></rect>
<path class="warning-color" d="M730.33,985.67h0c0,2.32,4.23,4.19,9.44,4.19h104.3c5.22,0,9.44-1.88,9.44-4.19h0c0-2.32-4.23-4.19-9.44-4.19H739.78C734.56,981.47,730.33,983.35,730.33,985.67Z"></path>
<rect class="warning-color" height="8.39" rx="4.19" ry="4.19" width="78.11" x="997.06" y="981.47"></rect>
<g id="round-conf">
<path class="warning-color circle c1" d="M536.41,155.14a17.77,17.77,0,1,0,17.77,17.77A17.77,17.77,0,0,0,536.41,155.14Zm0,28.68a10.9,10.9,0,1,1,10.9-10.9A10.9,10.9,0,0,1,536.41,183.81Z"></path>
<path class="warning-color circle c1" d="M1345.09,82.44a17.77,17.77,0,1,0,17.77,17.77A17.77,17.77,0,0,0,1345.09,82.44Zm0,28.68a10.9,10.9,0,1,1,10.9-10.9A10.9,10.9,0,0,1,1345.09,111.12Z"></path>
<path class="warning-color circle c1" d="M70.12,363A17.77,17.77,0,1,0,87.89,380.8,17.77,17.77,0,0,0,70.12,363Zm0,28.68A10.9,10.9,0,1,1,81,380.8,10.9,10.9,0,0,1,70.12,391.7Z"></path>
<path class="warning-color circle c1" d="M170.47,751.82a17.77,17.77,0,1,0,17.77,17.77A17.77,17.77,0,0,0,170.47,751.82Zm0,28.68a10.9,10.9,0,1,1,10.9-10.9A10.9,10.9,0,0,1,170.47,780.5Z"></path>
<path class="warning-color circle c1" d="M1457.34,762.73a17.77,17.77,0,1,0,17.77,17.77A17.77,17.77,0,0,0,1457.34,762.73Zm0,28.68a10.9,10.9,0,1,1,10.9-10.9A10.9,10.9,0,0,1,1457.34,791.4Z"></path>
<path class="warning-color circle c1" d="M1829.15,407.49a17.77,17.77,0,1,0,17.77,17.77A17.77,17.77,0,0,0,1829.15,407.49Zm0,28.68a10.9,10.9,0,1,1,10.9-10.9A10.9,10.9,0,0,1,1829.15,436.17Z"></path>
</g>
</g>
<g id="fortyfour" data-name="Layer 2">
<g class="four a">
<rect class="primary-color" height="466.29" rx="57.1" ry="57.1" transform="translate(918.39 330.19) rotate(90)" width="120.71" x="233.74" y="391.14"></rect>
<rect class="primary-color" height="396.88" rx="60.36" ry="60.36" width="120.71" x="333.83" y="475.1"></rect>
<rect class="primary-color" height="604.75" rx="60.36" ry="60.36" transform="translate(290.49 -70.78) rotate(35)" width="120.71" x="197.13" y="122.89"></rect>
</g>
<g class="four b">
<rect class="primary-color" height="466.29" rx="57.1" ry="57.1" transform="translate(2244.26 -994.14) rotate(90)" width="120.71" x="1558.84" y="391.91"></rect>
<rect class="primary-color" height="396.88" rx="60.36" ry="60.36" width="120.71" x="1658.92" y="475.87"></rect>
<rect class="primary-color" height="604.75" rx="60.36" ry="60.36" transform="translate(530.57 -830.68) rotate(35)" width="120.71" x="1522.22" y="123.66"></rect>
</g>
<path class="primary-color" id="ou" d="M956.54,168.2c-194.34,0-351.89,157.55-351.89,351.89S762.19,872,956.54,872s351.89-157.55,351.89-351.89S1150.88,168.2,956.54,168.2Zm0,584.49c-128.46,0-232.6-104.14-232.6-232.6s104.14-232.6,232.6-232.6,232.6,104.14,232.6,232.6S1085,752.69,956.54,752.69Z"></path>
</g>
<g class="bicycle" data-name="Layer 5">
<path class="warning-color wheel" d="M1139.82,780.44a76.59,76.59,0,1,0-57.9,91.53A76.59,76.59,0,0,0,1139.82,780.44Zm-28.12,6.33a47.59,47.59,0,0,1,.83,15.8c-30.14-6.28-47.68-21.65-54.39-52.52A47.73,47.73,0,0,1,1111.69,786.77Zm-70.46-30.9c10.35,26.88,10.14,50.4-13.73,70.77a47.67,47.67,0,0,1,13.73-70.77Zm34.35,88a47.55,47.55,0,0,1-34.94-5.62c16.8-20.36,41.71-25.94,67.09-19.46A47.66,47.66,0,0,1,1075.58,843.85Z"></path>
<path class="warning-color wheel" d="M864.89,789.69a76.59,76.59,0,1,0-66.13,85.78A76.59,76.59,0,0,0,864.89,789.69Zm-28.59,3.7a47.59,47.59,0,0,1-.64,15.81c-29.43-9-45.47-26-49.3-57.33A47.73,47.73,0,0,1,836.3,793.39ZM769,756.1c7.82,27.72,5.43,51.12-20.22,69.2A47.67,47.67,0,0,1,769,756.1Zm26.06,90.78a47.55,47.55,0,0,1-34.27-8.83c18.61-18.72,43.93-22,68.6-13.16A47.66,47.66,0,0,1,795.06,846.88Z"></path>
<g>
<rect class="warning-color" height="53.21" transform="translate(-165.97 273.38) rotate(-16.19)" width="12.87" x="871.39" y="693.37"></rect>
<path class="secondary-color lighten-5" d="M813.93,679.35c-3.72-5.2,2.24-18.5,9.16-16.13,33.43,11.46,73.85,10.45,73.85,10.45,8.84.15,14.44,10.34,7.27,15.48-14.36,8.79-33.13,17-56.35,9.76C830.17,693.41,819.83,687.6,813.93,679.35Z"></path>
<path class="secondary-color opacity-o4" d="M813.93,679.35c-3.72-5.2,2.24-18.5,9.16-16.13,33.43,11.46,73.85,10.45,73.85,10.45,8.84.15,14.44,10.34,7.27,15.48-14.36,8.79-33.13,17-56.35,9.76C830.17,693.41,819.83,687.6,813.93,679.35Z"></path>
<path class="secondary-color lighten-5" d="M817.15,680.06c-3.59-5,1.69-16.51,8.37-14.22,32.3,11.09,71.41,7.83,71.41,7.83,8.54.14,17.45,9.94,7.43,15.88-13.87,8.51-32,16.44-54.44,9.44C832.84,693.67,822.85,688,817.15,680.06Z"></path>
</g>
<g>
<circle class="warning-color" cx="1022.66" cy="599.55" r="11.57" transform="translate(-4.86 8.38) rotate(-0.47)"></circle>
<path class="warning-color" d="M1069.76,792.37l-34.89-96.74,1.93-.8-1.71-4.15-1.74.72-13.26-36.76,1.27-.42-2.25-6.76,5.94-2-2.57-7.72-9.7,3.22c-11.55-22.55,2-36.33,15-41.86l-5.57-8.81c-23,10.29-29.61,28.75-19.53,54l-9.38,3.12,2.56,7.72,5.47-1.82,2.25,6.76,2.36-.78,13.62,37.76-2.35,1,1.71,4.15,2.16-.89,34.65,96.09a7.47,7.47,0,0,0,9.56,4.49h0A7.47,7.47,0,0,0,1069.76,792.37Z"></path>
<circle class="secondary-color lighten-5" cx="1027.9" cy="587.94" r="12.99" transform="translate(-4.77 8.42) rotate(-0.47)"></circle>
</g>
<path class="secondary-color lighten-5" d="M1021.29,654l-17.73,6.15,1.72,5.59-31.41,82.36c-19.35,32.53-66.3,36.72-75.56,16.68l-7.09-21.5L879,747.1l3.28,10.09-94.65,33.95c-11.49,2.29-11.85,15.79-2.61,17.84,0,0,39.11,3.66,103,9.5a92.75,92.75,0,0,0,40.89-5.29c44.32-16.56,57.73-50.67,57.73-50.67l26.82-67.26a1.37,1.37,0,0,1,2.53,0l1.42,3.33,17.75-7.62Z"></path>
<path class="secondary-color opacity-o4" d="M1021.29,654l-17.73,6.15,1.72,5.59-31.41,82.36c-19.35,32.53-66.3,36.72-75.56,16.68l-7.09-21.5L879,747.1l3.28,10.09-94.65,33.95c-11.49,2.29-11.85,15.79-2.61,17.84,0,0,39.11,3.66,103,9.5a92.75,92.75,0,0,0,40.89-5.29c44.32-16.56,57.73-50.67,57.73-50.67l26.82-67.26a1.37,1.37,0,0,1,2.53,0l1.42,3.33,17.75-7.62Z"></path>
</g>
</svg>
</div>
<div class="col-md-8 offset-md-2">
<h3>Oops! Parece que você não tem permissão de acesso.</h3>
<p class="sub-content">Você está tentando acessar uma página que você não tem acesso, foi movida ou não existe mais. Entre em contato com suporte.</p><a class="btn btn-primary btn-lg" href="{{ route('dashboard') }}">Voltar para o sistema</a>
</div>
</div>
</div>
</div>
</div>
<!-- error page end //-->
<!-- latest jquery-->
<script src="{{ asset('assets/js/jquery-3.5.1.min.js')}}"></script>
<!-- feather icon js-->
<script src="{{ asset('assets/js/icons/feather-icon/feather.min.js')}}"></script>
<script src="{{ asset('assets/js/icons/feather-icon/feather-icon.js')}}"></script>
<!-- Sidebar jquery-->
<script src="{{ asset('assets/js/sidebar-menu.js')}}"></script>
<script src="{{ asset('assets/js/config.js')}}"></script>
<!-- Bootstrap js-->
<script src="{{ asset('assets/js/bootstrap/popper.min.js')}}"></script>
<script src="{{ asset('assets/js/bootstrap/bootstrap.min.js')}}"></script>
<!-- Plugins JS start-->
<!-- Plugins JS Ends-->
<!-- Theme js-->
<script src="{{ asset('assets/js/script.js')}}"></script>
<!-- login js-->
<!-- Plugin used-->
</body>
</html>

View File

@@ -0,0 +1,6 @@
@extends('theme.default')
@section('title', 'Alunos')
@section('title.page', 'Alunos')
@section('title.page1', 'CAE')
@section('title.page2', 'Alunos')
@section('title.page3', 'Lista de alunos')

View File

@@ -30,19 +30,27 @@
<td>{{$escola->nome}}</td>
<td>{{$escola->cnpj}}</td>
<td>
<span>Endereço:</span>
<p>{{ $escola->endereco['street'] }} {{ $escola->endereco['number'] }}
</p>
@if (isset($escola->endereco))
<span>Endereço:</span>
<p>{{ $escola->endereco['street'] }} {{ $escola->endereco['number'] }}</p>
@endif
</td>
<td>
<span>Complemento:</span>
<p>{{ $escola->endereco['details'] }} </p>
@if (isset($escola->endereco))
<span>Complemento:</span>
<p>{{ $escola->endereco['details'] }} </p>
@endif
</td>
<td>
<span>Cep:</span>
<p>{{ $escola->endereco['zip'] }}</p>
@if (isset($escola->endereco))
<span>Cep:</span>
<p>{{ $escola->endereco['zip'] }}</p>
@endif
</td>
<td>

View File

@@ -5,9 +5,9 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="{{asset('assets/images/logo3.png')}}" type="image/x-icon">
<link rel="shortcut icon" href="{{asset('assets/images/favicon.jpg')}}" type="image/x-icon">
<title>{{ env('APP_NAME') }} - @yield('title')</title>
<link rel="icon" href="{{ asset('assets/images/logo3.png') }}" type="image/x-icon">
<link rel="shortcut icon" href="{{ asset('assets/images/favicon.jpg') }}" type="image/x-icon">
<title>@yield('title') - {{env('APP_NAME_SHORT')}}</title>
<!-- Google font-->
<link rel="preconnect" href="https://fonts.gstatic.com">
<link
@@ -20,43 +20,35 @@
href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap"
rel="stylesheet">
<!-- Font Awesome-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/fontawesome.css')}}">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/fontawesome.css') }}">
<!-- ico-font-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/icofont.css')}}">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/icofont.css') }}">
<!-- Themify icon-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/themify.css')}}">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/themify.css') }}">
<!-- Flag icon-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/flag-icon.css')}}">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/flag-icon.css') }}">
<!-- Feather icon-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/feather-icon.css')}}">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/feather-icon.css') }}">
<!-- Plugins css start-->
<!-- Plugins css Ends-->
<!-- Bootstrap css-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/bootstrap.css')}}">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/bootstrap.css') }}">
<!-- App css-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/style.css')}}">
<link id="color" rel="stylesheet" href="{{ asset('assets/css/color-1.css')}}" media="screen">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/style.css') }}">
<link id="color" rel="stylesheet" href="{{ asset('assets/css/color-1.css') }}" media="screen">
<!-- Responsive css-->
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/responsive.css')}}">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/responsive.css') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
<link rel="stylesheet" type="text/css" href="{{ asset('assets/css/dropzone.css') }}">
</head>
<body>
@php
use App\Models\escolas;
use App\Models\turmas;
if (Auth::user()->id_escola == 0) {
$escolas = escolas::all();
} else {
$escolas = escolas::where('id', Auth::user()->id_escola)->get();
}
use App\Models\Escola;
use App\Models\Turma;
if (Auth::user()->id_escola == 0) {
$turmas = turmas::all();
} else {
$turmas = turmas::where('id_escola', Auth::user()->id_escola)->get();
}
$escolas = Escola::all();
$turmas = Turma::all();
@endphp
<!-- Loader starts-->
<div class="loader-wrapper">
@@ -89,26 +81,36 @@
<ul class="nav-menus">
<li><a class="text-dark" href="#!" onclick="javascript:toggleFullScreen()"><i
data-feather="maximize"></i></a></li>
<li>
<div class="dropdown-basic">
<div class="dropdown">
<div class="btn-group mb-0">
<button class="dropbtn btn-primary btn-round" type="button">
Novo <span><i class="icofont icofont-arrow-down"></i></span>
</button>
<div class="dropdown-content">
<a href="#" data-bs-toggle="modal" data-bs-target="#modalEscola">Escola</a>
<a href="#" data-bs-toggle="modal" data-bs-target="#modalTurma">Turma</a>
<a href="#" data-bs-toggle="modal" data-bs-target="#modalAluno">Aluno</a>
<a href="#" data-bs-toggle="modal" data-bs-target="#modalEnvio">Envio de
Mensagem</a>
<a href="#" data-bs-toggle="modal" data-bs-target="#modalEvento">Evento</a>
@role('super_admin')
<li>
<div class="dropdown-basic">
<div class="dropdown">
<div class="btn-group mb-0">
<button class="dropbtn btn-primary btn-round" type="button">
Novo <span><i class="icofont icofont-arrow-down"></i></span>
</button>
<div class="dropdown-content">
<a href="#" data-bs-toggle="modal"
data-bs-target="#modalEscola">Escola</a>
<a href="#" data-bs-toggle="modal"
data-bs-target="#modalTurma">Turma</a>
<a href="#" data-bs-toggle="modal"
data-bs-target="#modalAluno">Aluno</a>
<a href="#" data-bs-toggle="modal" data-bs-target="#modalEnvio">Envio
de
Mensagem</a>
<a href="#" data-bs-toggle="modal"
data-bs-target="#modalEvento">Evento</a>
<a href="#" data-bs-toggle="modal"
data-bs-target="#modalUsuario">Usuário</a>
</div>
</div>
</div>
</div>
</div>
</li>
</li>
@endrole
<li class="onhover-dropdown p-0">
<form method="POST" action="{{ route('logout') }}">
@csrf
@@ -130,8 +132,8 @@
<header class="main-nav">
<div class="sidebar-user text-center"><a class="setting-primary" href="javascript:void(0)"><i
data-feather="settings"></i></a><img class="img-90 rounded-circle"
src="{{ asset('assets/images/logo3.png')}}" alt="">
<div class="badge-bottom"><span class="badge badge-primary">Responsável</span></div><a
src="{{ asset('assets/images/logo3.png') }}" alt="">
<div class="badge-bottom"><span class="badge badge-primary">{{ Auth::user()->funcao() }}</span></div><a
href="user-profile.html">
<h6 class="mt-3 f-14 f-w-600">{{ Auth::user()->name }}</h6>
</a>
@@ -142,30 +144,41 @@
<div id="mainnav">
<ul class="nav-menu custom-scrollbar">
<li class="back-btn">
<div class="mobile-back text-end"><span>Back</span><i class="fa fa-angle-right ps-2"
aria-hidden="true"></i></div>
<div class="mobile-back text-end"><span>Back</span><i
class="fa fa-angle-right ps-2" aria-hidden="true"></i></div>
</li>
<li class="sidebar-main-title">
<div>
<h6>Geral </h6>
</div>
</li>
<li class="dropdown"><a class="nav-link menu-title link-nav" href=""><i
<li class="dropdown"><a class="nav-link menu-title link-nav"
href="{{ route('dashboard') }}"><i
data-feather="git-pull-request"></i><span>Dashboard</span></a></li>
<li class="sidebar-main-title">
<div>
<h6>Administração </h6>
</div>
</li>
<li class="dropdown"><a class="nav-link menu-title link-nav"
href="{{ route('escolas') }}"><i
data-feather="award"></i><span>Escolas</span></a></li>
<li class="dropdown"><a class="nav-link menu-title link-nav"
href="{{ route('turmas') }}"><i data-feather="award"></i><span>Turmas</span></a>
</li>
<li class="dropdown"><a class="nav-link menu-title link-nav"
href="{{ route('alunos') }}"><i data-feather="award"></i><span>Alunos</span></a>
</li>
@role(['admin', 'super_admin'])
<li class="sidebar-main-title">
<div>
<h6>Administração </h6>
</div>
</li>
<li class="dropdown"><a class="nav-link menu-title link-nav"
href="{{ route('escolas') }}"><i
data-feather="award"></i><span>Escolas</span></a></li>
<li class="dropdown"><a class="nav-link menu-title link-nav"
href="{{ route('turmas') }}"><i
data-feather="award"></i><span>Turmas</span></a>
</li>
<li class="dropdown"><a class="nav-link menu-title link-nav"
href="{{ route('alunos') }}"><i
data-feather="award"></i><span>Alunos</span></a>
</li>
<li class="dropdown"><a class="nav-link menu-title link-nav"
href="{{ route('usuarios') }}"><i
data-feather="award"></i><span>Usuários</span></a>
</li>
@endrole
</ul>
</div>
<div class="right-arrow" id="right-arrow"><i data-feather="arrow-right"></i></div>
@@ -213,6 +226,8 @@
</footer>
</div>
</div>
<!-- Modal Escola -->
<div class="modal fade" id="modalEscola" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
@@ -234,7 +249,8 @@
<div class="col-8">
<div class="mb-3">
<label class="form-label" for="razao_social">Razão Social</label>
<input class="form-control" id="razao_social" name="razaosocial" type="text">
<input class="form-control" id="razao_social" name="razaosocial"
type="text">
</div>
</div>
</div>
@@ -295,6 +311,7 @@
</div>
</div>
<!-- Modal Turma -->
<div class="modal fade" id="modalTurma" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
@@ -322,7 +339,8 @@
<div class="col">
<div class="mb-3">
<label class="form-label" for="turma_nome">Nome</label>
<input class="form-control" id="turma_nome" name="turma_nome" type="text">
<input class="form-control" id="turma_nome" name="turma_nome"
type="text">
</div>
</div>
<div class="col">
@@ -335,7 +353,8 @@
<div class="col">
<div class="mb-3">
<label class="form-label" for="id_whatsapp">Whatsap Id</label>
<input class="form-control" id="id_whatsapp" name="id_whatsapp" type="text">
<input class="form-control" id="id_whatsapp" name="id_whatsapp"
type="text">
</div>
</div>
</div>
@@ -349,21 +368,241 @@
</div>
</div>
</div>
{{-- MODAL NOVO ALUNO --}}
<div class="modal fade" id="modalAluno" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Novo Aluno</h4>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- formulário -->
</div>
<form class="form theme-form" action="{{ route('alunos.add') }}" method="POST">
@csrf
<div class="modal-header">
<h4 class="modal-title">Novo Aluno</h4>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row">
{{-- Escola --}}
<div class="col-12">
<div class="mb-3">
<label class="form-label">Escola</label>
<select class="form-select" name="id_escola" id="id_escola" required>
<option value="">Selecione...</option>
@foreach ($escolas as $escola)
<option value="{{ $escola->id }}" @selected(auth()->user()?->current_escola_id == $escola->id)>
{{ $escola->nome }}
</option>
@endforeach
</select>
</div>
</div>
{{-- Turma --}}
<div class="col-12">
<div class="mb-3">
<label class="form-label">Turma</label>
<select class="form-select digits" name="id_turma" id="id_turma" required
disabled>
<option value="">Selecione uma escola primeiro...</option>
</select>
</div>
</div>
{{-- Nome --}}
<div class="col-12 col-md-8">
<div class="mb-3">
<label class="form-label">Nome do aluno</label>
<input type="text" name="nome" class="form-control" required>
</div>
</div>
{{-- CPF --}}
<div class="col-12 col-md-4">
<div class="mb-3">
<label class="form-label">CPF</label>
<input type="text" name="cpf" class="form-control">
</div>
</div>
{{-- Data de nascimento --}}
<div class="col-12 col-md-6">
<div class="mb-3">
<label class="form-label">Data de nascimento *</label>
<input type="date" name="data_nascimento" class="form-control" required>
</div>
</div>
{{-- Data de inscrição --}}
<div class="col-12 col-md-6">
<div class="mb-3">
<label class="form-label">Data de inscrição *</label>
<input type="date" name="data_inscricao" class="form-control"
value="{{ now()->format('Y-m-d') }}" required>
</div>
</div>
{{-- Usuário do aluno --}}
<div class="col-12 col-md-6">
<div class="mb-3">
<label class="form-label">Usuário do aluno (opcional)</label>
<select class="form-select" name="user_id">
<option value="">Não vincular</option>
@foreach ($users ?? [] as $u)
<option value="{{ $u->id }}">
{{ $u->name }} ({{ $u->email }})
</option>
@endforeach
</select>
</div>
</div>
{{-- Usuário responsável --}}
<div class="col-12 col-md-6">
<div class="mb-3">
<label class="form-label">Usuário responsável (opcional)</label>
<select class="form-select" name="responsavel_user_id">
<option value="">Não vincular</option>
@foreach ($users ?? [] as $u)
<option value="{{ $u->id }}">
{{ $u->name }} ({{ $u->email }})
</option>
@endforeach
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">
Cancelar
</button>
<button class="btn btn-primary" type="submit">
Salvar Aluno
</button>
</div>
</form>
</div>
</div>
</div>
{{-- JS carregar turmas --}}
<script>
document.addEventListener('DOMContentLoaded', () => {
const modalEl = document.getElementById('modalAluno');
const escolaSelect = document.getElementById('id_escola');
const turmaSelect = document.getElementById('id_turma');
if (!modalEl || !escolaSelect || !turmaSelect) return;
let controller = null; // abort fetch anterior
function setTurmaOptions({
message = null,
turmas = []
} = {}) {
turmaSelect.innerHTML = '';
if (message) {
const opt = document.createElement('option');
opt.value = '';
opt.textContent = message;
turmaSelect.appendChild(opt);
turmaSelect.disabled = true;
return;
}
const opt0 = document.createElement('option');
opt0.value = '';
opt0.textContent = 'Selecione...';
turmaSelect.appendChild(opt0);
turmas.forEach(t => {
const opt = document.createElement('option');
opt.value = t.id;
opt.textContent = t.nome;
turmaSelect.appendChild(opt);
});
turmaSelect.disabled = turmas.length === 0;
if (turmas.length === 0) {
setTurmaOptions({
message: 'Nenhuma turma encontrada para esta escola.'
});
}
}
async function loadTurmas(escolaId) {
if (!escolaId) {
setTurmaOptions({
message: 'Selecione uma escola primeiro...'
});
return;
}
// cancela requisição anterior
if (controller) controller.abort();
controller = new AbortController();
setTurmaOptions({
message: 'Carregando turmas...'
});
try {
const resp = await fetch(`/api/escolas/${escolaId}/turmas`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
signal: controller.signal,
});
// se deu redirect/403/500 etc, não tenta parsear json
if (!resp.ok) {
throw new Error(`HTTP ${resp.status}`);
}
const turmas = await resp.json();
setTurmaOptions({
turmas
});
} catch (err) {
if (err.name === 'AbortError') return; // troca rápida de escola
console.error('Erro ao carregar turmas:', err);
setTurmaOptions({
message: 'Erro ao carregar turmas.'
});
}
}
// 1) carrega sempre ao abrir o modal (garante estabilidade)
modalEl.addEventListener('shown.bs.modal', () => {
loadTurmas(escolaSelect.value);
});
// 2) recarrega ao trocar escola
escolaSelect.addEventListener('change', () => {
loadTurmas(escolaSelect.value);
});
// 3) opcional: ao fechar modal, reseta turmas (evita “ficar antigo”)
modalEl.addEventListener('hidden.bs.modal', () => {
setTurmaOptions({
message: 'Selecione uma escola primeiro...'
});
});
});
</script>
<div class="modal fade" id="modalEnvio" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
@@ -454,21 +693,267 @@
</div>
</div>
<div class="modal fade" id="modalUsuario" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Novo Usuário</h4>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{{ route('usuarios.add') }}" method="POST">
@csrf
<div class="mb-3">
<label class="form-label">Nome</label>
<input class="form-control" name="name" required>
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input class="form-control" type="email" name="email" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Senha</label>
<input class="form-control" type="password" name="password" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Confirmar senha</label>
<input class="form-control" type="password" name="password_confirmation" required>
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="1" id="is_super_admin" name="is_super_admin">
<label class="form-check-label" for="is_super_admin">Super Admin</label>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Escola</label>
<select class="form-select" name="escola_id" id="escola_id_user">
<option value="">Selecione...</option>
@foreach($escolas as $escola)
<option value="{{ $escola->id }}">{{ $escola->nome }}</option>
@endforeach
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Cargo</label>
<select class="form-select" name="role_name" id="role_name" disabled>
<option value="">Selecione uma escola primeiro...</option>
</select>
</div>
</div>
{{-- ALUNO: vínculo 1 aluno --}}
<div class="mb-3 d-none" id="box_aluno_unico">
<label class="form-label" id="label_aluno_unico">Qual aluno este usuário é?</label>
<select class="form-select" name="aluno_id" id="aluno_id" disabled>
<option value="">Selecione uma escola primeiro...</option>
</select>
</div>
{{-- RESPONSÁVEL: vínculo múltiplos alunos --}}
<div class="mb-3 d-none" id="box_alunos_responsavel">
<label class="form-label">Quais alunos este responsável representa?</label>
<select class="form-select" name="aluno_ids[]" id="aluno_ids" multiple disabled>
<option value="">Selecione uma escola primeiro...</option>
</select>
<small class="text-muted">
Segure <b>Ctrl</b> (Windows) ou <b>Cmd</b> (Mac) para selecionar mais de um.
</small>
</div>
<button class="btn btn-primary" type="submit">Criar usuário</button>
</form>
<script>
document.addEventListener('DOMContentLoaded', () => {
const escolaSelect = document.getElementById('escola_id_user');
const roleSelect = document.getElementById('role_name');
const superCheck = document.getElementById('is_super_admin');
const boxAlunoUnico = document.getElementById('box_aluno_unico');
const alunoSelect = document.getElementById('aluno_id');
const labelAlunoUnico = document.getElementById('label_aluno_unico');
const boxResp = document.getElementById('box_alunos_responsavel');
const alunosMulti = document.getElementById('aluno_ids');
const roleLabel = {
'coordenacao': 'Coordenação',
'secretaria': 'Secretaria',
'responsavel': 'Responsável',
'aluno': 'Aluno',
'representante_de_turma': 'Representante de Turma',
};
function resetSelect(select, msg) {
select.innerHTML = `<option value="">${msg}</option>`;
select.disabled = true;
}
function hideAllBindings() {
boxAlunoUnico.classList.add('d-none');
boxResp.classList.add('d-none');
alunoSelect.value = '';
alunosMulti.value = '';
resetSelect(alunoSelect, 'Selecione uma escola primeiro...');
resetSelect(alunosMulti, 'Selecione uma escola primeiro...');
}
async function loadRoles(escolaId) {
if (!escolaId) {
resetSelect(roleSelect, 'Selecione uma escola primeiro...');
return;
}
resetSelect(roleSelect, 'Carregando cargos...');
const resp = await fetch(`/api/escolas/${escolaId}/roles`, {
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
});
if (!resp.ok) {
resetSelect(roleSelect, 'Erro ao carregar cargos');
return;
}
const roles = await resp.json();
roleSelect.innerHTML = `<option value="">Selecione...</option>`;
roles.forEach(r => {
const opt = document.createElement('option');
opt.value = r.name;
opt.textContent = roleLabel[r.name] ?? r.name;
roleSelect.appendChild(opt);
});
roleSelect.disabled = false;
}
async function loadAlunos(escolaId, targetSelect, multiple = false) {
if (!escolaId) {
resetSelect(targetSelect, 'Selecione uma escola primeiro...');
return;
}
resetSelect(targetSelect, 'Carregando alunos...');
const resp = await fetch(`/api/escolas/${escolaId}/alunos`, {
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
});
if (!resp.ok) {
resetSelect(targetSelect, 'Erro ao carregar alunos');
return;
}
const alunos = await resp.json();
targetSelect.innerHTML = multiple
? '' // multi select não precisa placeholder fixo
: `<option value="">Selecione...</option>`;
alunos.forEach(a => {
const opt = document.createElement('option');
opt.value = a.id;
opt.textContent = a.nome;
targetSelect.appendChild(opt);
});
targetSelect.disabled = alunos.length === 0;
}
function toggleSuperAdmin(isSuper) {
if (isSuper) {
escolaSelect.value = '';
roleSelect.value = '';
resetSelect(roleSelect, 'Super Admin não usa cargo');
escolaSelect.disabled = true;
roleSelect.disabled = true;
hideAllBindings();
alunoSelect.disabled = true;
alunosMulti.disabled = true;
} else {
escolaSelect.disabled = false;
resetSelect(roleSelect, 'Selecione uma escola primeiro...');
roleSelect.disabled = true;
hideAllBindings();
}
}
superCheck.addEventListener('change', () => toggleSuperAdmin(superCheck.checked));
escolaSelect.addEventListener('change', async () => {
await loadRoles(escolaSelect.value);
hideAllBindings();
});
roleSelect.addEventListener('change', async () => {
const role = roleSelect.value;
// sempre reseta
hideAllBindings();
if (role === 'responsavel') {
boxResp.classList.remove('d-none');
await loadAlunos(escolaSelect.value, alunosMulti, true);
return;
}
if (role === 'aluno') {
labelAlunoUnico.textContent = 'Qual aluno este usuário é?';
boxAlunoUnico.classList.remove('d-none');
await loadAlunos(escolaSelect.value, alunoSelect, false);
return;
}
// (opcional) se representante também tiver vínculo com 1 aluno, habilite:
// if (role === 'representante_de_turma') { ... }
});
// estado inicial
toggleSuperAdmin(superCheck.checked);
});
</script>
</div>
</div>
</div>
</div>
<!-- latest jquery-->
<script src="{{ asset('assets/js/jquery-3.5.1.min.js')}}"></script>
<script src="{{ asset('assets/js/jquery-3.5.1.min.js') }}"></script>
<!-- feather icon js-->
<script src="{{ asset('assets/js/icons/feather-icon/feather.min.js')}}"></script>
<script src="{{ asset('assets/js/icons/feather-icon/feather-icon.js')}}"></script>
<script src="{{ asset('assets/js/icons/feather-icon/feather.min.js') }}"></script>
<script src="{{ asset('assets/js/icons/feather-icon/feather-icon.js') }}"></script>
<!-- Sidebar jquery-->
<script src="{{ asset('assets/js/sidebar-menu.js')}}"></script>
<script src="{{ asset('assets/js/config.js')}}"></script>
<script src="{{ asset('assets/js/sidebar-menu.js') }}"></script>
<script src="{{ asset('assets/js/config.js') }}"></script>
<!-- Bootstrap js-->
<script src="{{ asset('assets/js/bootstrap/popper.min.js')}}"></script>
<script src="{{ asset('assets/js/bootstrap/bootstrap.min.js')}}"></script>
<script src="{{ asset('assets/js/bootstrap/popper.min.js') }}"></script>
<script src="{{ asset('assets/js/bootstrap/bootstrap.min.js') }}"></script>
<!-- Plugins JS start-->
<!-- Plugins JS Ends-->
<!-- Theme js-->
<script src="{{ asset('assets/js/script.js')}}"></script>
<script src="{{ asset('assets/js/script.js') }}"></script>
<!-- login js-->
<!-- Plugin used-->
<script src="{{ asset('assets/js/icons/icons-notify.js') }}"></script>
@@ -486,7 +971,7 @@
let ultimoCNPJ = '';
let timeout = null;
document.getElementById('cnpj').addEventListener('input', function () {
document.getElementById('cnpj').addEventListener('input', function() {
let cnpj = this.value.replace(/\D/g, '');
clearTimeout(timeout);
@@ -550,7 +1035,7 @@
});
</script>
<script>
document.getElementById('tipo_envio').addEventListener('change', function () {
document.getElementById('tipo_envio').addEventListener('change', function() {
const campoImagem = document.getElementById('campoImagem');
if (this.value === 'imagem') {
@@ -561,7 +1046,6 @@
});
</script>
</body>
</html>
</html>

View File

@@ -0,0 +1,40 @@
@extends('theme.default')
@section('title', 'Dashboard')
@section('title.page', 'Dashboard')
@section('title.page1', 'Escola da árvore')
@section('title.page2', 'Home')
@section('title.page3', 'Dashboard')
@section('content')
<div class="container-fluid user-card">
<div class="row">
@foreach ($usuarios as $usuario)
<div class="col-md-6 col-lg-6 col-xl-4 box-col-6">
<div class="card custom-card">
<div class="card-header"><img class="img-fluid" src="https://bucket.cciti.com.br/cae/images/banner_user.png" alt=""></div>
<div class="card-profile"><img class="rounded-circle" src="https://bucket.cciti.com.br/cae/images/logo3.png" alt=""></div>
<div class="text-center profile-details"><a href="user-profile.html">
<h4>{{ $usuario->name }}</h4>
</a>
<h6>{{$usuario->funcao()}}</h6>
</div>
<div class="card-footer row">
<div class="col-4 col-sm-4">
<h6>Follower</h6>
<h3 class="counter">9564</h3>
</div>
<div class="col-4 col-sm-4">
<h6>Following</h6>
<h3><span class="counter">49</span>K</h3>
</div>
<div class="col-4 col-sm-4">
<h6>Total Post</h6>
<h3><span class="counter">96</span>M</h3>
</div>
</div>
</div>
</div>
@endforeach
</div>
</div>
@endsection

View File

@@ -5,6 +5,7 @@ use App\Http\Controllers\EnvioWhatsap;
use App\Http\Controllers\EscolasController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\TurmasController;
use App\Http\Controllers\UsersController;
use Illuminate\Support\Facades\Route;
@@ -26,11 +27,18 @@ Route::middleware('auth')->group(function () {
})->name('dashboard');
Route::get('/escolas', [EscolasController::class, 'index'])->name('escolas');
Route::post('/escolas', [EscolasController::class, 'createOrUpdate'])->name('escola.novo');
Route::get('/turmas', [TurmasController::class, 'index'])->name('turmas');
Route::get('/turmas', [TurmasController::class, 'index'])->name('turmas')->middleware('role:admin|super_admin');
Route::get('/turmas/{id}', [TurmasController::class, 'detalhes'])->name('turmas.detalhes');
Route::post('/turmas', [TurmasController::class, 'createOrUpdate'])->name('turma.novo');
Route::get('/alunos', [AlunosController::class, 'index'])->name('alunos');
Route::get('/alunos', [AlunosController::class, 'index'])->name('alunos')->middleware('permission:alunos.ver_todos|alunos.ver_meus');
Route::post('/alunos', [AlunosController::class, 'updateOrCreate'])->name('alunos.add')->middleware('role:super_admin');
Route::post('/envio-wpp', [EnvioWhatsap::class, 'envio'])->name('envio.wpp');
Route::get('/usuarios', [UsersController::class, 'index'])->name('usuarios')->middleware('role:super_admin');
Route::post('/usuarios', [UsersController::class, 'store'])->name('usuarios.add')->middleware('role:super_admin');
Route::get('/api/escolas/{escola}/turmas', [TurmasController::class, 'byEscola'])->name('api.escolas.turmas');
Route::get('/api/escolas/{escola}/roles', [UsersController::class, 'rolesByEscola'])->name('api.escolas.roles');
Route::get('/api/escolas/{escola}/alunos', [UsersController::class, 'alunosByEscola'])->name('api.escolas.alunos');
});