save
This commit is contained in:
Generated
+1
@@ -0,0 +1 @@
|
|||||||
|
\\192.168.1.50\cesizen\
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
vendor
|
||||||
|
public/build
|
||||||
|
.git
|
||||||
|
.env
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
FROM php:8.3-fpm
|
||||||
|
|
||||||
|
# 1. Installer les dépendances système
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
libpng-dev \
|
||||||
|
libonig-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
libicu-dev \
|
||||||
|
libzip-dev \
|
||||||
|
zip \
|
||||||
|
unzip \
|
||||||
|
gnupg
|
||||||
|
|
||||||
|
# 2. Installer Node.js (pour Vite)
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||||
|
apt-get install -y nodejs
|
||||||
|
|
||||||
|
# 3. Nettoyer le cache apt
|
||||||
|
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 4. Installer les extensions PHP
|
||||||
|
# On ajoute 'zip' et on configure 'intl' correctement
|
||||||
|
RUN docker-php-ext-configure intl && \
|
||||||
|
docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd intl zip
|
||||||
|
|
||||||
|
# 5. Installer Composer
|
||||||
|
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
# 6. Définir le répertoire de travail
|
||||||
|
WORKDIR /var/www
|
||||||
|
|
||||||
|
# 7. Copier le projet
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 8. Installer les dépendances PHP et Node
|
||||||
|
RUN composer install --no-interaction --optimize-autoloader --no-dev
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# 9. Forcer les permissions sur les binaires node_modules
|
||||||
|
RUN chmod -R +x node_modules/.bin/
|
||||||
|
|
||||||
|
# 10. Lancer le build de Vite
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# 11. Fixer les permissions pour Laravel
|
||||||
|
RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
CMD ["php-fpm"]
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\InformationResource\Pages;
|
||||||
|
use App\Models\Information;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class InformationResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Information::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-information-circle';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Informations';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'Information';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('title')
|
||||||
|
->label('Titre')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\TextInput::make('category')
|
||||||
|
->label('Catégorie')
|
||||||
|
->required()
|
||||||
|
->maxLength(255)
|
||||||
|
->default('Général'),
|
||||||
|
Forms\Components\RichEditor::make('content')
|
||||||
|
->label('Contenu')
|
||||||
|
->required()
|
||||||
|
->columnSpanFull(),
|
||||||
|
Forms\Components\FileUpload::make('image_url')
|
||||||
|
->label('Image')
|
||||||
|
->image()
|
||||||
|
->directory('informations'),
|
||||||
|
Forms\Components\Toggle::make('is_published')
|
||||||
|
->label('Publié')
|
||||||
|
->default(true)
|
||||||
|
->required(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('title')
|
||||||
|
->label('Titre')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('category')
|
||||||
|
->label('Catégorie')
|
||||||
|
->badge()
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\IconColumn::make('is_published')
|
||||||
|
->label('Publié')
|
||||||
|
->boolean(),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->label('Créé le')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
Tables\Filters\TernaryFilter::make('is_published')
|
||||||
|
->label('Statut de publication')
|
||||||
|
->boolean()
|
||||||
|
->trueLabel('Publiés')
|
||||||
|
->falseLabel('Brouillons')
|
||||||
|
->native(false),
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
Tables\Actions\DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListInformation::route('/'),
|
||||||
|
'create' => Pages\CreateInformation::route('/create'),
|
||||||
|
'edit' => Pages\EditInformation::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\InformationResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\InformationResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateInformation extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = InformationResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\InformationResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\InformationResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditInformation extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = InformationResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\InformationResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\InformationResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListInformation extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = InformationResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
|
||||||
|
use App\Models\RelaxationActivity;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class RelaxationActivityResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = RelaxationActivity::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-sparkles';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Activités de détente';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'Activité de détente';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('title')
|
||||||
|
->label('Titre')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\Select::make('type')
|
||||||
|
->options([
|
||||||
|
'video' => 'Vidéo',
|
||||||
|
'audio' => 'Audio',
|
||||||
|
'article' => 'Article',
|
||||||
|
'exercice' => 'Exercice',
|
||||||
|
])
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('duration')
|
||||||
|
->label('Durée (min)')
|
||||||
|
->numeric(),
|
||||||
|
Forms\Components\TextInput::make('url')
|
||||||
|
->label('Lien / URL')
|
||||||
|
->url()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\Textarea::make('description')
|
||||||
|
->columnSpanFull(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('title')
|
||||||
|
->label('Titre')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('type')
|
||||||
|
->badge(),
|
||||||
|
Tables\Columns\TextColumn::make('duration')
|
||||||
|
->label('Durée')
|
||||||
|
->suffix(' min'),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
Tables\Filters\SelectFilter::make('type')
|
||||||
|
->options([
|
||||||
|
'video' => 'Vidéo',
|
||||||
|
'audio' => 'Audio',
|
||||||
|
'article' => 'Article',
|
||||||
|
'exercice' => 'Exercice',
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
Tables\Actions\DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListRelaxationActivities::route('/'),
|
||||||
|
'create' => Pages\CreateRelaxationActivity::route('/create'),
|
||||||
|
'edit' => Pages\EditRelaxationActivity::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\RelaxationActivityResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateRelaxationActivity extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = RelaxationActivityResource::class;
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\RelaxationActivityResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditRelaxationActivity extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = RelaxationActivityResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\RelaxationActivityResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListRelaxationActivities extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = RelaxationActivityResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\StressEventResource\Pages;
|
||||||
|
use App\Models\StressEvent;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class StressEventResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = StressEvent::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-bolt';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Événements de stress';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'Événement de stress';
|
||||||
|
|
||||||
|
protected static ?string $pluralModelLabel = 'Événements de stress';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('event_name')
|
||||||
|
->label('Nom de l\'événement')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\TextInput::make('points')
|
||||||
|
->label('Points de stress')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(0)
|
||||||
|
->maxValue(100),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('event_name')
|
||||||
|
->label('Événement')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('points')
|
||||||
|
->label('Points')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
|
->label('Dernière modification')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
Tables\Actions\DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListStressEvents::route('/'),
|
||||||
|
'create' => Pages\CreateStressEvent::route('/create'),
|
||||||
|
'edit' => Pages\EditStressEvent::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\StressEventResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\StressEventResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateStressEvent extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = StressEventResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\StressEventResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\StressEventResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditStressEvent extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = StressEventResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\StressEventResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\StressEventResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListStressEvents extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = StressEventResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\UserResource\Pages;
|
||||||
|
use App\Models\User;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class UserResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = User::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-users';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Utilisateurs';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'Utilisateur';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('name')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\TextInput::make('email')
|
||||||
|
->email()
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\Select::make('id_role')
|
||||||
|
->relationship('role', 'libelle')
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('password')
|
||||||
|
->password()
|
||||||
|
->dehydrated(fn ($state) => filled($state))
|
||||||
|
->required(fn (string $context): bool => $context === 'create'),
|
||||||
|
Forms\Components\Toggle::make('is_active')
|
||||||
|
->label('Compte actif')
|
||||||
|
->default(true)
|
||||||
|
->required(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('email')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('role.libelle')
|
||||||
|
->label('Rôle')
|
||||||
|
->badge()
|
||||||
|
->color(fn (string $state): string => match ($state) {
|
||||||
|
'Admin' => 'danger',
|
||||||
|
'Utilisateur' => 'info',
|
||||||
|
default => 'gray',
|
||||||
|
}),
|
||||||
|
Tables\Columns\ToggleColumn::make('is_active')
|
||||||
|
->label('Actif'),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
Tables\Filters\TernaryFilter::make('is_active')
|
||||||
|
->label('Statut')
|
||||||
|
->boolean()
|
||||||
|
->trueLabel('Utilisateurs actifs')
|
||||||
|
->falseLabel('Utilisateurs désactivés')
|
||||||
|
->native(false),
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListUsers::route('/'),
|
||||||
|
'create' => Pages\CreateUser::route('/create'),
|
||||||
|
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\UserResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateUser extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = UserResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\UserResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditUser extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = UserResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\UserResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListUsers extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = UserResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Information;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class InformationController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$informations = Information::where('is_published', true)
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $informations
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$information = Information::where('is_published', true)->find($id);
|
||||||
|
|
||||||
|
if (!$information) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Information non trouvée'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $information
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Web;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Information;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class InformationController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$informations = Information::where('is_published', true)
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->paginate(6);
|
||||||
|
|
||||||
|
return view('informations.index', compact('informations'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$information = Information::where('is_published', true)->findOrFail($id);
|
||||||
|
|
||||||
|
return view('informations.show', compact('information'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Information extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'information';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'content',
|
||||||
|
'category',
|
||||||
|
'image_url',
|
||||||
|
'is_published',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'is_published' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers\Filament;
|
||||||
|
|
||||||
|
use Filament\Http\Middleware\Authenticate;
|
||||||
|
use Filament\Http\Middleware\AuthenticateSession;
|
||||||
|
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||||
|
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||||
|
use Filament\Pages;
|
||||||
|
use Filament\Panel;
|
||||||
|
use Filament\PanelProvider;
|
||||||
|
use Filament\Support\Colors\Color;
|
||||||
|
use Filament\Widgets;
|
||||||
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
|
|
||||||
|
class AdminPanelProvider extends PanelProvider
|
||||||
|
{
|
||||||
|
public function panel(Panel $panel): Panel
|
||||||
|
{
|
||||||
|
return $panel
|
||||||
|
->id('admin')
|
||||||
|
->path('admin')
|
||||||
|
->brandName('CESIZen')
|
||||||
|
->homeUrl('/')
|
||||||
|
->colors([
|
||||||
|
'primary' => Color::Amber,
|
||||||
|
])
|
||||||
|
->discoverResources(in: app_path('Filament/Admin/Resources'), for: 'App\\Filament\\Admin\\Resources')
|
||||||
|
->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages')
|
||||||
|
->pages([
|
||||||
|
Pages\Dashboard::class,
|
||||||
|
])
|
||||||
|
->discoverWidgets(in: app_path('Filament/Admin/Widgets'), for: 'App\\Filament\\Admin\\Widgets')
|
||||||
|
->widgets([
|
||||||
|
Widgets\AccountWidget::class,
|
||||||
|
Widgets\FilamentInfoWidget::class,
|
||||||
|
])
|
||||||
|
->middleware([
|
||||||
|
EncryptCookies::class,
|
||||||
|
AddQueuedCookiesToResponse::class,
|
||||||
|
StartSession::class,
|
||||||
|
AuthenticateSession::class,
|
||||||
|
ShareErrorsFromSession::class,
|
||||||
|
VerifyCsrfToken::class,
|
||||||
|
SubstituteBindings::class,
|
||||||
|
DisableBladeIconComponents::class,
|
||||||
|
DispatchServingFilamentEvent::class,
|
||||||
|
])
|
||||||
|
->authMiddleware([
|
||||||
|
Authenticate::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
class StressCalculator
|
||||||
|
{
|
||||||
|
public const LEVEL_HIGH = 'Élevé';
|
||||||
|
public const LEVEL_MODERATE = 'Modéré';
|
||||||
|
public const LEVEL_LOW = 'Faible';
|
||||||
|
|
||||||
|
public function calculateScore(array $points): int
|
||||||
|
{
|
||||||
|
return array_sum($points);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function determineLevel(int $score): string
|
||||||
|
{
|
||||||
|
if ($score >= 300) {
|
||||||
|
return self::LEVEL_HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($score >= 150) {
|
||||||
|
return self::LEVEL_MODERATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::LEVEL_LOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->morphs('tokenable');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('token', 64)->unique();
|
||||||
|
$table->text('abilities')->nullable();
|
||||||
|
$table->timestamp('last_used_at')->nullable();
|
||||||
|
$table->timestamp('expires_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('personal_access_tokens');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?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('cache', function (Blueprint $table) {
|
||||||
|
$table->string('key')->primary();
|
||||||
|
$table->mediumText('value');
|
||||||
|
$table->bigInteger('expiration')->index();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('cache_locks', function (Blueprint $table) {
|
||||||
|
$table->string('key')->primary();
|
||||||
|
$table->string('owner');
|
||||||
|
$table->bigInteger('expiration')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('cache');
|
||||||
|
Schema::dropIfExists('cache_locks');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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::create('sessions', function (Blueprint $table) {
|
||||||
|
$table->string('id')->primary();
|
||||||
|
$table->foreignId('user_id')->nullable()->index();
|
||||||
|
$table->string('ip_address', 45)->nullable();
|
||||||
|
$table->text('user_agent')->nullable();
|
||||||
|
$table->longText('payload');
|
||||||
|
$table->integer('last_activity')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('sessions');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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('users', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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('users', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_active')->default(true)->after('id_role');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_active');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('information', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('title');
|
||||||
|
$table->text('content');
|
||||||
|
$table->string('category')->default('Général'); // ex: Prévention, Stress, Sommeil
|
||||||
|
$table->string('image_url')->nullable();
|
||||||
|
$table->boolean('is_published')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('information');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
services:
|
||||||
|
# PHP Application
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: cesizen-app
|
||||||
|
restart: unless-stopped
|
||||||
|
tty: true
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www
|
||||||
|
- /var/www/node_modules
|
||||||
|
- ./storage:/var/www/storage
|
||||||
|
networks:
|
||||||
|
- cesizen-network
|
||||||
|
|
||||||
|
# Nginx Service
|
||||||
|
webserver:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: cesizen-webserver
|
||||||
|
restart: unless-stopped
|
||||||
|
tty: true
|
||||||
|
ports:
|
||||||
|
- "8090:80" # WEB
|
||||||
|
- "8091:81" # API
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www
|
||||||
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
networks:
|
||||||
|
- cesizen-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
cesizen-network:
|
||||||
|
driver: bridge
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Configuration pour le Web (Port 80 interne, mappé sur 8090)
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _; # Accepte tout car NPM a déjà filtré le domaine
|
||||||
|
index index.php index.html;
|
||||||
|
root /var/www/public;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
try_files $uri =404;
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass app:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
|
||||||
|
# Optimisation pour le proxy
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configuration pour l'API (Port 81 interne, mappé sur 8091)
|
||||||
|
server {
|
||||||
|
listen 81;
|
||||||
|
server_name _; # Accepte tout car NPM a déjà filtré le domaine
|
||||||
|
index index.php index.html;
|
||||||
|
root /var/www/public;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
try_files $uri =404;
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass app:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
|
||||||
|
# Optimisation pour le proxy
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
.fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
function d(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){this.isLoading=!0;let t=await this.$wire.getGroupedSelectableTableRecordKeys(e);this.areRecordsSelected(this.getRecordsInGroupOnPage(e))?this.deselectRecords(t):this.selectRecords(t),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let l=s.indexOf(this.lastChecked),r=s.indexOf(t),o=[l,r].sort((c,n)=>c-n),i=[];for(let c=o[0];c<=o[1];c++)s[c].checked=t.checked,i.push(s[c].value);t.checked?this.selectRecords(i):this.deselectRecords(i)}this.lastChecked=t}}}export{d as default};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,46 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-3xl font-bold text-indigo-900">Informations & Santé Mentale</h1>
|
||||||
|
<p class="text-gray-600">Découvrez nos conseils et articles pour mieux gérer votre quotidien.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
@foreach($informations as $info)
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-shadow">
|
||||||
|
@if($info->image_url)
|
||||||
|
<img src="{{ asset('storage/' . $info->image_url) }}" alt="{{ $info->title }}" class="w-full h-48 object-cover">
|
||||||
|
@else
|
||||||
|
<div class="w-full h-48 bg-indigo-50 flex items-center justify-center">
|
||||||
|
<svg class="w-12 h-12 text-indigo-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="p-6">
|
||||||
|
<span class="inline-block px-3 py-1 bg-indigo-100 text-indigo-700 text-xs font-semibold rounded-full mb-3">
|
||||||
|
{{ $info->category }}
|
||||||
|
</span>
|
||||||
|
<h2 class="text-xl font-bold text-gray-900 mb-2">{{ $info->title }}</h2>
|
||||||
|
<p class="text-gray-600 text-sm mb-4 line-clamp-3">
|
||||||
|
{!! strip_tags($info->content) !!}
|
||||||
|
</p>
|
||||||
|
<a href="{{ route('informations.show', $info->id) }}" class="text-indigo-600 font-semibold hover:text-indigo-800 flex items-center">
|
||||||
|
Lire la suite
|
||||||
|
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-12">
|
||||||
|
{{ $informations->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<div class="max-w-4xl mx-auto">
|
||||||
|
<a href="{{ route('informations.index') }}" class="inline-flex items-center text-indigo-600 hover:text-indigo-800 mb-6">
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||||
|
</svg>
|
||||||
|
Retour aux articles
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-3xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
@if($information->image_url)
|
||||||
|
<img src="{{ asset('storage/' . $information->image_url) }}" alt="{{ $information->title }}" class="w-full h-96 object-cover">
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="p-8 md:p-12">
|
||||||
|
<span class="inline-block px-4 py-1 bg-indigo-100 text-indigo-700 text-sm font-semibold rounded-full mb-6">
|
||||||
|
{{ $information->category }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<h1 class="text-4xl font-bold text-gray-900 mb-6 leading-tight">
|
||||||
|
{{ $information->title }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="flex items-center text-gray-500 mb-8 text-sm">
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||||
|
</svg>
|
||||||
|
Publié le {{ $information->created_at->format('d/m/Y') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="prose prose-indigo max-w-none text-gray-700 leading-relaxed text-lg">
|
||||||
|
{!! $information->content !!}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-12 p-6 bg-gray-50 rounded-2xl border border-gray-100">
|
||||||
|
<div class="flex items-start">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<svg class="h-6 w-6 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-sm text-gray-600 italic">
|
||||||
|
Information de santé publique - CESIZen. Ces contenus sont rédigés à but informatif et ne sauraient remplacer une consultation professionnelle.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Role;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AdminAccessTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_guest_is_redirected_to_login(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/admin');
|
||||||
|
|
||||||
|
$response->assertRedirect('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_non_admin_user_cannot_access_admin_panel(): void
|
||||||
|
{
|
||||||
|
$role = Role::create(['libelle' => 'Utilisateur']);
|
||||||
|
$user = User::factory()->create(['id_role' => $role->id]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get('/admin');
|
||||||
|
|
||||||
|
$response->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_user_can_access_admin_panel(): void
|
||||||
|
{
|
||||||
|
$role = Role::create(['libelle' => 'Admin']);
|
||||||
|
$user = User::factory()->create(['id_role' => $role->id]);
|
||||||
|
|
||||||
|
$response = $this->actingAs($user)->get('/admin');
|
||||||
|
|
||||||
|
$response->assertSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use Tests\TestCase;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\StressEvent;
|
||||||
|
use App\Models\ResultatDiag;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
class DiagnosticTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_diagnostic_score_calculation_and_storage(): void
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware();
|
||||||
|
|
||||||
|
// Créer un utilisateur
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
// Créer des événements de stress
|
||||||
|
$event1 = StressEvent::create(['event_name' => 'Event 1', 'points' => 100]);
|
||||||
|
$event2 = StressEvent::create(['event_name' => 'Event 2', 'points' => 60]);
|
||||||
|
|
||||||
|
// Simuler une requête de diagnostic
|
||||||
|
$response = $this->actingAs($user)
|
||||||
|
->post(route('diagnostics.store'), [
|
||||||
|
'events' => [$event1->id, $event2->id]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Vérifier la redirection et le score en session
|
||||||
|
$response->assertStatus(302);
|
||||||
|
$response->assertSessionHas('score', 160);
|
||||||
|
|
||||||
|
// Vérifier l'enregistrement en base de données
|
||||||
|
$this->assertDatabaseHas('resultat_diags', [
|
||||||
|
'id_user' => $user->id,
|
||||||
|
'score_total' => 160,
|
||||||
|
'niveau_stress' => 'Modéré'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use App\Services\StressCalculator;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class StressCalculatorTest extends TestCase
|
||||||
|
{
|
||||||
|
private StressCalculator $calculator;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->calculator = new StressCalculator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_calculate_score_sums_points_correctly(): void
|
||||||
|
{
|
||||||
|
$points = [100, 73, 50];
|
||||||
|
$this->assertEquals(223, $this->calculator->calculateScore($points));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_calculate_score_returns_zero_for_empty_array(): void
|
||||||
|
{
|
||||||
|
$this->assertEquals(0, $this->calculator->calculateScore([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider stressLevelProvider
|
||||||
|
*/
|
||||||
|
public function test_determine_level_returns_correct_label(int $score, string $expectedLevel): void
|
||||||
|
{
|
||||||
|
$this->assertEquals($expectedLevel, $this->calculator->determineLevel($score));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function stressLevelProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[350, StressCalculator::LEVEL_HIGH],
|
||||||
|
[300, StressCalculator::LEVEL_HIGH],
|
||||||
|
[299, StressCalculator::LEVEL_MODERATE],
|
||||||
|
[150, StressCalculator::LEVEL_MODERATE],
|
||||||
|
[149, StressCalculator::LEVEL_LOW],
|
||||||
|
[0, StressCalculator::LEVEL_LOW],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../services/information_service.dart';
|
||||||
|
|
||||||
|
class InformationProvider with ChangeNotifier {
|
||||||
|
final InformationService _service = InformationService();
|
||||||
|
List<dynamic> _informations = [];
|
||||||
|
bool _isLoading = false;
|
||||||
|
String? _error;
|
||||||
|
|
||||||
|
List<dynamic> get informations => _informations;
|
||||||
|
bool get isLoading => _isLoading;
|
||||||
|
String? get error => _error;
|
||||||
|
|
||||||
|
Future<void> fetchInformations() async {
|
||||||
|
_isLoading = true;
|
||||||
|
_error = null;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
_informations = await _service.getInformations();
|
||||||
|
} catch (e) {
|
||||||
|
_error = e.toString();
|
||||||
|
} finally {
|
||||||
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../providers/information_provider.dart';
|
||||||
|
|
||||||
|
class InfoScreen extends StatefulWidget {
|
||||||
|
const InfoScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InfoScreen> createState() => _InfoScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InfoScreenState extends State<InfoScreen> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
Future.microtask(
|
||||||
|
() => context.read<InformationProvider>().fetchInformations(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Informations Santé', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
body: Consumer<InformationProvider>(
|
||||||
|
builder: (context, provider, child) {
|
||||||
|
if (provider.isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator(color: Color(0xFF000080)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.error != null) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.error_outline, size: 60, color: Colors.red),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text("Erreur : ${provider.error}"),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => provider.fetchInformations(),
|
||||||
|
child: const Text("Réessayer"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.informations.isEmpty) {
|
||||||
|
return const Center(child: Text("Aucune information disponible pour le moment."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
itemCount: provider.informations.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final info = provider.informations[index];
|
||||||
|
return _buildArticleCard(context, info);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildArticleCard(BuildContext context, dynamic info) {
|
||||||
|
// On définit une couleur selon la catégorie ou une couleur par défaut
|
||||||
|
final Color categoryColor = _getCategoryColor(info['category'] ?? 'Général');
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: Colors.grey.shade100),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.03),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => _showArticleDetail(context, info),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: categoryColor.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
info['category'] ?? 'Général',
|
||||||
|
style: TextStyle(color: categoryColor, fontWeight: FontWeight.bold, fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
Text(
|
||||||
|
info['title'] ?? 'Sans titre',
|
||||||
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
_stripHtml(info['content'] ?? '').split('\n').first,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(color: Colors.grey.shade600, fontSize: 14),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Lire la suite",
|
||||||
|
style: TextStyle(color: categoryColor, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Icon(Icons.arrow_forward_ios, size: 14, color: categoryColor),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getCategoryColor(String category) {
|
||||||
|
switch (category.toLowerCase()) {
|
||||||
|
case 'prévention': return Colors.blue;
|
||||||
|
case 'sommeil': return Colors.indigo;
|
||||||
|
case 'stress': return Colors.orange;
|
||||||
|
case 'exercices': return Colors.redAccent;
|
||||||
|
default: return Colors.teal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _stripHtml(String htmlString) {
|
||||||
|
return htmlString.replaceAll(RegExp(r'<[^>]*>| '), ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showArticleDetail(BuildContext context, dynamic info) {
|
||||||
|
final Color categoryColor = _getCategoryColor(info['category'] ?? 'Général');
|
||||||
|
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (context) => Container(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.85,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.fromLTRB(24, 20, 24, 40),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
width: 40,
|
||||||
|
height: 4,
|
||||||
|
decoration: BoxDecoration(color: Colors.grey.shade300, borderRadius: BorderRadius.circular(2)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
(info['category'] ?? 'Général').toUpperCase(),
|
||||||
|
style: TextStyle(color: categoryColor, fontWeight: FontWeight.bold, letterSpacing: 1.2),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
info['title'] ?? 'Sans titre',
|
||||||
|
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
_stripHtml(info['content'] ?? ''),
|
||||||
|
style: const TextStyle(fontSize: 16, height: 1.6, color: Colors.black87),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, color: Colors.grey),
|
||||||
|
SizedBox(width: 15),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"Ces informations sont fournies à titre indicatif et ne remplacent pas un avis médical.",
|
||||||
|
style: TextStyle(color: Colors.grey, fontSize: 13, fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import '../../../core/network/dio_client.dart';
|
||||||
|
|
||||||
|
class InformationService {
|
||||||
|
final Dio _dio = DioClient().dio;
|
||||||
|
|
||||||
|
Future<List<dynamic>> getInformations() async {
|
||||||
|
try {
|
||||||
|
final response = await _dio.get('/informations');
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
// Selon la structure de votre API Laravel
|
||||||
|
return response.data['data'] ?? response.data;
|
||||||
|
} else {
|
||||||
|
throw Exception('Erreur lors du chargement des informations');
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
print('Erreur InformationService: ${e.message}');
|
||||||
|
throw Exception('Erreur réseau lors du chargement des informations');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
-26
@@ -284,18 +284,18 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
child: _isFabExpanded
|
child: _isFabExpanded
|
||||||
? const Row(
|
? const Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
'Espace Santé',
|
'Espace Santé',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -311,16 +311,16 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
|
|
||||||
Widget _buildMenuCard(BuildContext context, String title, IconData icon, Color iconColor, {VoidCallback? onTap, bool isLocked = false}) {
|
Widget _buildMenuCard(BuildContext context, String title, IconData icon, Color iconColor, {VoidCallback? onTap, bool isLocked = false}) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: isLocked
|
onTap: isLocked
|
||||||
? () {
|
? () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Connectez-vous pour accéder à cette fonctionnalité'),
|
content: Text('Connectez-vous pour accéder à cette fonctionnalité'),
|
||||||
backgroundColor: Color(0xFF000080),
|
backgroundColor: Color(0xFF000080),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: onTap,
|
: onTap,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -342,15 +342,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
size: 36,
|
size: 36,
|
||||||
color: isLocked ? Colors.grey.shade300 : iconColor
|
color: isLocked ? Colors.grey.shade300 : iconColor
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: isLocked ? Colors.grey : Colors.black87,
|
color: isLocked ? Colors.grey : Colors.black87,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user