mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-08 05:43:18 +01:00
Compare commits
4 Commits
feature/fi
...
47327aa52b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47327aa52b | ||
|
|
d54296e66a | ||
|
|
95ddbf9ead | ||
|
|
70646a0dd4 |
3
.env.ci
3
.env.ci
@@ -64,3 +64,6 @@ GOTENBERG_URL=http://localhost:3000
|
||||
|
||||
# Octane
|
||||
OCTANE_SERVER=frankenphp
|
||||
|
||||
# Inertia
|
||||
INERTIA_SSR_ENABLED=false
|
||||
|
||||
@@ -88,3 +88,6 @@ FORWARD_DB_PORT=54329
|
||||
VITE_HOST_NAME=vite.solidtime.test
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
#SAIL_XDEBUG_MODE=develop,debug,coverage
|
||||
|
||||
# This app does not ship a Node SSR bundle, so Inertia SSR must stay disabled.
|
||||
INERTIA_SSR_ENABLED=false
|
||||
|
||||
@@ -13,3 +13,5 @@ LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=pgsql
|
||||
|
||||
INERTIA_SSR_ENABLED=false
|
||||
|
||||
38
app/Auth/ActiveUserProvider.php
Normal file
38
app/Auth/ActiveUserProvider.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Auth;
|
||||
|
||||
use Illuminate\Auth\EloquentUserProvider;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* User provider that only resolves non-placeholder users.
|
||||
*
|
||||
* Placeholder users are created by imports and when members are removed from an
|
||||
* organization. They can share an email address with a real user, so resolving a user by
|
||||
* email can return a placeholder instead of the real account. The login flow filters them
|
||||
* out explicitly, but the password broker and the guard credential checks (for example the
|
||||
* password confirmation) resolve users through the configured user provider.
|
||||
*
|
||||
* Registered as the "eloquent" provider driver in the AuthServiceProvider, so it replaces the
|
||||
* built-in one for every provider in config/auth.php.
|
||||
*/
|
||||
class ActiveUserProvider extends EloquentUserProvider
|
||||
{
|
||||
/**
|
||||
* @param Model|null $model
|
||||
* @return Builder<Model>
|
||||
*/
|
||||
#[Override]
|
||||
protected function newModelQuery($model = null): Builder
|
||||
{
|
||||
$query = parent::newModelQuery($model);
|
||||
$query->getQuery()->where('is_placeholder', '=', false);
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use stdClass;
|
||||
|
||||
class SelfHostDatabaseConsistency extends Command
|
||||
{
|
||||
@@ -101,7 +102,7 @@ class SelfHostDatabaseConsistency extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, \stdClass> $problems
|
||||
* @param Collection<int, stdClass> $problems
|
||||
*/
|
||||
private function logProblems(Collection $problems, string $message, bool &$hadAProblem): void
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace App\Console\Commands\SelfHost;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Encryption\Encrypter;
|
||||
use Illuminate\Support\Str;
|
||||
use phpseclib3\Crypt\RSA;
|
||||
use phpseclib4\Crypt\RSA;
|
||||
|
||||
class SelfHostGenerateKeysCommand extends Command
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Api;
|
||||
|
||||
use Override;
|
||||
|
||||
class EntityStillInUseApiException extends ApiException
|
||||
{
|
||||
private string $modelToDelete;
|
||||
@@ -32,7 +34,7 @@ class EntityStillInUseApiException extends ApiException
|
||||
/**
|
||||
* Get the translated message for the exception.
|
||||
*/
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getTranslatedMessage(): string
|
||||
{
|
||||
return __('exceptions.api.'.$this->getKey(), [
|
||||
|
||||
@@ -4,13 +4,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\AuditResource\Pages;
|
||||
use App\Filament\Resources\AuditResource\Pages\CreateAudit;
|
||||
use App\Filament\Resources\AuditResource\Pages\ListAudits;
|
||||
use App\Filament\Resources\AuditResource\Pages\ViewAudit;
|
||||
use App\Models\Audit;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Str;
|
||||
use Novadaemon\FilamentPrettyJson\Form\PrettyJsonField;
|
||||
@@ -19,32 +23,32 @@ class AuditResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Audit::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-archive-box';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-archive-box';
|
||||
|
||||
protected static ?string $navigationGroup = 'System';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'System';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('user_type')
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('user_type')
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('user_id'),
|
||||
Forms\Components\TextInput::make('event')
|
||||
TextInput::make('user_id'),
|
||||
TextInput::make('event')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('auditable_type')
|
||||
TextInput::make('auditable_type')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('auditable_id')
|
||||
TextInput::make('auditable_id')
|
||||
->required(),
|
||||
PrettyJsonField::make('old_values'),
|
||||
PrettyJsonField::make('new_values'),
|
||||
Forms\Components\Textarea::make('url'),
|
||||
Forms\Components\TextInput::make('ip_address'),
|
||||
Forms\Components\TextInput::make('user_agent')
|
||||
Textarea::make('url'),
|
||||
TextInput::make('ip_address'),
|
||||
TextInput::make('user_agent')
|
||||
->maxLength(1023),
|
||||
Forms\Components\TextInput::make('tags')
|
||||
TextInput::make('tags')
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
@@ -53,27 +57,27 @@ class AuditResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('user.name'),
|
||||
Tables\Columns\TextColumn::make('event'),
|
||||
Tables\Columns\TextColumn::make('auditable_type'),
|
||||
Tables\Columns\TextColumn::make('auditable_id'),
|
||||
TextColumn::make('user.name'),
|
||||
TextColumn::make('event'),
|
||||
TextColumn::make('auditable_type'),
|
||||
TextColumn::make('auditable_id'),
|
||||
IconColumn::make('was_command')
|
||||
->getStateUsing(fn (Audit $record) => Str::startsWith($record->url, 'artisan '))
|
||||
->boolean(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
TextColumn::make('created_at')
|
||||
->sortable()
|
||||
->dateTime(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
TextColumn::make('updated_at')
|
||||
->sortable()
|
||||
->dateTime(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\ViewAction::make(),
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
->toolbarActions([
|
||||
])
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
@@ -87,9 +91,9 @@ class AuditResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListAudits::route('/'),
|
||||
'create' => Pages\CreateAudit::route('/create'),
|
||||
'view' => Pages\ViewAudit::route('/{record}'),
|
||||
'index' => ListAudits::route('/'),
|
||||
'create' => CreateAudit::route('/create'),
|
||||
'view' => ViewAudit::route('/{record}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ClientResource\Pages;
|
||||
use App\Filament\Resources\ClientResource\Pages\CreateClient;
|
||||
use App\Filament\Resources\ClientResource\Pages\EditClient;
|
||||
use App\Filament\Resources\ClientResource\Pages\ListClients;
|
||||
use App\Models\Client;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -18,16 +23,16 @@ class ClientResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Client::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-briefcase';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-briefcase';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 4;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->required(),
|
||||
@@ -43,17 +48,17 @@ class ClientResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
TextColumn::make('name')
|
||||
->label('Name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('organization.name')
|
||||
TextColumn::make('organization.name')
|
||||
->sortable()
|
||||
->label('Organization'),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
TextColumn::make('created_at')
|
||||
->label('Created at')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
TextColumn::make('updated_at')
|
||||
->label('Updated at')
|
||||
->sortable(),
|
||||
])
|
||||
@@ -68,12 +73,12 @@ class ClientResource extends Resource
|
||||
->relationship('organization', 'id')
|
||||
->searchable(),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -88,9 +93,9 @@ class ClientResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListClients::route('/'),
|
||||
'create' => Pages\CreateClient::route('/create'),
|
||||
'edit' => Pages\EditClient::route('/{record}/edit'),
|
||||
'index' => ListClients::route('/'),
|
||||
'create' => CreateClient::route('/create'),
|
||||
'edit' => EditClient::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\ClientResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ClientResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditClient extends EditRecord
|
||||
@@ -15,7 +15,7 @@ class EditClient extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-m-trash'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\ClientResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ClientResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListClients extends ListRecords
|
||||
@@ -15,7 +15,7 @@ class ListClients extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->icon('heroicon-s-plus'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -7,16 +7,16 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Resources\FailedJobResource\Pages\ListFailedJobs;
|
||||
use App\Filament\Resources\FailedJobResource\Pages\ViewFailedJobs;
|
||||
use App\Models\FailedJob;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\BulkAction;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -30,19 +30,19 @@ class FailedJobResource extends Resource
|
||||
{
|
||||
protected static ?string $model = FailedJob::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-exclamation-circle';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-exclamation-circle';
|
||||
|
||||
protected static ?string $navigationGroup = 'System';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'System';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return (string) FailedJob::query()->count();
|
||||
}
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('uuid')->disabled()->columnSpan(4),
|
||||
TextInput::make('failed_at')->disabled(),
|
||||
TextInput::make('id')->disabled(),
|
||||
@@ -74,7 +74,7 @@ class FailedJobResource extends Resource
|
||||
TextColumn::make('queue')->sortable()->searchable()->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([])
|
||||
->bulkActions([
|
||||
->toolbarActions([
|
||||
BulkAction::make('retry')
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->label('Retry selected')
|
||||
@@ -91,7 +91,7 @@ class FailedJobResource extends Resource
|
||||
}),
|
||||
DeleteBulkAction::make(),
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
DeleteAction::make(),
|
||||
ViewAction::make(),
|
||||
Action::make('retry')
|
||||
|
||||
@@ -5,14 +5,21 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Enums\Role;
|
||||
use App\Filament\Resources\OrganizationInvitationResource\Pages;
|
||||
use App\Filament\Resources\OrganizationInvitationResource\Pages\EditOrganizationInvitation;
|
||||
use App\Filament\Resources\OrganizationInvitationResource\Pages\ListOrganizationInvitations;
|
||||
use App\Filament\Resources\OrganizationInvitationResource\Pages\ViewOrganizationInvitation;
|
||||
use App\Models\OrganizationInvitation;
|
||||
use App\Service\OrganizationInvitationService;
|
||||
use Filament\Forms;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
@@ -22,18 +29,18 @@ class OrganizationInvitationResource extends Resource
|
||||
|
||||
protected static ?string $label = 'Invitations';
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-user-plus';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-user-plus';
|
||||
|
||||
protected static ?string $navigationGroup = 'Users';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Users';
|
||||
|
||||
protected static ?int $navigationSort = 9;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
return $schema
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('email')
|
||||
->components([
|
||||
TextInput::make('email')
|
||||
->label('Email')
|
||||
->disabledOn(['edit'])
|
||||
->required(),
|
||||
@@ -45,11 +52,11 @@ class OrganizationInvitationResource extends Resource
|
||||
->searchable(['name'])
|
||||
->disabledOn(['edit'])
|
||||
->required(),
|
||||
Forms\Components\DateTimePicker::make('created_at')
|
||||
DateTimePicker::make('created_at')
|
||||
->label('Created At')
|
||||
->hiddenOn(['create'])
|
||||
->disabled(),
|
||||
Forms\Components\DateTimePicker::make('updated_at')
|
||||
DateTimePicker::make('updated_at')
|
||||
->label('Updated At')
|
||||
->hiddenOn(['create'])
|
||||
->disabled(),
|
||||
@@ -60,17 +67,17 @@ class OrganizationInvitationResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('organization.name')
|
||||
TextColumn::make('organization.name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('email')
|
||||
TextColumn::make('email')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('role'),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
TextColumn::make('role'),
|
||||
TextColumn::make('created_at')
|
||||
->label('Created At')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
TextColumn::make('updated_at')
|
||||
->label('Updated At')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
@@ -80,13 +87,13 @@ class OrganizationInvitationResource extends Resource
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\BulkAction::make('resend')
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
BulkAction::make('resend')
|
||||
->label('Resend')
|
||||
->action(function (Collection $records): void {
|
||||
foreach ($records as $organizationInvite) {
|
||||
@@ -106,9 +113,9 @@ class OrganizationInvitationResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListOrganizationInvitations::route('/'),
|
||||
'edit' => Pages\EditOrganizationInvitation::route('/{record}/edit'),
|
||||
'view' => Pages\ViewOrganizationInvitation::route('/{record}'),
|
||||
'index' => ListOrganizationInvitations::route('/'),
|
||||
'edit' => EditOrganizationInvitation::route('/{record}/edit'),
|
||||
'view' => ViewOrganizationInvitation::route('/{record}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\OrganizationInvitationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrganizationInvitationResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditOrganizationInvitation extends EditRecord
|
||||
@@ -15,7 +15,7 @@ class EditOrganizationInvitation extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-m-trash'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ use App\Enums\DateFormat;
|
||||
use App\Enums\IntervalFormat;
|
||||
use App\Enums\NumberFormat;
|
||||
use App\Enums\TimeFormat;
|
||||
use App\Filament\Resources\OrganizationResource\Pages;
|
||||
use App\Filament\Resources\OrganizationResource\Pages\CreateOrganization;
|
||||
use App\Filament\Resources\OrganizationResource\Pages\EditOrganization;
|
||||
use App\Filament\Resources\OrganizationResource\Pages\ListOrganizations;
|
||||
use App\Filament\Resources\OrganizationResource\Pages\ViewOrganization;
|
||||
use App\Filament\Resources\OrganizationResource\RelationManagers\InvitationsRelationManager;
|
||||
use App\Filament\Resources\OrganizationResource\RelationManagers\UsersRelationManager;
|
||||
use App\Models\Organization;
|
||||
@@ -21,13 +24,19 @@ use App\Service\Import\Importers\ReportDto;
|
||||
use App\Service\Import\ImportService;
|
||||
use App\Service\TimezoneService;
|
||||
use Brick\Money\ISOCurrencyProvider;
|
||||
use Filament\Forms;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -36,22 +45,22 @@ class OrganizationResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Organization::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-building-office-2';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-building-office-2';
|
||||
|
||||
protected static ?string $navigationGroup = 'Users';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Users';
|
||||
|
||||
protected static ?int $navigationSort = 7;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
return $schema
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\Toggle::make('personal_team')
|
||||
Toggle::make('personal_team')
|
||||
->label('Is personal?')
|
||||
->hiddenOn(['create'])
|
||||
->required(),
|
||||
@@ -89,7 +98,7 @@ class OrganizationResource extends Resource
|
||||
})
|
||||
->required()
|
||||
->searchable(),
|
||||
Forms\Components\TextInput::make('billable_rate')
|
||||
TextInput::make('billable_rate')
|
||||
->label('Billable rate (in Cents)')
|
||||
->nullable()
|
||||
->rules([
|
||||
@@ -99,11 +108,11 @@ class OrganizationResource extends Resource
|
||||
'max:2147483647',
|
||||
])
|
||||
->numeric(),
|
||||
Forms\Components\DateTimePicker::make('created_at')
|
||||
DateTimePicker::make('created_at')
|
||||
->label('Created At')
|
||||
->hiddenOn(['create'])
|
||||
->disabled(),
|
||||
Forms\Components\DateTimePicker::make('updated_at')
|
||||
DateTimePicker::make('updated_at')
|
||||
->label('Updated At')
|
||||
->hiddenOn(['create'])
|
||||
->disabled(),
|
||||
@@ -117,7 +126,7 @@ class OrganizationResource extends Resource
|
||||
TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\IconColumn::make('personal_team')
|
||||
IconColumn::make('personal_team')
|
||||
->boolean()
|
||||
->label('Is personal?')
|
||||
->sortable(),
|
||||
@@ -138,9 +147,9 @@ class OrganizationResource extends Resource
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make()
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make()
|
||||
->using(function (Organization $record): void {
|
||||
app(DeletionService::class)->deleteOrganization($record);
|
||||
}),
|
||||
@@ -158,7 +167,7 @@ class OrganizationResource extends Resource
|
||||
return response()->streamDownload(function () use ($file): void {
|
||||
echo Storage::disk(config('filesystems.private'))->get($file);
|
||||
}, 'export.zip');
|
||||
} catch (\Exception $exception) {
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
Notification::make()
|
||||
->title('Export failed')
|
||||
@@ -174,7 +183,7 @@ class OrganizationResource extends Resource
|
||||
try {
|
||||
$file = Storage::disk(config('filament.default_filesystem_disk'))->get($data['file']);
|
||||
if ($file === null) {
|
||||
throw new \Exception('File not found');
|
||||
throw new Exception('File not found');
|
||||
}
|
||||
/** @var string $timezone */
|
||||
$timezone = $data['timezone'];
|
||||
@@ -209,8 +218,8 @@ class OrganizationResource extends Resource
|
||||
}
|
||||
})
|
||||
->tooltip(fn (Organization $record): string => 'Import into '.$record->name)
|
||||
->form([
|
||||
Forms\Components\FileUpload::make('file')
|
||||
->schema([
|
||||
FileUpload::make('file')
|
||||
->label('File')
|
||||
->required(),
|
||||
Select::make('type')
|
||||
@@ -230,7 +239,7 @@ class OrganizationResource extends Resource
|
||||
->required(),
|
||||
]),
|
||||
])
|
||||
->bulkActions([
|
||||
->toolbarActions([
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -245,10 +254,10 @@ class OrganizationResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListOrganizations::route('/'),
|
||||
'create' => Pages\CreateOrganization::route('/create'),
|
||||
'edit' => Pages\EditOrganization::route('/{record}/edit'),
|
||||
'view' => Pages\ViewOrganization::route('/{record}'),
|
||||
'index' => ListOrganizations::route('/'),
|
||||
'create' => CreateOrganization::route('/create'),
|
||||
'edit' => EditOrganization::route('/{record}/edit'),
|
||||
'view' => ViewOrganization::route('/{record}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\OrganizationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrganizationResource;
|
||||
use App\Filament\Resources\OrganizationResource\Actions\DeleteOrganization;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditOrganization extends EditRecord
|
||||
@@ -14,7 +15,7 @@ class EditOrganization extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
OrganizationResource\Actions\DeleteOrganization::make(),
|
||||
DeleteOrganization::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\OrganizationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrganizationResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListOrganizations extends ListRecords
|
||||
@@ -15,7 +15,7 @@ class ListOrganizations extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->icon('heroicon-s-plus'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,12 +9,17 @@ use App\Filament\Resources\OrganizationInvitationResource;
|
||||
use App\Models\Organization;
|
||||
use App\Models\OrganizationInvitation;
|
||||
use App\Service\InvitationService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DetachBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Validation\Rule;
|
||||
@@ -25,10 +30,10 @@ class InvitationsRelationManager extends RelationManager
|
||||
|
||||
protected static ?string $title = 'Invitations';
|
||||
|
||||
public function form(Form $form): Form
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('email')
|
||||
->label('Email')
|
||||
->disabledOn(['edit'])
|
||||
@@ -53,33 +58,33 @@ class InvitationsRelationManager extends RelationManager
|
||||
->modelLabel('Invitation')
|
||||
->pluralModelLabel('Invitations')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('email'),
|
||||
Tables\Columns\TextColumn::make('role'),
|
||||
TextColumn::make('email'),
|
||||
TextColumn::make('role'),
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->icon('heroicon-s-plus')
|
||||
->using(function (array $data, string $model): Model {
|
||||
/** @var Organization $ownerRecord */
|
||||
$ownerRecord = $this->getOwnerRecord();
|
||||
|
||||
return app(InvitationService::class)
|
||||
->inviteUser($ownerRecord, $data['email'], Role::from($data['role']), auth()->user());
|
||||
->inviteUser($ownerRecord, $data['email'], ($data['role'] instanceof Role ? $data['role'] : Role::from($data['role'])), auth()->user());
|
||||
}),
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
Action::make('view')
|
||||
->icon('heroicon-o-eye')
|
||||
->color('gray')
|
||||
->url(fn (OrganizationInvitation $record): string => OrganizationInvitationResource::getUrl('view', [
|
||||
'record' => $record->getKey(),
|
||||
])),
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DetachBulkAction::make(),
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DetachBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -12,14 +12,15 @@ use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use App\Service\BillableRateService;
|
||||
use App\Service\MemberService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\AttachAction;
|
||||
use Filament\Actions\DetachAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\AttachAction;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Validation\Rule;
|
||||
@@ -28,10 +29,10 @@ class UsersRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'users';
|
||||
|
||||
public function form(Form $form): Form
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('role')
|
||||
->options(Role::class),
|
||||
TextInput::make('billable_rate')
|
||||
@@ -75,17 +76,17 @@ class UsersRelationManager extends RelationManager
|
||||
->using(function (User $record, array $data): void {
|
||||
/** @var Organization $organization */
|
||||
$organization = $this->getOwnerRecord();
|
||||
app(MemberService::class)->addMember($record, $organization, Role::from($data['role']), true);
|
||||
app(MemberService::class)->addMember($record, $organization, ($data['role'] instanceof Role ? $data['role'] : Role::from($data['role'])), true);
|
||||
}),
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
Action::make('view')
|
||||
->icon('heroicon-o-eye')
|
||||
->color('gray')
|
||||
->url(fn (User $record): string => UserResource::getUrl('view', [
|
||||
'record' => $record->getKey(),
|
||||
])),
|
||||
Tables\Actions\EditAction::make()
|
||||
EditAction::make()
|
||||
->using(function (User $record, array $data): User {
|
||||
/** @var Organization $organization */
|
||||
$organization = $this->getOwnerRecord();
|
||||
@@ -99,7 +100,7 @@ class UsersRelationManager extends RelationManager
|
||||
|
||||
if ($data['role'] !== $member->role) {
|
||||
try {
|
||||
app(MemberService::class)->changeRole($member, $organization, Role::from($data['role']), true);
|
||||
app(MemberService::class)->changeRole($member, $organization, ($data['role'] instanceof Role ? $data['role'] : Role::from($data['role'])), true);
|
||||
} catch (ApiException $exception) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
@@ -113,7 +114,7 @@ class UsersRelationManager extends RelationManager
|
||||
|
||||
return $record;
|
||||
}),
|
||||
Tables\Actions\DetachAction::make()
|
||||
DetachAction::make()
|
||||
->using(function (User $record): void {
|
||||
/** @var Organization $organization */
|
||||
$organization = $this->getOwnerRecord();
|
||||
@@ -133,7 +134,7 @@ class UsersRelationManager extends RelationManager
|
||||
}
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
->toolbarActions([
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,19 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ProjectMemberResource\Pages;
|
||||
use App\Filament\Resources\ProjectMemberResource\Pages\CreateProjectMember;
|
||||
use App\Filament\Resources\ProjectMemberResource\Pages\EditProjectMember;
|
||||
use App\Filament\Resources\ProjectMemberResource\Pages\ListProjectMembers;
|
||||
use App\Filament\Resources\ProjectMemberResource\Pages\ViewProjectMembers;
|
||||
use App\Models\ProjectMember;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ProjectMemberResource extends Resource
|
||||
@@ -18,11 +25,11 @@ class ProjectMemberResource extends Resource
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('billable_rate')
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('billable_rate')
|
||||
->label('Billable rate (in Cents)')
|
||||
->nullable()
|
||||
->rules([
|
||||
@@ -32,10 +39,10 @@ class ProjectMemberResource extends Resource
|
||||
'max:2147483647',
|
||||
])
|
||||
->numeric(),
|
||||
Forms\Components\Select::make('user_id')
|
||||
Select::make('user_id')
|
||||
->relationship('user', 'name')
|
||||
->required(),
|
||||
Forms\Components\Select::make('member_id')
|
||||
Select::make('member_id')
|
||||
->relationship('member', 'id')
|
||||
->required(),
|
||||
]);
|
||||
@@ -45,17 +52,17 @@ class ProjectMemberResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id')
|
||||
TextColumn::make('id')
|
||||
->label('ID'),
|
||||
Tables\Columns\TextColumn::make('billable_rate')
|
||||
TextColumn::make('billable_rate')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('project.name'),
|
||||
Tables\Columns\TextColumn::make('user.name'),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
TextColumn::make('project.name'),
|
||||
TextColumn::make('user.name'),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
@@ -63,12 +70,12 @@ class ProjectMemberResource extends Resource
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -83,10 +90,10 @@ class ProjectMemberResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListProjectMembers::route('/'),
|
||||
'create' => Pages\CreateProjectMember::route('/create'),
|
||||
'edit' => Pages\EditProjectMember::route('/{record}/edit'),
|
||||
'view' => Pages\ViewProjectMembers::route('/{record}'),
|
||||
'index' => ListProjectMembers::route('/'),
|
||||
'create' => CreateProjectMember::route('/create'),
|
||||
'edit' => EditProjectMember::route('/{record}/edit'),
|
||||
'view' => ViewProjectMembers::route('/{record}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\ProjectMemberResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProjectMemberResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditProjectMember extends EditRecord
|
||||
@@ -15,7 +15,7 @@ class EditProjectMember extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-m-trash'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\ProjectMemberResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProjectMemberResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListProjectMembers extends ListRecords
|
||||
@@ -15,7 +15,7 @@ class ListProjectMembers extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->icon('heroicon-s-plus'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,14 +4,19 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ProjectResource\Pages;
|
||||
use App\Filament\Resources\ProjectResource\Pages\CreateProject;
|
||||
use App\Filament\Resources\ProjectResource\Pages\EditProject;
|
||||
use App\Filament\Resources\ProjectResource\Pages\ListProjects;
|
||||
use App\Filament\Resources\ProjectResource\RelationManagers\ProjectMembersRelationManager;
|
||||
use App\Models\Project;
|
||||
use Filament\Forms;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\ColorPicker;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\ColorColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
@@ -21,24 +26,24 @@ class ProjectResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Project::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-folder';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-folder';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
ColorPicker::make('color')
|
||||
->label('Color')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('billable_rate')
|
||||
TextInput::make('billable_rate')
|
||||
->label('Billable rate (in Cents)')
|
||||
->nullable()
|
||||
->rules([
|
||||
@@ -48,7 +53,7 @@ class ProjectResource extends Resource
|
||||
'max:2147483647',
|
||||
])
|
||||
->numeric(),
|
||||
Forms\Components\Select::make('organization_id')
|
||||
Select::make('organization_id')
|
||||
->relationship(name: 'organization', titleAttribute: 'name')
|
||||
->searchable(['name'])
|
||||
->required(),
|
||||
@@ -81,12 +86,12 @@ class ProjectResource extends Resource
|
||||
->searchable(),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -101,9 +106,9 @@ class ProjectResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListProjects::route('/'),
|
||||
'create' => Pages\CreateProject::route('/create'),
|
||||
'edit' => Pages\EditProject::route('/{record}/edit'),
|
||||
'index' => ListProjects::route('/'),
|
||||
'create' => CreateProject::route('/create'),
|
||||
'edit' => EditProject::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\ProjectResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProjectResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditProject extends EditRecord
|
||||
@@ -15,7 +15,7 @@ class EditProject extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-m-trash'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\ProjectResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProjectResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListProjects extends ListRecords
|
||||
@@ -15,7 +15,7 @@ class ListProjects extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->icon('heroicon-s-plus'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ namespace App\Filament\Resources\ProjectResource\RelationManagers;
|
||||
|
||||
use App\Filament\Resources\ProjectMemberResource;
|
||||
use App\Models\ProjectMember;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ProjectMembersRelationManager extends RelationManager
|
||||
@@ -18,10 +18,10 @@ class ProjectMembersRelationManager extends RelationManager
|
||||
|
||||
protected static string $relationship = 'members';
|
||||
|
||||
public function form(Form $form): Form
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ class ProjectMembersRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('name')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('user.name'),
|
||||
Tables\Columns\TextColumn::make('billable_rate')
|
||||
TextColumn::make('user.name'),
|
||||
TextColumn::make('billable_rate')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
])
|
||||
@@ -40,7 +40,7 @@ class ProjectMembersRelationManager extends RelationManager
|
||||
])
|
||||
->headerActions([
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
Action::make('view')
|
||||
->icon('heroicon-o-eye')
|
||||
->color('gray')
|
||||
@@ -54,7 +54,7 @@ class ProjectMembersRelationManager extends RelationManager
|
||||
]))
|
||||
->openUrlInNewTab(),
|
||||
])
|
||||
->bulkActions([
|
||||
->toolbarActions([
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,21 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ReportResource\Pages;
|
||||
use App\Filament\Resources\ReportResource\Pages\EditReport;
|
||||
use App\Filament\Resources\ReportResource\Pages\ListReports;
|
||||
use App\Filament\Resources\ReportResource\Pages\ViewReport;
|
||||
use App\Models\Report;
|
||||
use App\Service\Dto\ReportPropertiesDto;
|
||||
use Filament\Forms;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\ToggleColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
@@ -24,22 +29,22 @@ class ReportResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Report::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-document-chart-bar';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-document-chart-bar';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 7;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
return $schema
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('description')
|
||||
TextInput::make('description')
|
||||
->label('Description')
|
||||
->nullable()
|
||||
->maxLength(255),
|
||||
@@ -49,13 +54,13 @@ class ReportResource extends Resource
|
||||
DateTimePicker::make('public_until')
|
||||
->label('Public until')
|
||||
->nullable(),
|
||||
Forms\Components\Select::make('organization_id')
|
||||
Select::make('organization_id')
|
||||
->label('Organization')
|
||||
->relationship(name: 'organization', titleAttribute: 'name')
|
||||
->searchable(['name'])
|
||||
->disabled()
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('share_secret')
|
||||
TextInput::make('share_secret')
|
||||
->label('Share Secret')
|
||||
->nullable(),
|
||||
PrettyJsonField::make('properties')
|
||||
@@ -109,18 +114,18 @@ class ReportResource extends Resource
|
||||
->relationship('organization', 'id')
|
||||
->searchable(),
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
Action::make('public-view')
|
||||
->label('Public')
|
||||
->icon('heroicon-o-eye')
|
||||
->color('gray')
|
||||
->hidden(fn (Report $record): bool => $record->getShareableLink() === null)
|
||||
->url(fn (Report $record): string => $record->getShareableLink(), true),
|
||||
Tables\Actions\ViewAction::make(),
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
->toolbarActions([
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -133,9 +138,9 @@ class ReportResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListReports::route('/'),
|
||||
'edit' => Pages\EditReport::route('/{record}/edit'),
|
||||
'view' => Pages\ViewReport::route('/{record}'),
|
||||
'index' => ListReports::route('/'),
|
||||
'edit' => EditReport::route('/{record}/edit'),
|
||||
'view' => ViewReport::route('/{record}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\ReportResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ReportResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditReport extends EditRecord
|
||||
@@ -15,7 +15,7 @@ class EditReport extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-m-trash'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,13 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\TagResource\Pages;
|
||||
use App\Filament\Resources\TagResource\Pages\CreateTag;
|
||||
use App\Filament\Resources\TagResource\Pages\EditTag;
|
||||
use App\Filament\Resources\TagResource\Pages\ListTags;
|
||||
use App\Models\Tag;
|
||||
use Filament\Forms;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -18,20 +23,20 @@ class TagResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Tag::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-tag';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-tag';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 5;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->required(),
|
||||
Forms\Components\Select::make('organization_id')
|
||||
Select::make('organization_id')
|
||||
->relationship(name: 'organization', titleAttribute: 'name')
|
||||
->label('Organization')
|
||||
->searchable(['name'])
|
||||
@@ -43,17 +48,17 @@ class TagResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
TextColumn::make('name')
|
||||
->label('Name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('organization.name')
|
||||
TextColumn::make('organization.name')
|
||||
->sortable()
|
||||
->label('Organization'),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
TextColumn::make('created_at')
|
||||
->label('Created at')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
TextColumn::make('updated_at')
|
||||
->label('Updated at')
|
||||
->sortable(),
|
||||
])
|
||||
@@ -68,12 +73,12 @@ class TagResource extends Resource
|
||||
->relationship('organization', 'id')
|
||||
->searchable(),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -88,9 +93,9 @@ class TagResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListTags::route('/'),
|
||||
'create' => Pages\CreateTag::route('/create'),
|
||||
'edit' => Pages\EditTag::route('/{record}/edit'),
|
||||
'index' => ListTags::route('/'),
|
||||
'create' => CreateTag::route('/create'),
|
||||
'edit' => EditTag::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\TagResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TagResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTag extends EditRecord
|
||||
@@ -15,7 +15,7 @@ class EditTag extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-m-trash'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\TagResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TagResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTags extends ListRecords
|
||||
@@ -15,7 +15,7 @@ class ListTags extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->icon('heroicon-s-plus'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,13 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\TaskResource\Pages;
|
||||
use App\Filament\Resources\TaskResource\Pages\CreateTask;
|
||||
use App\Filament\Resources\TaskResource\Pages\EditTask;
|
||||
use App\Filament\Resources\TaskResource\Pages\ListTasks;
|
||||
use App\Models\Task;
|
||||
use Filament\Forms;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -18,17 +23,17 @@ class TaskResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Task::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-list-bullet';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-list-bullet';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 3;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
@@ -47,16 +52,16 @@ class TaskResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('project.name')
|
||||
TextColumn::make('project.name')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('organization.name')
|
||||
TextColumn::make('organization.name')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
TextColumn::make('created_at')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
TextColumn::make('updated_at')
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
@@ -70,12 +75,12 @@ class TaskResource extends Resource
|
||||
->searchable(),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -90,9 +95,9 @@ class TaskResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListTasks::route('/'),
|
||||
'create' => Pages\CreateTask::route('/create'),
|
||||
'edit' => Pages\EditTask::route('/{record}/edit'),
|
||||
'index' => ListTasks::route('/'),
|
||||
'create' => CreateTask::route('/create'),
|
||||
'edit' => EditTask::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\TaskResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TaskResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTask extends EditRecord
|
||||
@@ -15,7 +15,7 @@ class EditTask extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-m-trash'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\TaskResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TaskResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTasks extends ListRecords
|
||||
@@ -15,7 +15,7 @@ class ListTasks extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->icon('heroicon-s-plus'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,16 +4,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\TimeEntryResource\Pages;
|
||||
use App\Filament\Resources\TimeEntryResource\Pages\CreateTimeEntry;
|
||||
use App\Filament\Resources\TimeEntryResource\Pages\EditTimeEntry;
|
||||
use App\Filament\Resources\TimeEntryResource\Pages\ListTimeEntries;
|
||||
use App\Models\Member;
|
||||
use App\Models\TimeEntry;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
@@ -23,16 +27,16 @@ class TimeEntryResource extends Resource
|
||||
{
|
||||
protected static ?string $model = TimeEntry::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-clock';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-clock';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('id')
|
||||
->label('ID')
|
||||
->readOnly()
|
||||
@@ -111,12 +115,12 @@ class TimeEntryResource extends Resource
|
||||
->searchable(),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -131,9 +135,9 @@ class TimeEntryResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListTimeEntries::route('/'),
|
||||
'create' => Pages\CreateTimeEntry::route('/create'),
|
||||
'edit' => Pages\EditTimeEntry::route('/{record}/edit'),
|
||||
'index' => ListTimeEntries::route('/'),
|
||||
'create' => CreateTimeEntry::route('/create'),
|
||||
'edit' => EditTimeEntry::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace App\Filament\Resources\TimeEntryResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TimeEntryResource;
|
||||
use App\Models\Member;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTimeEntry extends EditRecord
|
||||
@@ -16,7 +16,7 @@ class EditTimeEntry extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make()
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-m-trash'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TimeEntryResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTimeEntries extends ListRecords
|
||||
@@ -15,7 +15,7 @@ class ListTimeEntries extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->icon('heroicon-s-plus'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,12 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\TokenResource\Pages;
|
||||
use App\Filament\Resources\TokenResource\Pages\ListTokens;
|
||||
use App\Filament\Resources\TokenResource\Pages\ViewToken;
|
||||
use App\Models\Passport\Token;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -18,48 +24,48 @@ class TokenResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Token::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-key';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-key';
|
||||
|
||||
protected static ?string $navigationGroup = 'Auth';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Auth';
|
||||
|
||||
protected static ?int $navigationSort = 6;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
return $schema
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('id')
|
||||
->components([
|
||||
TextInput::make('id')
|
||||
->label('ID')
|
||||
->disabled()
|
||||
->visibleOn(['update', 'show'])
|
||||
->readOnly()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('name')
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\Select::make('owner_id')
|
||||
Select::make('owner_id')
|
||||
->label('User')
|
||||
->relationship(name: 'user', titleAttribute: 'name')
|
||||
->searchable(['name'])
|
||||
->disabled()
|
||||
->required(),
|
||||
Forms\Components\Select::make('client_id')
|
||||
Select::make('client_id')
|
||||
->label('Client')
|
||||
->relationship(name: 'client', titleAttribute: 'name')
|
||||
->searchable(['name'])
|
||||
->required(),
|
||||
Forms\Components\Toggle::make('revoked')
|
||||
Toggle::make('revoked')
|
||||
->label('Revoked')
|
||||
->required(),
|
||||
Forms\Components\DateTimePicker::make('expires_at')
|
||||
DateTimePicker::make('expires_at')
|
||||
->label('Expires At')
|
||||
->disabled(),
|
||||
Forms\Components\DateTimePicker::make('created_at')
|
||||
DateTimePicker::make('created_at')
|
||||
->label('Created At')
|
||||
->disabled(),
|
||||
Forms\Components\DateTimePicker::make('updated_at')
|
||||
DateTimePicker::make('updated_at')
|
||||
->label('Updated At')
|
||||
->disabled(),
|
||||
]);
|
||||
@@ -69,32 +75,32 @@ class TokenResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('user.name')
|
||||
TextColumn::make('user.name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('client.name')
|
||||
TextColumn::make('client.name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\IconColumn::make('personal_access_client')
|
||||
IconColumn::make('personal_access_client')
|
||||
->state(function (Token $token): bool {
|
||||
return in_array('personal_access', $token->client->grant_types ?? [], true);
|
||||
})
|
||||
->boolean()
|
||||
->label('API token?'),
|
||||
Tables\Columns\IconColumn::make('revoked')
|
||||
IconColumn::make('revoked')
|
||||
->boolean()
|
||||
->label('Revoked?')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('expires_at')
|
||||
TextColumn::make('expires_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
@@ -120,10 +126,10 @@ class TokenResource extends Resource
|
||||
TernaryFilter::make('revoked')
|
||||
->label('Revoked?'),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\ViewAction::make(),
|
||||
->recordActions([
|
||||
ViewAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
->toolbarActions([
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -136,8 +142,8 @@ class TokenResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListTokens::route('/'),
|
||||
'view' => Pages\ViewToken::route('/{record}'),
|
||||
'index' => ListTokens::route('/'),
|
||||
'view' => ViewToken::route('/{record}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ namespace App\Filament\Resources;
|
||||
|
||||
use App\Enums\Weekday;
|
||||
use App\Exceptions\Api\ApiException;
|
||||
use App\Filament\Resources\UserResource\Pages;
|
||||
use App\Filament\Resources\UserResource\Pages\CreateUser;
|
||||
use App\Filament\Resources\UserResource\Pages\EditUser;
|
||||
use App\Filament\Resources\UserResource\Pages\ListUsers;
|
||||
use App\Filament\Resources\UserResource\Pages\ViewUser;
|
||||
use App\Filament\Resources\UserResource\RelationManagers\OrganizationsRelationManager;
|
||||
use App\Filament\Resources\UserResource\RelationManagers\OwnedOrganizationsRelationManager;
|
||||
use App\Models\User;
|
||||
@@ -15,12 +18,18 @@ use App\Service\TimezoneService;
|
||||
use App\Service\UserService;
|
||||
use Brick\Money\ISOCurrencyProvider;
|
||||
use Exception;
|
||||
use Filament\Forms;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -28,26 +37,26 @@ use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Korridor\LaravelModelValidationRules\Rules\UniqueEloquent;
|
||||
use STS\FilamentImpersonate\Tables\Actions\Impersonate;
|
||||
use STS\FilamentImpersonate\Actions\Impersonate;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-user';
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-user';
|
||||
|
||||
protected static ?string $navigationGroup = 'Users';
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Users';
|
||||
|
||||
protected static ?int $navigationSort = 6;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
/** @var User|null $record */
|
||||
$record = $form->getRecord();
|
||||
$record = $schema->getRecord();
|
||||
|
||||
return $form
|
||||
return $schema
|
||||
->columns(1)
|
||||
->schema([
|
||||
->components([
|
||||
TextInput::make('id')
|
||||
->label('ID')
|
||||
->disabled()
|
||||
@@ -69,23 +78,23 @@ class UserResource extends Resource
|
||||
'email:rfc,strict',
|
||||
])
|
||||
->maxLength(255),
|
||||
Forms\Components\Toggle::make('is_placeholder')
|
||||
Toggle::make('is_placeholder')
|
||||
->label('Is Placeholder?')
|
||||
->hiddenOn(['create'])
|
||||
->disabledOn(['edit']),
|
||||
Forms\Components\DateTimePicker::make('email_verified_at')
|
||||
DateTimePicker::make('email_verified_at')
|
||||
->label('Email Verified At')
|
||||
->hiddenOn(['create'])
|
||||
->nullable(),
|
||||
Forms\Components\Toggle::make('is_email_verified')
|
||||
Toggle::make('is_email_verified')
|
||||
->label('Email Verified?')
|
||||
->visibleOn(['create']),
|
||||
Forms\Components\Select::make('timezone')
|
||||
Select::make('timezone')
|
||||
->label('Timezone')
|
||||
->options(fn (): array => app(TimezoneService::class)->getSelectOptions())
|
||||
->searchable()
|
||||
->required(),
|
||||
Forms\Components\Select::make('week_start')
|
||||
Select::make('week_start')
|
||||
->label('Week Start')
|
||||
->options(Weekday::class)
|
||||
->required(),
|
||||
@@ -103,7 +112,7 @@ class UserResource extends Resource
|
||||
->visibleOn(['create'])
|
||||
->required(fn (string $context): bool => $context === 'create')
|
||||
->maxLength(255),
|
||||
Forms\Components\Select::make('currency')
|
||||
Select::make('currency')
|
||||
->label('Currency (Personal Organization)')
|
||||
->options(function (): array {
|
||||
$currencies = ISOCurrencyProvider::getInstance()->getAvailableCurrencies();
|
||||
@@ -117,11 +126,11 @@ class UserResource extends Resource
|
||||
->required()
|
||||
->visibleOn(['create'])
|
||||
->searchable(),
|
||||
Forms\Components\DateTimePicker::make('created_at')
|
||||
DateTimePicker::make('created_at')
|
||||
->label('Created At')
|
||||
->hiddenOn(['create'])
|
||||
->disabled(),
|
||||
Forms\Components\DateTimePicker::make('updated_at')
|
||||
DateTimePicker::make('updated_at')
|
||||
->label('Updated At')
|
||||
->hiddenOn(['create'])
|
||||
->disabled(),
|
||||
@@ -132,25 +141,25 @@ class UserResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('email')
|
||||
TextColumn::make('email')
|
||||
->icon('heroicon-m-envelope')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\IconColumn::make('is_real_user')
|
||||
IconColumn::make('is_real_user')
|
||||
->getStateUsing(fn (User $record): bool => ! $record->is_placeholder)
|
||||
->label('Real user?')
|
||||
->boolean(),
|
||||
Tables\Columns\IconColumn::make('email_verified')
|
||||
IconColumn::make('email_verified')
|
||||
->getStateUsing(fn (User $record): bool => $record->email_verified_at !== null)
|
||||
->label('Email verified?')
|
||||
->boolean(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
@@ -178,7 +187,7 @@ class UserResource extends Resource
|
||||
->attribute('email_verified_at')
|
||||
->nullable(),
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
Impersonate::make()->before(function (User $record): void {
|
||||
if ($record->currentOrganization === null) {
|
||||
$organization = $record->organizations()->where('personal_team', '=', true)->first();
|
||||
@@ -191,8 +200,8 @@ class UserResource extends Resource
|
||||
app(UserService::class)->switchCurrentOrganization($record, $organization);
|
||||
}
|
||||
}),
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make()
|
||||
EditAction::make(),
|
||||
DeleteAction::make()
|
||||
->hidden(fn (User $record) => $record->is(Auth::user()))
|
||||
->using(function (User $record): void {
|
||||
try {
|
||||
@@ -207,8 +216,8 @@ class UserResource extends Resource
|
||||
}
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkAction::make('Resend verification email')
|
||||
->toolbarActions([
|
||||
BulkAction::make('Resend verification email')
|
||||
->icon('heroicon-o-paper-airplane')
|
||||
->action(function (Collection $records): void {
|
||||
foreach ($records as $user) {
|
||||
@@ -230,10 +239,10 @@ class UserResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
'create' => Pages\CreateUser::route('/create'),
|
||||
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||
'view' => Pages\ViewUser::route('/{record}'),
|
||||
'index' => ListUsers::route('/'),
|
||||
'create' => CreateUser::route('/create'),
|
||||
'edit' => EditUser::route('/{record}/edit'),
|
||||
'view' => ViewUser::route('/{record}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class CreateUser extends CreateRecord
|
||||
$data['email'],
|
||||
$data['password_create'],
|
||||
$data['timezone'],
|
||||
Weekday::from($data['week_start']),
|
||||
$data['week_start'] instanceof Weekday ? $data['week_start'] : Weekday::from($data['week_start']),
|
||||
$data['currency'],
|
||||
verifyEmail: (bool) $data['is_email_verified']
|
||||
);
|
||||
|
||||
@@ -5,8 +5,9 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use App\Filament\Resources\UserResource\Actions\DeleteUser;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use STS\FilamentImpersonate\Pages\Actions\Impersonate;
|
||||
use STS\FilamentImpersonate\Actions\Impersonate;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
{
|
||||
@@ -16,7 +17,7 @@ class EditUser extends EditRecord
|
||||
{
|
||||
return [
|
||||
Impersonate::make()->record($this->getRecord()),
|
||||
UserResource\Actions\DeleteUser::make(),
|
||||
DeleteUser::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
@@ -15,7 +15,7 @@ class ListUsers extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->icon('heroicon-s-plus'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace App\Filament\Resources\UserResource\Pages;
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
use STS\FilamentImpersonate\Pages\Actions\Impersonate;
|
||||
use STS\FilamentImpersonate\Actions\Impersonate;
|
||||
|
||||
class ViewUser extends ViewRecord
|
||||
{
|
||||
|
||||
@@ -11,12 +11,13 @@ use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use App\Service\MemberService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DetachAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -24,10 +25,10 @@ class OrganizationsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'organizations';
|
||||
|
||||
public function form(Form $form): Form
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('role')
|
||||
->options(Role::class),
|
||||
]);
|
||||
@@ -46,21 +47,21 @@ class OrganizationsRelationManager extends RelationManager
|
||||
])
|
||||
->headerActions([
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
Action::make('view')
|
||||
->icon('heroicon-o-eye')
|
||||
->color('gray')
|
||||
->url(fn (Organization $record): string => OrganizationResource::getUrl('view', [
|
||||
'record' => $record->getKey(),
|
||||
])),
|
||||
Tables\Actions\EditAction::make()
|
||||
EditAction::make()
|
||||
->using(function (Organization $record, array $data): Organization {
|
||||
/** @var Member $member */
|
||||
$member = $record->getRelation('membership');
|
||||
|
||||
if ($data['role'] !== $member->role) {
|
||||
try {
|
||||
app(MemberService::class)->changeRole($member, $record, Role::from($data['role']), true);
|
||||
app(MemberService::class)->changeRole($member, $record, ($data['role'] instanceof Role ? $data['role'] : Role::from($data['role'])), true);
|
||||
} catch (ApiException $exception) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
@@ -74,7 +75,7 @@ class OrganizationsRelationManager extends RelationManager
|
||||
|
||||
return $record;
|
||||
}),
|
||||
Tables\Actions\DetachAction::make()
|
||||
DetachAction::make()
|
||||
->using(function (Organization $record): void {
|
||||
/** @var User $user */
|
||||
$user = $this->getOwnerRecord();
|
||||
@@ -94,7 +95,7 @@ class OrganizationsRelationManager extends RelationManager
|
||||
}
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
->toolbarActions([
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ namespace App\Filament\Resources\UserResource\RelationManagers;
|
||||
|
||||
use App\Filament\Resources\OrganizationResource;
|
||||
use App\Models\Organization;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class OwnedOrganizationsRelationManager extends RelationManager
|
||||
@@ -18,10 +18,10 @@ class OwnedOrganizationsRelationManager extends RelationManager
|
||||
|
||||
protected static string $relationship = 'ownedOrganizations';
|
||||
|
||||
public function form(Form $form): Form
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
return $schema
|
||||
->components([
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -30,14 +30,14 @@ class OwnedOrganizationsRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('name')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name'),
|
||||
TextColumn::make('name'),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
])
|
||||
->actions([
|
||||
->recordActions([
|
||||
Action::make('view')
|
||||
->icon('heroicon-o-eye')
|
||||
->color('gray')
|
||||
@@ -51,7 +51,7 @@ class OwnedOrganizationsRelationManager extends RelationManager
|
||||
]))
|
||||
->openUrlInNewTab(),
|
||||
])
|
||||
->bulkActions([
|
||||
->toolbarActions([
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ServerOverview extends Widget
|
||||
{
|
||||
protected static string $view = 'filament.widgets.server-overview';
|
||||
protected string $view = 'filament.widgets.server-overview';
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
|
||||
@@ -11,7 +11,7 @@ use Flowframe\Trend\TrendValue;
|
||||
|
||||
class TimeEntriesCreated extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Time Entries Created';
|
||||
protected ?string $heading = 'Time Entries Created';
|
||||
|
||||
public ?string $filter = 'week';
|
||||
|
||||
@@ -53,7 +53,7 @@ class TimeEntriesCreated extends ChartWidget
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => self::$heading,
|
||||
'label' => $this->heading,
|
||||
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -11,7 +11,7 @@ use Flowframe\Trend\TrendValue;
|
||||
|
||||
class TimeEntriesImported extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Time Entries Imported';
|
||||
protected ?string $heading = 'Time Entries Imported';
|
||||
|
||||
public ?string $filter = 'week';
|
||||
|
||||
@@ -53,7 +53,7 @@ class TimeEntriesImported extends ChartWidget
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => self::$heading,
|
||||
'label' => $this->heading,
|
||||
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -11,7 +11,7 @@ use Flowframe\Trend\TrendValue;
|
||||
|
||||
class UserRegistrations extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'User Registrations';
|
||||
protected ?string $heading = 'User Registrations';
|
||||
|
||||
public ?string $filter = 'week';
|
||||
|
||||
@@ -54,7 +54,7 @@ class UserRegistrations extends ChartWidget
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => self::$heading,
|
||||
'label' => $this->heading,
|
||||
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -14,6 +14,7 @@ use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
|
||||
class ApiTokenController extends Controller
|
||||
{
|
||||
@@ -62,7 +63,7 @@ class ApiTokenController extends Controller
|
||||
$tokenModel = $token->getToken();
|
||||
|
||||
return new ApiTokenWithAccessTokenResource($tokenModel, $token->accessToken);
|
||||
} catch (\RuntimeException $exception) {
|
||||
} catch (RuntimeException $exception) {
|
||||
report($exception);
|
||||
if (Str::contains($exception->getMessage(), ['Personal access client not found'])) {
|
||||
throw new PersonalAccessClientIsNotConfiguredException;
|
||||
|
||||
@@ -33,6 +33,7 @@ use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class MemberController extends Controller
|
||||
{
|
||||
@@ -146,7 +147,7 @@ class MemberController extends Controller
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
* @throws OnlyPlaceholdersCanBeMergedIntoAnotherMember
|
||||
* @throws \Throwable
|
||||
* @throws Throwable
|
||||
*
|
||||
* @operationId mergeMember
|
||||
*/
|
||||
|
||||
@@ -55,6 +55,7 @@ use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use LogicException;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
|
||||
@@ -265,7 +266,7 @@ class TimeEntryController extends Controller
|
||||
}
|
||||
$viewFile = file_get_contents(resource_path('views/reports/time-entry-index/pdf.blade.php'));
|
||||
if ($viewFile === false) {
|
||||
throw new \LogicException('View file not found');
|
||||
throw new LogicException('View file not found');
|
||||
}
|
||||
$timeEntriesAggregateQuery = $this->getTimeEntriesAggregateQuery($organization, $request, $memberFilter);
|
||||
$aggregatedData = $timeEntryAggregationService->getAggregatedTimeEntries(
|
||||
@@ -293,7 +294,7 @@ class TimeEntryController extends Controller
|
||||
]);
|
||||
$footerViewFile = file_get_contents(resource_path('views/reports/time-entry-index/pdf-footer.blade.php'));
|
||||
if ($footerViewFile === false) {
|
||||
throw new \LogicException('View file not found');
|
||||
throw new LogicException('View file not found');
|
||||
}
|
||||
$footerHtml = Blade::render($footerViewFile);
|
||||
if ($debug) {
|
||||
@@ -495,7 +496,7 @@ class TimeEntryController extends Controller
|
||||
]);
|
||||
$viewFile = file_get_contents(resource_path('views/reports/time-entry-aggregate/pdf.blade.php'));
|
||||
if ($viewFile === false) {
|
||||
throw new \LogicException('View file not found');
|
||||
throw new LogicException('View file not found');
|
||||
}
|
||||
$html = Blade::render($viewFile, [
|
||||
'aggregatedData' => $aggregatedData,
|
||||
@@ -512,7 +513,7 @@ class TimeEntryController extends Controller
|
||||
]);
|
||||
$footerViewFile = file_get_contents(resource_path('views/reports/time-entry-aggregate/pdf-footer.blade.php'));
|
||||
if ($footerViewFile === false) {
|
||||
throw new \LogicException('View file not found');
|
||||
throw new LogicException('View file not found');
|
||||
}
|
||||
$footerHtml = Blade::render($footerViewFile);
|
||||
if ($debug) {
|
||||
|
||||
@@ -16,6 +16,7 @@ use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
|
||||
use Laravel\Fortify\Features;
|
||||
use stdClass;
|
||||
|
||||
class UserProfileController extends Controller
|
||||
{
|
||||
@@ -103,7 +104,7 @@ class UserProfileController extends Controller
|
||||
/**
|
||||
* Get the current sessions.
|
||||
*
|
||||
* @return array<int, object{agent: array{is_desktop: bool, platform: string|null, browser: string|null}, ip_address: string, is_current_device: bool, last_active: string}&\stdClass>
|
||||
* @return array<int, object{agent: array{is_desktop: bool, platform: string|null, browser: string|null}, ip_address: string, is_current_device: bool, last_active: string}&stdClass>
|
||||
*/
|
||||
public function sessions(Request $request): array
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\Organization;
|
||||
use App\Service\BillingContract;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use LogicException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckOrganizationBlocked
|
||||
@@ -25,7 +26,7 @@ class CheckOrganizationBlocked
|
||||
$organization = $request->route('organization');
|
||||
|
||||
if (! ($organization instanceof Organization)) {
|
||||
throw new \LogicException('The organization must be loaded before this middleware.');
|
||||
throw new LogicException('The organization must be loaded before this middleware.');
|
||||
}
|
||||
|
||||
/** @var BillingContract $billing */
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Middleware\TrustHosts as BaseTrustHosts;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
@@ -39,7 +40,7 @@ class TrustHosts extends BaseTrustHosts
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Closure(Request): Response $next
|
||||
* @param Closure(Request):Response $next
|
||||
*/
|
||||
public function handle(Request $request, $next)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ class ImportRequest extends BaseFormRequest
|
||||
'data' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:'.config('import.max_data_size'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ use App\Enums\Weekday;
|
||||
use App\Http\Requests\V1\BaseFormRequest;
|
||||
use App\Models\Organization;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\Rule as LegacyValidationRule;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* @property Organization $organization Organization from model binding
|
||||
@@ -27,7 +29,7 @@ class ReportStoreRequest extends BaseFormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule|LegacyValidationRule|\Closure>>
|
||||
* @return array<string, array<string|ValidationRule|LegacyValidationRule|Closure>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -85,7 +87,7 @@ class ReportStoreRequest extends BaseFormRequest
|
||||
],
|
||||
'properties.client_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -101,7 +103,7 @@ class ReportStoreRequest extends BaseFormRequest
|
||||
],
|
||||
'properties.project_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -117,7 +119,7 @@ class ReportStoreRequest extends BaseFormRequest
|
||||
],
|
||||
'properties.tag_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -137,7 +139,7 @@ class ReportStoreRequest extends BaseFormRequest
|
||||
],
|
||||
'properties.task_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -213,7 +215,7 @@ class ReportStoreRequest extends BaseFormRequest
|
||||
{
|
||||
$start = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('properties.start'));
|
||||
if ($start === null) {
|
||||
throw new \LogicException('Start date validation is not working');
|
||||
throw new LogicException('Start date validation is not working');
|
||||
}
|
||||
|
||||
return $start;
|
||||
@@ -223,7 +225,7 @@ class ReportStoreRequest extends BaseFormRequest
|
||||
{
|
||||
$end = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('properties.end'));
|
||||
if ($end === null) {
|
||||
throw new \LogicException('End date validation is not working');
|
||||
throw new LogicException('End date validation is not working');
|
||||
}
|
||||
|
||||
return $end;
|
||||
|
||||
@@ -19,11 +19,13 @@ use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* @property Organization $organization
|
||||
@@ -33,7 +35,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|\Closure>>
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|Closure>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -97,7 +99,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
],
|
||||
'project_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -114,7 +116,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
],
|
||||
'client_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -131,7 +133,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
],
|
||||
'tag_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -152,7 +154,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
],
|
||||
'task_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -236,7 +238,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
{
|
||||
$start = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('start'), 'UTC');
|
||||
if ($start === null) {
|
||||
throw new \LogicException('Start date validation is not working');
|
||||
throw new LogicException('Start date validation is not working');
|
||||
}
|
||||
|
||||
return $start;
|
||||
@@ -246,7 +248,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
{
|
||||
$end = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('end'), 'UTC');
|
||||
if ($end === null) {
|
||||
throw new \LogicException('End date validation is not working');
|
||||
throw new LogicException('End date validation is not working');
|
||||
}
|
||||
|
||||
return $end;
|
||||
|
||||
@@ -17,6 +17,7 @@ use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -31,7 +32,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|\Closure>>
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|Closure>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -83,7 +84,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
],
|
||||
'project_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -100,7 +101,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
],
|
||||
'client_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -117,7 +118,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
],
|
||||
'tag_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -138,7 +139,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
],
|
||||
'task_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* @property Organization $organization
|
||||
@@ -29,7 +31,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|\Closure>>
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|Closure>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -68,7 +70,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
],
|
||||
'client_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -85,7 +87,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
],
|
||||
'project_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -102,7 +104,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
],
|
||||
'tag_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -123,7 +125,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
],
|
||||
'task_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -200,7 +202,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
{
|
||||
$start = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('start'), 'UTC');
|
||||
if ($start === null) {
|
||||
throw new \LogicException('Start date validation is not working');
|
||||
throw new LogicException('Start date validation is not working');
|
||||
}
|
||||
|
||||
return $start;
|
||||
@@ -210,7 +212,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
{
|
||||
$end = Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('end'), 'UTC');
|
||||
if ($end === null) {
|
||||
throw new \LogicException('End date validation is not working');
|
||||
throw new LogicException('End date validation is not working');
|
||||
}
|
||||
|
||||
return $end;
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\Rule as RuleContract;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -29,7 +30,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule|RuleContract|\Closure>>
|
||||
* @return array<string, array<string|ValidationRule|RuleContract|Closure>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -61,7 +62,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
],
|
||||
'client_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -78,7 +79,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
],
|
||||
'project_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -95,7 +96,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
],
|
||||
'tag_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
@@ -116,7 +117,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
],
|
||||
'task_ids.*' => [
|
||||
'string',
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Service\PermissionStore;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -26,7 +27,7 @@ class TimeEntryStoreRequest extends BaseFormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|\Closure|ValidationRule|\Illuminate\Contracts\Validation\Rule>>
|
||||
* @return array<string, array<string|Closure|ValidationRule|\Illuminate\Contracts\Validation\Rule>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -94,7 +95,7 @@ class TimeEntryStoreRequest extends BaseFormRequest
|
||||
// Type of the time entry (work time or a break)
|
||||
'type' => [
|
||||
Rule::enum(TimeEntryType::class),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value === TimeEntryType::Break->value && ! $this->organization->breaks_enabled) {
|
||||
$fail('Breaks are disabled for this organization.');
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Service\PermissionStore;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@@ -29,7 +30,7 @@ class TimeEntryUpdateRequest extends BaseFormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|\Closure|ValidationRule|\Illuminate\Contracts\Validation\Rule|ProhibitedIf|ConditionalRules>>
|
||||
* @return array<string, array<string|Closure|ValidationRule|\Illuminate\Contracts\Validation\Rule|ProhibitedIf|ConditionalRules>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -105,7 +106,7 @@ class TimeEntryUpdateRequest extends BaseFormRequest
|
||||
// Type of the time entry (work time or a break)
|
||||
'type' => [
|
||||
Rule::enum(TimeEntryType::class),
|
||||
function (string $attribute, mixed $value, \Closure $fail) use ($timeEntry): void {
|
||||
function (string $attribute, mixed $value, Closure $fail) use ($timeEntry): void {
|
||||
// While breaks are disabled, entries that already are breaks may stay
|
||||
// breaks, but converting a work entry to a break is not allowed.
|
||||
if ($value === TimeEntryType::Break->value
|
||||
|
||||
@@ -38,7 +38,10 @@ use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
|
||||
* @property string|null $pending_email
|
||||
* @property Carbon|null $email_verified_at
|
||||
* @property string|null $password
|
||||
* @property string|null $remember_token
|
||||
* @property string|null $two_factor_secret
|
||||
* @property string|null $two_factor_recovery_codes
|
||||
* @property Carbon|null $two_factor_confirmed_at
|
||||
* @property string $timezone
|
||||
* @property bool $is_placeholder
|
||||
* @property Weekday $week_start
|
||||
@@ -150,7 +153,9 @@ class User extends Authenticatable implements AuditableContract, FilamentUser, M
|
||||
|
||||
public function canAccessPanel(Panel $panel): bool
|
||||
{
|
||||
return in_array($this->email, config('auth.super_admins', []), true) && $this->hasVerifiedEmail();
|
||||
return $this->is_placeholder === false
|
||||
&& in_array($this->email, config('auth.super_admins', []), true)
|
||||
&& $this->hasVerifiedEmail();
|
||||
}
|
||||
|
||||
public function isMemberOfOrganization(Organization $organization): bool
|
||||
|
||||
@@ -25,7 +25,7 @@ use Dedoc\Scramble\Scramble;
|
||||
use Dedoc\Scramble\Support\Generator\OpenApi;
|
||||
use Dedoc\Scramble\Support\Generator\SecurityScheme;
|
||||
use Dedoc\Scramble\Support\Generator\SecuritySchemes\OAuthFlow;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
@@ -4,11 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Auth\ActiveUserProvider;
|
||||
use App\Models\Passport\AuthCode;
|
||||
use App\Models\Passport\Client;
|
||||
use App\Models\Passport\RefreshToken;
|
||||
use App\Models\Passport\Token;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Passport\Passport;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
@@ -26,6 +29,13 @@ class AuthServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// Replaces the built-in eloquent user provider, so that no authentication flow can
|
||||
// resolve a placeholder user. The driver name is kept, because Passport recognizes
|
||||
// only providers that are configured with the driver "eloquent".
|
||||
Auth::provider('eloquent', function (Application $app, array $config): ActiveUserProvider {
|
||||
return new ActiveUserProvider($app->make('hash'), $config['model']);
|
||||
});
|
||||
|
||||
// define scopes for passport tokens
|
||||
Passport::tokensCan([
|
||||
'create' => 'Create resources',
|
||||
|
||||
@@ -13,7 +13,7 @@ use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Navigation\NavigationGroup;
|
||||
use Filament\Pages;
|
||||
use Filament\Pages\Dashboard;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
@@ -41,7 +41,7 @@ class AdminPanelProvider extends PanelProvider
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
Pages\Dashboard::class,
|
||||
Dashboard::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->widgets([
|
||||
@@ -51,7 +51,6 @@ class AdminPanelProvider extends PanelProvider
|
||||
TimeEntriesCreated::class,
|
||||
TimeEntriesImported::class,
|
||||
])
|
||||
->viteTheme('resources/css/filament/admin/theme.css')
|
||||
->plugins([
|
||||
EnvironmentIndicatorPlugin::make()
|
||||
->color(fn () => match (App::environment()) {
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Log;
|
||||
use Throwable;
|
||||
|
||||
class ApiService
|
||||
{
|
||||
@@ -42,7 +43,7 @@ class ApiService
|
||||
|
||||
return null;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
Log::warning('Failed to check for update', [
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
@@ -16,6 +16,7 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
|
||||
class DashboardService
|
||||
{
|
||||
@@ -101,7 +102,7 @@ class DashboardService
|
||||
$value1 = Carbon::createFromFormat('Y-m-d', $possibleDates->first(), $timeZone);
|
||||
$value2 = Carbon::createFromFormat('Y-m-d', $possibleDates->last(), $timeZone);
|
||||
if ($value2 === null || $value1 === null) {
|
||||
throw new \RuntimeException('Provided date is not valid');
|
||||
throw new RuntimeException('Provided date is not valid');
|
||||
}
|
||||
if ($value1->gt($value2)) {
|
||||
$last = $value1;
|
||||
|
||||
@@ -17,6 +17,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ReportPropertiesDto implements Castable
|
||||
{
|
||||
@@ -101,15 +102,15 @@ class ReportPropertiesDto implements Castable
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): ReportPropertiesDto
|
||||
{
|
||||
if (! is_string($value)) {
|
||||
throw new \InvalidArgumentException('The given value is not a string');
|
||||
throw new InvalidArgumentException('The given value is not a string');
|
||||
}
|
||||
$data = json_decode($value, false);
|
||||
if ($data === null) {
|
||||
throw new \InvalidArgumentException('The given value is not a JSON string');
|
||||
throw new InvalidArgumentException('The given value is not a JSON string');
|
||||
}
|
||||
foreach (self::REQUIRED_PROPERTIES as $property) {
|
||||
if (! property_exists($data, $property)) {
|
||||
throw new \InvalidArgumentException('The given JSON string does not contain the required property "'.$property.'"');
|
||||
throw new InvalidArgumentException('The given JSON string does not contain the required property "'.$property.'"');
|
||||
}
|
||||
}
|
||||
$dto = new ReportPropertiesDto;
|
||||
@@ -145,7 +146,7 @@ class ReportPropertiesDto implements Castable
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): string
|
||||
{
|
||||
if (! ($value instanceof ReportPropertiesDto)) {
|
||||
throw new \InvalidArgumentException('The given value is not an instance of ReportPropertiesDto');
|
||||
throw new InvalidArgumentException('The given value is not an instance of ReportPropertiesDto');
|
||||
}
|
||||
|
||||
$data = (object) [
|
||||
@@ -171,7 +172,7 @@ class ReportPropertiesDto implements Castable
|
||||
|
||||
$jsonString = json_encode($data);
|
||||
if ($jsonString === false) {
|
||||
throw new \InvalidArgumentException('Could not encode the given data to a JSON string');
|
||||
throw new InvalidArgumentException('Could not encode the given data to a JSON string');
|
||||
}
|
||||
|
||||
return $jsonString;
|
||||
@@ -188,10 +189,10 @@ class ReportPropertiesDto implements Castable
|
||||
$collection = new Collection;
|
||||
foreach ($ids as $id) {
|
||||
if (! is_string($id)) {
|
||||
throw new \InvalidArgumentException('The given ID is not a string');
|
||||
throw new InvalidArgumentException('The given ID is not a string');
|
||||
}
|
||||
if ($id !== TimeEntryFilter::NONE_VALUE && ! Str::isUuid($id)) {
|
||||
throw new \InvalidArgumentException('The given ID is not a valid UUID');
|
||||
throw new InvalidArgumentException('The given ID is not a valid UUID');
|
||||
}
|
||||
$collection->push($id);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @template TModel of Model
|
||||
@@ -133,7 +134,7 @@ class ImportDatabaseHelper
|
||||
{
|
||||
$jsonData = json_encode($data);
|
||||
if ($jsonData === false) {
|
||||
throw new \RuntimeException('Failed to encode data to JSON');
|
||||
throw new RuntimeException('Failed to encode data to JSON');
|
||||
}
|
||||
|
||||
return md5($jsonData);
|
||||
@@ -164,7 +165,7 @@ class ImportDatabaseHelper
|
||||
|
||||
return $this->createEntity($identifierData, $createValues, $externalIdentifier);
|
||||
} else {
|
||||
throw new \RuntimeException('Not implemented');
|
||||
throw new RuntimeException('Not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,8 @@ use App\Service\Import\Importers\ImporterContract;
|
||||
use App\Service\Import\Importers\ImporterProvider;
|
||||
use App\Service\Import\Importers\ImportException;
|
||||
use App\Service\Import\Importers\ReportDto;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ImportService
|
||||
{
|
||||
@@ -25,8 +22,6 @@ class ImportService
|
||||
/** @var ImporterContract $importer */
|
||||
$importer = app(ImporterProvider::class)->getImporter($importerType);
|
||||
$importer->init($organization);
|
||||
Storage::disk(config('filesystems.default'))
|
||||
->put('import/'.Carbon::now()->toDateString().'-'.$organization->getKey().'-'.Str::uuid(), $data);
|
||||
|
||||
$lock = Cache::lock('import:'.$organization->getKey(), config('octane.max_execution_time', 60) + 1);
|
||||
|
||||
|
||||
@@ -9,13 +9,14 @@ use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Csv\Exception as CsvException;
|
||||
use League\Csv\Reader;
|
||||
use Override;
|
||||
|
||||
class ClockifyProjectsImporter extends DefaultImporter
|
||||
{
|
||||
/**
|
||||
* @throws ImportException
|
||||
*/
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function importData(string $data, string $timezone): void
|
||||
{
|
||||
try {
|
||||
@@ -129,13 +130,13 @@ class ClockifyProjectsImporter extends DefaultImporter
|
||||
return $billableRateKey;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.clockify_projects.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.clockify_projects.description');
|
||||
|
||||
@@ -15,6 +15,7 @@ use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Csv\Exception as CsvException;
|
||||
use League\Csv\Reader;
|
||||
use Override;
|
||||
|
||||
class ClockifyTimeEntriesImporter extends DefaultImporter
|
||||
{
|
||||
@@ -44,7 +45,7 @@ class ClockifyTimeEntriesImporter extends DefaultImporter
|
||||
/**
|
||||
* @throws ImportException
|
||||
*/
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function importData(string $data, string $timezone): void
|
||||
{
|
||||
try {
|
||||
@@ -262,13 +263,13 @@ class ClockifyTimeEntriesImporter extends DefaultImporter
|
||||
return null;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.clockify_time_entries.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.clockify_time_entries.description');
|
||||
|
||||
@@ -18,6 +18,7 @@ use App\Service\ColorService;
|
||||
use App\Service\Import\ImportDatabaseHelper;
|
||||
use App\Service\TimezoneService;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Override;
|
||||
|
||||
abstract class DefaultImporter implements ImporterContract
|
||||
{
|
||||
@@ -181,7 +182,7 @@ abstract class DefaultImporter implements ImporterContract
|
||||
$this->billableRateService = app(BillableRateService::class);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getReport(): ReportDto
|
||||
{
|
||||
return new ReportDto(
|
||||
|
||||
@@ -14,6 +14,7 @@ use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Csv\Exception as CsvException;
|
||||
use League\Csv\Reader;
|
||||
use Override;
|
||||
|
||||
class GenericTimeEntriesImporter extends DefaultImporter
|
||||
{
|
||||
@@ -59,7 +60,7 @@ class GenericTimeEntriesImporter extends DefaultImporter
|
||||
/**
|
||||
* @throws ImportException
|
||||
*/
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function importData(string $data, string $timezone): void
|
||||
{
|
||||
try {
|
||||
@@ -194,13 +195,13 @@ class GenericTimeEntriesImporter extends DefaultImporter
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.generic_time_entries.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.generic_time_entries.description');
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace App\Service\Import\Importers;
|
||||
use Exception;
|
||||
use League\Csv\Exception as CsvException;
|
||||
use League\Csv\Reader;
|
||||
use Override;
|
||||
|
||||
class HarvestClientsImporter extends DefaultImporter
|
||||
{
|
||||
@@ -20,7 +21,7 @@ class HarvestClientsImporter extends DefaultImporter
|
||||
/**
|
||||
* @throws ImportException
|
||||
*/
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function importData(string $data, string $timezone): void
|
||||
{
|
||||
try {
|
||||
@@ -62,13 +63,13 @@ class HarvestClientsImporter extends DefaultImporter
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.harvest_clients.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.harvest_clients.description');
|
||||
|
||||
@@ -8,6 +8,7 @@ use Exception;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Csv\Exception as CsvException;
|
||||
use League\Csv\Reader;
|
||||
use Override;
|
||||
|
||||
class HarvestProjectsImporter extends DefaultImporter
|
||||
{
|
||||
@@ -24,7 +25,7 @@ class HarvestProjectsImporter extends DefaultImporter
|
||||
/**
|
||||
* @throws ImportException
|
||||
*/
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function importData(string $data, string $timezone): void
|
||||
{
|
||||
try {
|
||||
@@ -93,13 +94,13 @@ class HarvestProjectsImporter extends DefaultImporter
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.harvest_projects.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.harvest_projects.description');
|
||||
|
||||
@@ -4,4 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Import\Importers;
|
||||
|
||||
class ImportException extends \Exception {}
|
||||
use Exception;
|
||||
|
||||
class ImportException extends Exception {}
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Import\Importers;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ImporterProvider
|
||||
{
|
||||
/**
|
||||
@@ -49,7 +51,7 @@ class ImporterProvider
|
||||
public function getImporter(string $type): ImporterContract
|
||||
{
|
||||
if (! array_key_exists($type, $this->importers)) {
|
||||
throw new \InvalidArgumentException('Invalid importer type');
|
||||
throw new InvalidArgumentException('Invalid importer type');
|
||||
}
|
||||
|
||||
return new $this->importers[$type];
|
||||
|
||||
@@ -16,7 +16,6 @@ use Illuminate\Support\Str;
|
||||
use League\Csv\Reader;
|
||||
use Override;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
use ZipArchive;
|
||||
|
||||
class SolidtimeImporter extends DefaultImporter
|
||||
{
|
||||
@@ -34,16 +33,10 @@ class SolidtimeImporter extends DefaultImporter
|
||||
$temporaryDirectoryZip = null;
|
||||
$temporaryDirectory = null;
|
||||
try {
|
||||
$zip = new ZipArchive;
|
||||
$temporaryDirectoryZip = TemporaryDirectory::make();
|
||||
file_put_contents($temporaryDirectoryZip->path('import.zip'), $data);
|
||||
$res = $zip->open($temporaryDirectoryZip->path('import.zip'), ZipArchive::RDONLY);
|
||||
if ($res !== true) {
|
||||
throw new ImportException('Invalid ZIP, error code: '.$res);
|
||||
}
|
||||
$temporaryDirectory = TemporaryDirectory::make();
|
||||
$zip->extractTo($temporaryDirectory->path());
|
||||
$zip->close();
|
||||
app(ZipImportHelper::class)->extract($temporaryDirectoryZip->path('import.zip'), $temporaryDirectory->path());
|
||||
|
||||
if (! file_exists($temporaryDirectory->path('meta.json'))) {
|
||||
throw new ImportException('File "meta.json" missing in ZIP');
|
||||
|
||||
@@ -13,7 +13,6 @@ use Illuminate\Support\Str;
|
||||
use Override;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
use ValueError;
|
||||
use ZipArchive;
|
||||
|
||||
class TogglDataImporter extends DefaultImporter
|
||||
{
|
||||
@@ -26,16 +25,10 @@ class TogglDataImporter extends DefaultImporter
|
||||
$temporaryDirectoryZip = null;
|
||||
$temporaryDirectory = null;
|
||||
try {
|
||||
$zip = new ZipArchive;
|
||||
$temporaryDirectoryZip = TemporaryDirectory::make();
|
||||
file_put_contents($temporaryDirectoryZip->path('import.zip'), $data);
|
||||
$res = $zip->open($temporaryDirectoryZip->path('import.zip'), ZipArchive::RDONLY);
|
||||
if ($res !== true) {
|
||||
throw new ImportException('Invalid ZIP, error code: '.$res);
|
||||
}
|
||||
$temporaryDirectory = TemporaryDirectory::make();
|
||||
$zip->extractTo($temporaryDirectory->path());
|
||||
$zip->close();
|
||||
app(ZipImportHelper::class)->extract($temporaryDirectoryZip->path('import.zip'), $temporaryDirectory->path());
|
||||
if (! file_exists($temporaryDirectory->path('clients.json'))) {
|
||||
throw new ImportException('File "clients.json" missing in ZIP');
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Csv\Exception as CsvException;
|
||||
use League\Csv\Reader;
|
||||
use Override;
|
||||
|
||||
class TogglTimeEntriesImporter extends DefaultImporter
|
||||
{
|
||||
@@ -43,7 +44,7 @@ class TogglTimeEntriesImporter extends DefaultImporter
|
||||
/**
|
||||
* @throws ImportException
|
||||
*/
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function importData(string $data, string $timezone): void
|
||||
{
|
||||
try {
|
||||
@@ -192,13 +193,13 @@ class TogglTimeEntriesImporter extends DefaultImporter
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.toggl_time_entries.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
#[Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.toggl_time_entries.description');
|
||||
|
||||
129
app/Service/Import/Importers/ZipImportHelper.php
Normal file
129
app/Service/Import/Importers/ZipImportHelper.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Import\Importers;
|
||||
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
* Extracts uploaded ZIP archives with limits on file count, total uncompressed
|
||||
* size and entry paths, so a small malicious archive can not fill the disk
|
||||
* (decompression bomb) or write outside the target directory (zip slip).
|
||||
*/
|
||||
class ZipImportHelper
|
||||
{
|
||||
private const int CHUNK_SIZE = 1024 * 1024;
|
||||
|
||||
/**
|
||||
* @throws ImportException
|
||||
*/
|
||||
public function extract(string $zipPath, string $targetPath): void
|
||||
{
|
||||
$zip = new ZipArchive;
|
||||
$res = $zip->open($zipPath, ZipArchive::RDONLY);
|
||||
if ($res !== true) {
|
||||
throw new ImportException('Invalid ZIP, error code: '.$res);
|
||||
}
|
||||
|
||||
try {
|
||||
$maxFiles = (int) config('import.zip_max_files');
|
||||
$maxUncompressedSize = (int) config('import.zip_max_uncompressed_size');
|
||||
|
||||
if ($zip->numFiles > $maxFiles) {
|
||||
throw new ImportException('ZIP contains too many files, maximum is '.$maxFiles);
|
||||
}
|
||||
|
||||
// Check the sizes declared in the archive before writing anything to disk
|
||||
$declaredSize = 0;
|
||||
for ($index = 0; $index < $zip->numFiles; $index++) {
|
||||
$stat = $zip->statIndex($index);
|
||||
if ($stat === false) {
|
||||
throw new ImportException('Invalid ZIP entry');
|
||||
}
|
||||
$this->validateEntryName($stat['name']);
|
||||
$declaredSize += $stat['size'];
|
||||
if ($declaredSize > $maxUncompressedSize) {
|
||||
throw new ImportException('ZIP uncompressed size exceeds the maximum of '.$maxUncompressedSize.' bytes');
|
||||
}
|
||||
}
|
||||
|
||||
// The declared sizes can be forged, so the written bytes are counted as well
|
||||
$writtenSize = 0;
|
||||
for ($index = 0; $index < $zip->numFiles; $index++) {
|
||||
$stat = $zip->statIndex($index);
|
||||
if ($stat === false) {
|
||||
throw new ImportException('Invalid ZIP entry');
|
||||
}
|
||||
$name = $stat['name'];
|
||||
$entryPath = $targetPath.DIRECTORY_SEPARATOR.$name;
|
||||
|
||||
if (str_ends_with($name, '/')) {
|
||||
$this->ensureDirectoryExists($entryPath);
|
||||
|
||||
continue;
|
||||
}
|
||||
$this->ensureDirectoryExists(dirname($entryPath));
|
||||
|
||||
$stream = $zip->getStreamIndex($index);
|
||||
if ($stream === false) {
|
||||
throw new ImportException('ZIP entry "'.$name.'" can not be read');
|
||||
}
|
||||
$target = fopen($entryPath, 'wb');
|
||||
if ($target === false) {
|
||||
fclose($stream);
|
||||
throw new ImportException('ZIP entry "'.$name.'" can not be extracted');
|
||||
}
|
||||
try {
|
||||
while (! feof($stream)) {
|
||||
$chunk = fread($stream, self::CHUNK_SIZE);
|
||||
if ($chunk === false) {
|
||||
throw new ImportException('ZIP entry "'.$name.'" can not be read');
|
||||
}
|
||||
$writtenSize += strlen($chunk);
|
||||
if ($writtenSize > $maxUncompressedSize) {
|
||||
throw new ImportException('ZIP uncompressed size exceeds the maximum of '.$maxUncompressedSize.' bytes');
|
||||
}
|
||||
fwrite($target, $chunk);
|
||||
}
|
||||
} finally {
|
||||
fclose($target);
|
||||
fclose($stream);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$zip->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ImportException
|
||||
*/
|
||||
private function validateEntryName(string $name): void
|
||||
{
|
||||
if ($name === '' || str_contains($name, "\0") || str_contains($name, '\\') || str_starts_with($name, '/')) {
|
||||
throw new ImportException('ZIP contains an invalid file path: "'.$name.'"');
|
||||
}
|
||||
if (preg_match('/^[a-zA-Z]:/', $name) === 1) {
|
||||
throw new ImportException('ZIP contains an invalid file path: "'.$name.'"');
|
||||
}
|
||||
foreach (explode('/', rtrim($name, '/')) as $segment) {
|
||||
if ($segment === '' || $segment === '..') {
|
||||
throw new ImportException('ZIP contains an invalid file path: "'.$name.'"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ImportException
|
||||
*/
|
||||
private function ensureDirectoryExists(string $path): void
|
||||
{
|
||||
if (is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
if (! mkdir($path, 0700, true) && ! is_dir($path)) {
|
||||
throw new ImportException('Directory "'.$path.'" can not be created');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,7 +218,16 @@ class MemberService
|
||||
|
||||
$placeholderUser = $user->replicate();
|
||||
$placeholderUser->is_placeholder = true;
|
||||
$placeholderUser->current_team_id = $member->organization_id;
|
||||
// Reset authentication relevant properties on the placeholder user
|
||||
$placeholderUser->password = null;
|
||||
$placeholderUser->remember_token = null;
|
||||
$placeholderUser->two_factor_secret = null;
|
||||
$placeholderUser->two_factor_recovery_codes = null;
|
||||
$placeholderUser->two_factor_confirmed_at = null;
|
||||
$placeholderUser->email_verified_at = null;
|
||||
$placeholderUser->pending_email = null;
|
||||
$placeholderUser->current_team_id = null;
|
||||
$placeholderUser->profile_photo_path = null;
|
||||
$placeholderUser->save();
|
||||
|
||||
$member->user()->associate($placeholderUser);
|
||||
|
||||
@@ -14,6 +14,7 @@ use League\Csv\CannotInsertRecord;
|
||||
use League\Csv\Exception;
|
||||
use League\Csv\UnavailableStream;
|
||||
use League\Csv\Writer;
|
||||
use LogicException;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
|
||||
/**
|
||||
@@ -116,7 +117,7 @@ abstract class CsvExport
|
||||
private function validateRow(array $row): void
|
||||
{
|
||||
if (array_keys($row) !== static::HEADER) {
|
||||
throw new \LogicException('Invalid row');
|
||||
throw new LogicException('Invalid row');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class TimeEntryAggregationService
|
||||
{
|
||||
@@ -526,7 +527,7 @@ class TimeEntryAggregationService
|
||||
public function timeSlotsBetween(Carbon $start, Carbon $end, string $timezone, Weekday $startOfWeek, TimeEntryAggregationTypeInterval $interval, string $format): Collection
|
||||
{
|
||||
if ($start->gt($end)) {
|
||||
throw new \InvalidArgumentException('Start date must be before end date');
|
||||
throw new InvalidArgumentException('Start date must be before end date');
|
||||
}
|
||||
$slots = new Collection;
|
||||
$current = $start->copy()->timezone($timezone);
|
||||
@@ -539,7 +540,7 @@ class TimeEntryAggregationService
|
||||
} elseif ($interval === TimeEntryAggregationTypeInterval::Year) {
|
||||
$current->startOfYear();
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid interval');
|
||||
throw new InvalidArgumentException('Invalid interval');
|
||||
}
|
||||
|
||||
while ($current->lt($end)) {
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Service;
|
||||
|
||||
use App\Models\User;
|
||||
use Carbon\CarbonTimeZone;
|
||||
use Exception;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -287,7 +288,7 @@ class TimezoneService
|
||||
{
|
||||
try {
|
||||
return new CarbonTimeZone($user->timezone);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
Log::error('User has a invalid timezone', [
|
||||
'user_id' => $user->getKey(),
|
||||
'timezone' => $user->timezone,
|
||||
|
||||
@@ -20,6 +20,7 @@ use App\Models\User;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class UserService
|
||||
{
|
||||
@@ -148,7 +149,7 @@ class UserService
|
||||
->whereBelongsTo($newOwner, 'user')
|
||||
->first();
|
||||
if ($userMembership === null) {
|
||||
throw new \InvalidArgumentException('User is not a member of the organization');
|
||||
throw new InvalidArgumentException('User is not a member of the organization');
|
||||
}
|
||||
$userMembership->role = Role::Owner->value;
|
||||
$userMembership->save();
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use finfo;
|
||||
use Symfony\Component\Mime\MimeTypes;
|
||||
|
||||
class Base64File
|
||||
@@ -27,7 +28,7 @@ class Base64File
|
||||
return null;
|
||||
}
|
||||
|
||||
$mimeType = (new \finfo(FILEINFO_MIME_TYPE))->buffer($decoded);
|
||||
$mimeType = (new finfo(FILEINFO_MIME_TYPE))->buffer($decoded);
|
||||
if ($mimeType === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
"require": {
|
||||
"php": "8.3.*",
|
||||
"ext-zip": "*",
|
||||
"brick/money": "^0.10.0",
|
||||
"brick/money": "^0.11.2",
|
||||
"datomatic/laravel-enum-helper": "^2.0.0",
|
||||
"dedoc/scramble": "^0.12.2",
|
||||
"filament/filament": "^3.2",
|
||||
"flowframe/laravel-trend": "^0.4.0",
|
||||
"dedoc/scramble": "^0.13.43",
|
||||
"filament/filament": "^5.0",
|
||||
"flowframe/laravel-trend": "^0.5.0",
|
||||
"gotenberg/gotenberg-php": "^2.8",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"inertiajs/inertia-laravel": "^2.0.3",
|
||||
"inertiajs/inertia-laravel": "^3.3.4",
|
||||
"korridor/laravel-computed-attributes": "^3.1",
|
||||
"korridor/laravel-has-many-sync": "^3.1",
|
||||
"korridor/laravel-model-validation-rules": "^3.0",
|
||||
@@ -26,15 +26,15 @@
|
||||
"league/csv": "^9.16.0",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"league/iso3166": "^4.3",
|
||||
"maatwebsite/excel": "^3.1",
|
||||
"maatwebsite/excel": "^4.0.3",
|
||||
"mobiledetect/mobiledetectlib": "^4.11",
|
||||
"novadaemon/filament-pretty-json": "^2.2",
|
||||
"nwidart/laravel-modules": "^12.0.4",
|
||||
"novadaemon/filament-pretty-json": "^4.0",
|
||||
"nwidart/laravel-modules": "^13.0.0",
|
||||
"owen-it/laravel-auditing": "^14.0.0",
|
||||
"pxlrbt/filament-environment-indicator": "^2.1.0",
|
||||
"pxlrbt/filament-environment-indicator": "^3.0",
|
||||
"spatie/temporary-directory": "^2.2",
|
||||
"staudenmeir/eloquent-json-relations": "^1.1",
|
||||
"stechstudio/filament-impersonate": "^3.8",
|
||||
"stechstudio/filament-impersonate": "^5.0",
|
||||
"tightenco/ziggy": "^2.1.0",
|
||||
"tpetry/laravel-postgresql-enhanced": "^3.0.0",
|
||||
"wikimedia/composer-merge-plugin": "^2.1.0"
|
||||
|
||||
3120
composer.lock
generated
3120
composer.lock
generated
File diff suppressed because it is too large
Load Diff
34
config/import.php
Normal file
34
config/import.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Import payload limit
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Maximum length of the base64 encoded "data" field of an import request in
|
||||
| bytes. Requests with a larger payload are rejected with a validation error.
|
||||
|
|
||||
*/
|
||||
|
||||
'max_data_size' => (int) (env('IMPORT_MAX_DATA_SIZE') ?: 50 * 1024 * 1024),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ZIP extraction limits
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Limits applied to ZIP based importers before and during extraction to
|
||||
| protect the instance against decompression bombs. The uncompressed size
|
||||
| is the sum of all files in the archive in bytes.
|
||||
|
|
||||
*/
|
||||
|
||||
'zip_max_files' => (int) (env('IMPORT_ZIP_MAX_FILES') ?: 100),
|
||||
|
||||
'zip_max_uncompressed_size' => (int) (env('IMPORT_ZIP_MAX_UNCOMPRESSED_SIZE') ?: 500 * 1024 * 1024),
|
||||
|
||||
];
|
||||
208
config/inertia.php
Normal file
208
config/inertia.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Server Side Rendering
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configures if and how Inertia uses Server Side Rendering
|
||||
| to pre-render the initial visits made to your application's pages.
|
||||
|
|
||||
| You can specify a custom SSR bundle path, or omit it to let Inertia
|
||||
| try and automatically detect it for you.
|
||||
|
|
||||
| Do note that enabling these options will NOT automatically make SSR work,
|
||||
| as a separate rendering service needs to be available. To learn more,
|
||||
| please visit https://inertiajs.com/server-side-rendering
|
||||
|
|
||||
*/
|
||||
|
||||
'ssr' => [
|
||||
|
||||
'enabled' => (bool) env('INERTIA_SSR_ENABLED', true),
|
||||
|
||||
'runtime' => env('INERTIA_SSR_RUNTIME', 'node'),
|
||||
|
||||
'ensure_runtime_exists' => (bool) env('INERTIA_SSR_ENSURE_RUNTIME_EXISTS', false),
|
||||
|
||||
'url' => env('INERTIA_SSR_URL', 'http://127.0.0.1:13714'),
|
||||
|
||||
'hot_url' => env('INERTIA_SSR_HOT_URL'),
|
||||
|
||||
'ensure_bundle_exists' => (bool) env('INERTIA_SSR_ENSURE_BUNDLE_EXISTS', true),
|
||||
|
||||
// 'bundle' => base_path('bootstrap/ssr/ssr.mjs'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| SSR Error Handling
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When SSR rendering fails, Inertia gracefully falls back to client-side
|
||||
| rendering. Set throw_on_error to true to throw an exception instead.
|
||||
| This is useful for E2E testing where you want SSR errors to fail loudly.
|
||||
|
|
||||
| You can also listen for the Inertia\Ssr\SsrRenderFailed event to handle
|
||||
| failures in your own way (e.g., logging, error tracking service).
|
||||
|
|
||||
*/
|
||||
|
||||
'throw_on_error' => (bool) env('INERTIA_SSR_THROW_ON_ERROR', false),
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pages
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set `ensure_pages_exist` to true if you want to enforce that Inertia page
|
||||
| components exist on disk when rendering a page. This is useful for
|
||||
| catching missing or misnamed components.
|
||||
|
|
||||
| The `paths` and `extensions` options define where to look for page
|
||||
| components and which file extensions to consider.
|
||||
|
|
||||
*/
|
||||
|
||||
'pages' => [
|
||||
|
||||
'ensure_pages_exist' => false,
|
||||
|
||||
'paths' => [
|
||||
|
||||
resource_path('js/Pages'),
|
||||
base_path('extensions/Invoicing/resources/js/Pages'),
|
||||
|
||||
],
|
||||
|
||||
'extensions' => [
|
||||
|
||||
'js',
|
||||
'jsx',
|
||||
'svelte',
|
||||
'ts',
|
||||
'tsx',
|
||||
'vue',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Testing
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using `assertInertia`, the assertion attempts to locate the
|
||||
| component as a file relative to the `pages.paths` AND with any of
|
||||
| the `pages.extensions` specified above.
|
||||
|
|
||||
| You can disable this behavior by setting `ensure_pages_exist`
|
||||
| to false.
|
||||
|
|
||||
*/
|
||||
|
||||
'testing' => [
|
||||
|
||||
'ensure_pages_exist' => true,
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expose Shared Prop Keys
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When enabled, each page response includes a `sharedProps` metadata key
|
||||
| listing the top-level prop keys that were registered via `Inertia::share`.
|
||||
| The frontend can use this to carry shared props over during instant visits.
|
||||
|
|
||||
*/
|
||||
|
||||
'expose_shared_prop_keys' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| History
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Enable `encrypt` to encrypt page data before it is stored in the
|
||||
| browser's history state, preventing sensitive information from
|
||||
| being accessible after logout. Can also be enabled per-request
|
||||
| or via the `inertia.encrypt` middleware.
|
||||
|
|
||||
*/
|
||||
|
||||
'history' => [
|
||||
|
||||
'encrypt' => (bool) env('INERTIA_ENCRYPT_HISTORY', false),
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| DevTools
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Records one entry per request to disk so the DevTools Chrome extension may
|
||||
| read it back over HTTP. Recording is limited to your local environment.
|
||||
| See https://inertiajs.com/docs/devtools for the gate and storage options.
|
||||
|
|
||||
*/
|
||||
|
||||
'devtools' => [
|
||||
|
||||
'enabled' => env('INERTIA_DEVTOOLS_ENABLED'),
|
||||
|
||||
'except' => ['telescope*', 'horizon*', '_inertia/devtools*'],
|
||||
|
||||
'storage' => [
|
||||
|
||||
'path' => storage_path('inertia-devtools'),
|
||||
|
||||
'ttl' => (int) env('INERTIA_DEVTOOLS_TTL_HOURS', 24),
|
||||
|
||||
'prune_interval' => (int) env('INERTIA_DEVTOOLS_PRUNE_INTERVAL_SECONDS', 300),
|
||||
|
||||
'limit' => (int) env('INERTIA_DEVTOOLS_LIMIT', 100),
|
||||
|
||||
],
|
||||
|
||||
'middleware' => ['web'],
|
||||
|
||||
'gate' => env('INERTIA_DEVTOOLS_GATE'),
|
||||
|
||||
'redact' => [
|
||||
|
||||
'keys' => [
|
||||
'password',
|
||||
'password_confirmation',
|
||||
'current_password',
|
||||
'token',
|
||||
'_token',
|
||||
'access_token',
|
||||
'refresh_token',
|
||||
'secret',
|
||||
'client_secret',
|
||||
'api_key',
|
||||
],
|
||||
|
||||
'headers' => [
|
||||
'cookie',
|
||||
'set-cookie',
|
||||
'authorization',
|
||||
'proxy-authorization',
|
||||
'x-xsrf-token',
|
||||
'x-csrf-token',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Placeholder users used to be created as a full copy of the user they were made from,
|
||||
* which included the credentials and the account state of that user. A placeholder is a
|
||||
* stand-in for a person in one organization, not an account, and the row shares the email
|
||||
* address with the real account, so these values are removed from the placeholders that
|
||||
* already exist. The organization a placeholder belongs to is recorded on its member row.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('users')
|
||||
->where('is_placeholder', '=', true)
|
||||
->where(function (Builder $builder): void {
|
||||
$builder->whereNotNull('password')
|
||||
->orWhereNotNull('remember_token')
|
||||
->orWhereNotNull('two_factor_secret')
|
||||
->orWhereNotNull('two_factor_recovery_codes')
|
||||
->orWhereNotNull('two_factor_confirmed_at')
|
||||
->orWhereNotNull('email_verified_at')
|
||||
->orWhereNotNull('pending_email')
|
||||
->orWhereNotNull('current_team_id')
|
||||
->orWhereNotNull('profile_photo_path');
|
||||
})
|
||||
->update([
|
||||
'password' => null,
|
||||
'remember_token' => null,
|
||||
'two_factor_secret' => null,
|
||||
'two_factor_recovery_codes' => null,
|
||||
'two_factor_confirmed_at' => null,
|
||||
'email_verified_at' => null,
|
||||
'pending_email' => null,
|
||||
'current_team_id' => null,
|
||||
'profile_photo_path' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
1
public/fonts/filament/filament/inter/index.css
Normal file
1
public/fonts/filament/filament/inter/index.css
Normal file
@@ -0,0 +1 @@
|
||||
@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-ext-wght-normal-SP7Z6XGK.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-wght-normal-DR6K5BQD.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-ext-wght-normal-A2J6H3EX.woff2") format("woff2-variations");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-wght-normal-ZABHMKQG.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-vietnamese-wght-normal-VWEHJHBA.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-ext-wght-normal-ZIT2UBIY.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-wght-normal-DIHYUR35.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user