mohamedsabil83 / filament-hijri-picker

A Hijri datetime picker component for Filament
MIT License
21 stars 5 forks source link

[Bug]: Date Selection in HijriDatePicker not working as expected #22

Open CodeDanger opened 2 weeks ago

CodeDanger commented 2 weeks ago

What happened?

so whenever i try to set hijri date picker to live mode and give it some after state updated there is 2 problem appears first one is assume today one of these days [21,22,23,24,25] some days depend in month day count 30 or 29 if you hover with mouse over todays date then go again to the end date hover it the current month will be automaticly switched to the next month i have attached video for this process so you can understand

https://github.com/user-attachments/assets/4ba3d050-3909-47a5-97d4-88d95dc9e56e

the second point is when ever try to select last day in current month it doesn't selected instead it select the next day which is the first date in the next month

here is specific part of code causes error ` // Hijri Date

                    Section::make([
                        HijriDatePicker::make('start_date_hijri')
                            ->label(__('تاريخ بدء العقد'))
                            ->required()
                            ->columnSpan(1)
                            ->format('Y-m-d')
                            ->displayFormat('Y-m-d')
                            ->native(false)
                            ->default(toHijriDatePicker(null))
                            ->closeOnDateSelection()
                            ->afterStateUpdated(fn ($state, callable $set)=> $set('start_date',toGregoryDatePicker($state)))
                            ->live(onBlur:true),
                        HijriDatePicker::make('end_date_hijri')
                            ->label(__('تاريخ انتهاء العقد'))
                            ->required()
                            ->native(false)
                            ->columnSpan(1)
                            ->format('Y-m-d')
                            ->displayFormat('Y-m-d')
                            ->default(toHijri()->addYear())
                            ->closeOnDateSelection()
                            ->afterStateUpdated(fn ($state, callable $set)=> $set('end_date',toGregoryDatePicker($state)))
                            ->live(onBlur:true),
                    ])
                    ->heading(__('التاريخ الهجري'))
                    ->columnSpan(1)
                    ->columns(2),

                    // Gorgeian Date
                    Section::make([
                        DatePicker::make('start_date')
                            ->label(__('تاريخ بدء العقد'))
                            // day-month-year
                            // ->format('D-m-Y')
                            ->required()
                            ->columnSpan(1)
                            ->native(false)
                            ->displayFormat('Y-m-d')
                            ->format('Y-m-d')
                            ->closeOnDateSelection()
                            ->afterStateUpdated(fn ($state, callable $set)=> $set('start_date_hijri',toHijriDatePicker($state)))
                            ->afterStateHydrated(fn (callable $set,callable $get,?string $context)=>$context!=='create'?  $set('start_date_hijri',toHijriDatePicker($get('start_date'))):null)
                            ->default(fn(callable $get)=>toGregoryDatePicker($get('start_date_hijri')))
                            ->live(onBlur:true),
                        DatePicker::make('end_date')
                            ->label(__('تاريخ انتهاء العقد'))
                            ->required()
                            ->columnSpan(1)
                            ->native(false)
                            ->displayFormat('Y-m-d')
                            ->format('Y-m-d')
                            ->closeOnDateSelection()
                            ->afterStateUpdated(fn ($state, callable $set)=> $set('end_date_hijri',toHijriDatePicker($state)))
                            ->afterStateHydrated(fn (callable $set,callable $get,?string $context)=>$context!=='create'?   $set('end_date_hijri',toHijriDatePicker($get('end_date'))):null)
                            ->default(fn(callable $get)=>toGregoryDatePicker($get('end_date_hijri')))
                            ->live(onBlur:true),
                    ])
                    ->live(onBlur:true)
                    ->afterStateUpdated(fn(?string $context,callable $set,callable $get)=>ContractResource::updatePayments($context, $set, $get))
                    ->heading(__('التاريخ الميلادي'))
                    ->columnSpan(1)
                    ->columns(2),

`

and here is the full resource code

`<?php

namespace App\Filament\Resources;

use App\Filament\Actions\Tables\ExportPdfAction; use App\Filament\Exports\ContractExporter; use App\Models\Contract; use Filament\Resources\Resource; use App\Filament\Resources\ContractResource\Pages; use Filament\Forms\Form; use Filament\Forms\Components{Actions, Component, Select, DatePicker, TextInput, Textarea, Repeater, Section, Tabs}; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Actions\EditAction; use Filament\Tables\Actions\DeleteAction; use Filament\Tables\Table; use Illuminate\Validation\Rule; use Filament\Resources\Pages\CreateRecord; use Filament\Tables\Actions\Action; use Filament\Tables\Actions\ActionGroup; use MohamedSabil83\FilamentHijriPicker\Forms\Components\HijriDatePicker; use Filament\Support\Colors\Color; use Carbon\Carbon; use Carbon\CarbonInterval;

class ContractResource extends Resource { protected static ?string $model = Contract::class; protected static ?string $navigationIcon = 'heroicon-o-document-text'; protected static ?int $navigationSort = 4;

public static function getNavigationGroup(): string
{
    return __('إدارة المستاجرين');
}

public static function getNavigationLabel(): string
{
    return __('إدارة العقود');
}

public static function getPluralLabel(): string
{
    return __('العقود');
}

public static function getLabel(): string
{
    return __('عقد');
}

public static function calculateRequiredPayments($start_date, $end_date, $payment_cycle) {
    // Convert dates to Carbon instances
    $start =  Carbon::parse($start_date);
    $end = Carbon::parse($end_date);

    // Define intervals for each payment cycle
    $intervals = [
        'prepaid' => CarbonInterval::days(1), // Prepaid is considered as a single payment
        'daily' => CarbonInterval::days(1), // Daily payments
        'monthly' => CarbonInterval::months(1), // Monthly payments
        'quarterly' => CarbonInterval::months(3), // Quarterly payments
        'semi_annual' => CarbonInterval::months(6), // Semi-annual payments
        'annual' => CarbonInterval::years(1) // Annual payments
    ];

    if (!array_key_exists($payment_cycle, $intervals)) {
        return false;
    }

    $interval = $intervals[$payment_cycle];

    if ($payment_cycle === 'prepaid') {
        return 1;
    }

    $count = 0;
    $current = $start->copy();

    while ($current <= $end) {
        $count++;
        $current->add($interval);
    }

    return ceil($count);
}

public static function generatePaymentsArray($start_date, $end_date, $payment_cycle,$rent) {
    // Convert dates to Carbon instances
    $start = Carbon::parse($start_date);
    $end = Carbon::parse($end_date);

    // Define intervals for each payment cycle
    $intervals = [
        'prepaid' => CarbonInterval::days(1), // Prepaid is considered as a single payment
        'daily' => CarbonInterval::days(1), // Daily payments
        'monthly' => CarbonInterval::months(1), // Monthly payments
        'quarterly' => CarbonInterval::months(3), // Quarterly payments
        'semi_annual' => CarbonInterval::months(6), // Semi-annual payments
        'annual' => CarbonInterval::years(1) // Annual payments
    ];

    if (!array_key_exists($payment_cycle, $intervals)) {
        return false;
    }

    $interval = $intervals[$payment_cycle];

    if ($payment_cycle === 'prepaid') {
        // For prepaid, return a single payment
        return [
            [
                'date' => $start->toDateString(),
                'date_hijri' => toHijriDatePicker($start->toDateString()),
                // 'pay_date' => $start->toDateString(),
                // 'pay_date_hijri' => toHijriDatePicker($start->toDateString()),
                'amount' => $rent, // Default amount
                'paid_amount' => 0, // Default paid amount
                'status' => 'pending', // Default status
                'pay_type' => null, // Default payment type
                'details' => null // Default details
            ]
        ];
    }

    // Calculate the number of required payments
    $count = 0;
    $current = $start->copy();

    $payments = [];

    while ($current->lessThanOrEqualTo($end)) {
        $payments[] = [
            'date' => $current->toDateString(),
            'date_hijri' => toHijriDatePicker($current->toDateString()),
            // 'pay_date' => $current->toDateString(),
            // 'pay_date_hijri' => toHijriDatePicker($current->toDateString()),
            'amount' => (int)$rent, // Default amount
            'paid_amount' => 0, // Default paid amount
            'status' => 'pending', // Default status
            'pay_type' => null, // Default payment type
            'details' => null // Default details
        ];

        $current->add($interval);
    }

    return $payments;
}
public static function updatePayments($context,$set, $get)
{
    if(!$context==='create')return;
    $conditions_arr=[
        $get('start_date')!==null,
        $get('end_date')!==null,
        $get('end_date')!==null,
        $get('rent')!==null,
    ];
    if(in_array(false,$conditions_arr,true))return;
    // $payments = ContractResource::calculateRequiredPayments(
    $payments = ContractResource::generatePaymentsArray(
        $get('start_date'),
        $get('end_date'),
        $get('payment_cycle'),
        $get('rent')
    );
    // if date difference = 0 or payment cycle is not exist
    if(!$payments) return;
    $set('payments',$payments);

}

public static function form(Form $form): Form
{
    return $form
        ->schema([
            Tabs::make(__('العقد'))
                ->tabs([
                    Tabs\Tab::make(__('المعلومات الاساسية'))
                    ->schema([
                        Select::make('unit_id')
                        ->label(__('الوحدة'))
                        ->relationship('unit', 'name', function ($query) {
                            $query->free();
                        })
                        ->visibleOn(['create'])
                        ->searchable()
                        ->preload()
                        ->columnSpan(1)
                        ->required(),
                    Select::make('unit_id')
                        ->label(__('الوحدة'))
                        ->relationship('unit', 'name')
                        ->visibleOn(['edit','show'])
                        ->searchable()
                        ->preload()
                        ->columnSpan(1)
                        ->required(),
                    Select::make('tenant_id')
                        ->label(__('المستأجر'))
                        ->relationship('tenant', 'name')
                        // here search with id also 1181815315
                        ->searchable(['name','national_id'])
                        ->preload()
                        ->columnSpan(1)
                        ->required(),

                    // Hijri Date

                    Section::make([
                        HijriDatePicker::make('start_date_hijri')
                            ->label(__('تاريخ بدء العقد'))
                            ->required()
                            ->columnSpan(1)
                            ->format('Y-m-d')
                            ->displayFormat('Y-m-d')
                            ->native(false)
                            ->default(toHijriDatePicker(null))
                            ->closeOnDateSelection()
                            ->afterStateUpdated(fn ($state, callable $set)=> $set('start_date',toGregoryDatePicker($state)))
                            ->live(onBlur:true),
                        HijriDatePicker::make('end_date_hijri')
                            ->label(__('تاريخ انتهاء العقد'))
                            ->required()
                            ->native(false)
                            ->columnSpan(1)
                            ->format('Y-m-d')
                            ->displayFormat('Y-m-d')
                            ->default(toHijri()->addYear())
                            ->closeOnDateSelection()
                            ->afterStateUpdated(fn ($state, callable $set)=> $set('end_date',toGregoryDatePicker($state)))
                            ->live(onBlur:true),
                    ])
                    ->heading(__('التاريخ الهجري'))
                    ->columnSpan(1)
                    ->columns(2),

                    // Gorgeian Date
                    Section::make([
                        DatePicker::make('start_date')
                            ->label(__('تاريخ بدء العقد'))
                            // day-month-year
                            // ->format('D-m-Y')
                            ->required()
                            ->columnSpan(1)
                            ->native(false)
                            ->displayFormat('Y-m-d')
                            ->format('Y-m-d')
                            ->closeOnDateSelection()
                            ->afterStateUpdated(fn ($state, callable $set)=> $set('start_date_hijri',toHijriDatePicker($state)))
                            ->afterStateHydrated(fn (callable $set,callable $get,?string $context)=>$context!=='create'?  $set('start_date_hijri',toHijriDatePicker($get('start_date'))):null)
                            ->default(fn(callable $get)=>toGregoryDatePicker($get('start_date_hijri')))
                            ->live(onBlur:true),
                        DatePicker::make('end_date')
                            ->label(__('تاريخ انتهاء العقد'))
                            ->required()
                            ->columnSpan(1)
                            ->native(false)
                            ->displayFormat('Y-m-d')
                            ->format('Y-m-d')
                            ->closeOnDateSelection()
                            ->afterStateUpdated(fn ($state, callable $set)=> $set('end_date_hijri',toHijriDatePicker($state)))
                            ->afterStateHydrated(fn (callable $set,callable $get,?string $context)=>$context!=='create'?   $set('end_date_hijri',toHijriDatePicker($get('end_date'))):null)
                            ->default(fn(callable $get)=>toGregoryDatePicker($get('end_date_hijri')))
                            ->live(onBlur:true),
                    ])
                    ->live(onBlur:true)
                    ->afterStateUpdated(fn(?string $context,callable $set,callable $get)=>ContractResource::updatePayments($context, $set, $get))
                    ->heading(__('التاريخ الميلادي'))
                    ->columnSpan(1)
                    ->columns(2),

                    TextInput::make('rent')
                        ->label(__('الإيجار'))
                        ->numeric()
                        ->columnSpan(1)
                        ->afterStateUpdated(fn(?string $context,callable $set,callable $get)=>ContractResource::updatePayments($context, $set, $get))
                        ->required()
                        ->live(debounce:500),
                    Select::make('payment_cycle')
                        ->label(__('دورية الدفع'))
                        ->required()
                        ->options([
                            'prepaid' => __('مدفوع مسبقا'),
                            'daily' => __('يومي'),
                            'monthly' => __('شهري'),
                            'quarterly' => __('ربع سنوي'),
                            'semi_annual' => __('نصف سنوي'),
                            'annual' => __('سنوي'),
                        ])
                        ->searchable()
                        ->default('monthly')
                        ->afterStateUpdated(fn(?string $context,callable $set,callable $get)=>ContractResource::updatePayments($context, $set, $get))
                        ->live()
                        ->columnSpan(1),
                    Textarea::make('details')
                        ->label(__('تفاصيل العقد')),

                    ]),

                // payments tab
                Tabs\Tab::make(__('ادارة الدفعات'))
                ->schema([
                    Repeater::make('payments')
                    ->label(__('دفعات الإيجار'))
                    ->collapsed()
                    // ->relationship('payments')
                    ->grid(2)
                    ->itemLabel(fn (array $state): ?string => $state['date'].' / '.$state['date_hijri'] ?? null)
                    ->cloneable()
                    ->schema([
                        Section::make([
                            DatePicker::make('date')
                                ->label(__('تاريخ الدفع الميلادي'))
                                ->required()
                                ->reactive()
                                ->native(false)
                                ->displayFormat('Y-m-d')
                                ->format('Y-m-d')
                                ->afterStateUpdated(fn (callable $set, $state) => $set('date_hijri', toHijriDatePicker($state))),

                            HijriDatePicker::make('date_hijri')
                                ->label(__('تاريخ الدفع الهجري'))
                                ->required()
                                ->format('Y-m-d')
                                ->displayFormat('Y-m-d')
                                ->reactive()
                                ->afterStateUpdated(fn (callable $set, $state) => $set('date', toGregoryDatePicker($state))),

                            DatePicker::make('pay_date')
                                ->label(__('تاريخ الدفع الفعلي الميلادي'))
                                ->nullable()
                                ->native(false)
                                ->displayFormat('Y-m-d')
                                ->format('Y-m-d')
                                ->reactive()
                                ->afterStateUpdated(fn (callable $set, $state) => $set('pay_date_hijri', toHijriDatePicker($state))),

                            HijriDatePicker::make('pay_date_hijri')
                                ->label(__('تاريخ الدفع الفعلي الهجري'))
                                ->nullable()
                                ->format('Y-m-d')
                                ->displayFormat('Y-m-d')
                                ->reactive()
                                ->afterStateUpdated(fn (callable $set, $state) => $set('pay_date', toGregoryDatePicker($state))),
                        ])
                        ->heading(__('التاريخ'))
                        ->columnSpan(2)
                        ->columns(2),

                        TextInput::make('amount')
                            ->label(__('المبلغ'))
                            ->numeric()
                            ->required(),

                        TextInput::make('paid_amount')
                            ->label(__('المبلغ المدفوع'))
                            ->numeric()
                            ->default(0)
                            ->required(),

                        Select::make('status')
                            ->label(__('الحالة'))
                            ->options([
                                'pending' => __('معلقة'),
                                'paid' => __('مدفوعة'),
                            ])
                            ->native(false)
                            ->default('pending')
                            ->columnSpan(1),

                        Select::make('pay_type')
                            ->label(__('نوع الدفع'))
                            ->native(false)
                            ->options([
                                'cash' => __('نقدي'),
                                'credit' => __('بطاقة ائتمان'),
                                'bank-transfare' => __('تحويل بنكي'),
                            ])
                            ->nullable()
                            ->columnSpan(1),

                        Textarea::make('details')
                            ->label(__('التفاصيل'))
                            ->nullable()
                            ->columnSpan(1),
                    ])
                    ->defaultItems(0)
                    ->disabled(fn (callable $get) => !$get('rent') || !$get('start_date') || !$get('end_date') || !$get('payment_cycle'))
                    // ->live()
                    ->reorderable(false)
                    ->columns(2)
                    ->columnSpan(2),

                ]),

                ])
                ->columns(2)
                ->columnSpan('full'),
        ]);
}

public static function table(Table $table): Table
{
    return $table
        ->columns([
            // TextColumn::make('contract_number')
            //     ->label(__('رقم العقد'))
            //     ->sortable()
            //     ->searchable(),
            TextColumn::make('unit.name') // Display unit name
                ->label(__('الوحدة'))
                ->searchable(),
            TextColumn::make('tenant.name') // Display tenant name
                ->label(__('المستأجر'))
                ->searchable(),
            TextColumn::make('tenant.national_id') // Display tenant name
                ->label(__('رقم الهوية / الاقامة / الحدود'))
                ->searchable(),
            TextColumn::make('start_date')
                ->label(__('تاريخ بدء العقد'))
                ->date('Y-m-d')
                ->searchable(),
            TextColumn::make('end_date')
                ->label(__('تاريخ انتهاء العقد'))
                ->date('Y-m-d')
                ->searchable(),
            // TextColumn::make('rent')
            //     ->label(__('الإيجار'))
            //     ->searchable(),
            TextColumn::make('total_money')
                ->label(__('المبلغ الاجمالي'))
                ->formatStateUsing(fn ($state) => number_format($state, 3)) 
                ->searchable(),
            TextColumn::make('remaining_to_finish_amount')
                ->label(__('اجمالي المتبقي'))
                ->formatStateUsing(fn ($state) => number_format($state, 3)) 
                ->searchable(),
            // TextColumn::make('remaining_payment_amount')
            //     ->label(__('اجمالي المتبقي'))
            //     ->searchable(),  
            TextColumn::make('total_remaining_amount')
                ->formatStateUsing(fn ($state) => number_format($state, 3)) 
                ->label(__('المتاخرات'))
                ->searchable(), 
        ])
        ->actions([
            ActionGroup::make([
                Action::make('single_export')
                ->label(__('استخراج'))
                ->color('success')
                ->icon('heroicon-o-document-text')
                ->action(function ($record) {
                    $exporter = new \App\Exports\SingleContractExporter([$record]);
                    return $exporter->export();
                })
                ->visible(fn ($record) => auth()->user()->can('حذف العقود')),
            EditAction::make()
                ->label(__('تعديل'))
                ->color('warning')
                ->visible(fn ($record) => auth()->user()->can('تعديل العقود')),
            DeleteAction::make()
                ->label(__('حذف'))
                ->visible(fn ($record) => auth()->user()->can('حذف العقود')),
            ])
            ->label(__('ادارة'))
            ->color(Color::Indigo)
            ->button(),

        ])
        ->headerActions([
            ExportPdfAction::make()
                ->exporter(ContractExporter::class)
                ->label(__('استخراج كافة العقود'))
                ->columnMapping(false)
                ->visible(fn ($record) => auth()->user()->can('حذف العقود')),
        ])
        ->bulkActions([
            // ExportPdfAction::make()
            //     ->exporter(ContractExporter::class)
            //     ->label(__('استخراج العقود المحددة'))
            //     ->before(function ($records) {
            //         return $records;
            //     })
            //     ->visible(fn ($record) => auth()->user()->can('حذف العقود')),
        ]);
}

public static function canViewAny(): bool
{
    return auth()->user()->can('عرض العقود');
}

public static function canCreate(): bool
{
    return auth()->user()->can('إنشاء العقود');
}

public static function canDeleteAny(): bool
{
    return auth()->user()->can('حذف العقود');
}

public static function getPages(): array
{
    return [
        'index' => Pages\ListContracts::route('/'),
        'create' => Pages\CreateContract::route('/create'),
        'edit' => Pages\EditContract::route('/{record}/edit'),
        'view' => Pages\ViewContract::route('/{record}'),
    ];
}
// protected static function beforeSave(Contract $contract)
// {

// }    

}`

How to reproduce the bug

just do as example that i told in what happend

Package Version

latest

PHP Version

8.3.10

Laravel Version

11

Which operating systems does with happen with?

Windows

Notes

No response

mohamedsabil83 commented 2 weeks ago

I noticed that the issue is related to the hijri-calendar extension for the dayjs library. We might replace the calendar package in the next major release unless a solution is found.

adel1415 commented 1 week ago

لدي نفس المشكلة والمشكلة الاخرى ان شهر اثنين عند الضغط على يوم 29 / 2 / 1446 يطلع 01 / 03 / 1446

mohamedsabil83 commented 1 week ago

Because the extension and process are heavily related to the Gregory calendar. PR is welcome.