diff --git a/docs/fields.md b/docs/fields.md index 9bdfa7d..c1cd099 100644 --- a/docs/fields.md +++ b/docs/fields.md @@ -107,6 +107,19 @@ $logger->fields([ ->keyValue(differenceOnly: true) ->label('Attributes'), ]) +``` + + #### Key-Value with fields +```php +$logger->fields([ + Field::make('recipient') + ->hasOne('recipient') + ->keyValue([ + Field::make('recipient.full_name'), + Field::make('recipient.phone'), + Field::make('recipient.shipping_provider'), + ]), +]) ``` ![Screenshot](./assets/images/key-value-screenshot.png) diff --git a/resources/dist/filament-activity-log.css b/resources/dist/filament-activity-log.css index 81a05ef..e4042af 100644 --- a/resources/dist/filament-activity-log.css +++ b/resources/dist/filament-activity-log.css @@ -1 +1 @@ -.right-4{right:1rem}.top-1{top:.25rem}.top-20{top:5rem}.mr-2{margin-right:.5rem}.w-fit{width:-moz-fit-content;width:fit-content}.\!whitespace-normal{white-space:normal!important}.break-all{word-break:break-all}.bg-blue-50\/70{background-color:#eff6ffb3}.bg-gray-100\/30{background-color:rgba(var(--gray-100),.3)}.bg-gray-50\/70{background-color:rgba(var(--gray-50),.7)}.bg-green-50\/70{background-color:#f0fdf4b3}.bg-orange-50\/70{background-color:#fff7edb3}.bg-red-50\/70{background-color:#fef2f2b3}.\!py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.align-top{vertical-align:top}.text-blue-700{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity))}.text-orange-700{--tw-text-opacity:1;color:rgb(194 65 12/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.shadow,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.group:hover .group-hover\:opacity-100{opacity:1}:is([dir=rtl] .rtl\:divide-x-reverse)>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:1}:is(.dark .dark\:divide-white\/20)>:not([hidden])~:not([hidden]){border-color:#fff3}:is(.dark .dark\:border-blue-600){--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-600){--tw-border-opacity:1;border-color:rgba(var(--gray-600),var(--tw-border-opacity))}:is(.dark .dark\:border-green-600){--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity))}:is(.dark .dark\:border-orange-600){--tw-border-opacity:1;border-color:rgb(234 88 12/var(--tw-border-opacity))}:is(.dark .dark\:border-red-600){--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity))}:is(.dark .dark\:bg-blue-100\/10){background-color:#dbeafe1a}:is(.dark .dark\:bg-gray-100\/10){background-color:rgba(var(--gray-100),.1)}:is(.dark .dark\:bg-gray-700){--tw-bg-opacity:1;background-color:rgba(var(--gray-700),var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800){--tw-bg-opacity:1;background-color:rgba(var(--gray-800),var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-900){--tw-bg-opacity:1;background-color:rgba(var(--gray-900),var(--tw-bg-opacity))}:is(.dark .dark\:bg-green-100\/10){background-color:#dcfce71a}:is(.dark .dark\:bg-orange-100\/10){background-color:#ffedd51a}:is(.dark .dark\:bg-red-100\/10){background-color:#fee2e21a}:is(.dark .dark\:bg-transparent){background-color:initial}:is(.dark .dark\:text-blue-400){--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-200){--tw-text-opacity:1;color:rgba(var(--gray-200),var(--tw-text-opacity))}:is(.dark .dark\:text-gray-300){--tw-text-opacity:1;color:rgba(var(--gray-300),var(--tw-text-opacity))}:is(.dark .dark\:text-gray-400){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity))}:is(.dark .dark\:text-green-400){--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity))}:is(.dark .dark\:text-orange-400){--tw-text-opacity:1;color:rgb(251 146 60/var(--tw-text-opacity))}:is(.dark .dark\:text-red-400){--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity))}:is(.dark .dark\:ring-white\/20){--tw-ring-color:#fff3} \ No newline at end of file +.right-4{right:1rem}.top-1{top:.25rem}.top-20{top:5rem}.mr-2{margin-right:.5rem}.w-\[1\%\]{width:1%}.w-fit{width:-moz-fit-content;width:fit-content}.max-w-0{max-width:0}.\!table-fixed{table-layout:fixed!important}.overflow-x-scroll{overflow-x:scroll}.\!whitespace-normal{white-space:normal!important}.break-all{word-break:break-all}.bg-blue-50\/70{background-color:#eff6ffb3}.bg-gray-100\/30{background-color:rgba(var(--gray-100),.3)}.bg-gray-50\/70{background-color:rgba(var(--gray-50),.7)}.bg-green-50\/70{background-color:#f0fdf4b3}.bg-orange-50\/70{background-color:#fff7edb3}.bg-red-50\/70{background-color:#fef2f2b3}.\!p-2{padding:.5rem!important}.\!py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.align-top{vertical-align:top}.text-blue-700{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity))}.text-orange-700{--tw-text-opacity:1;color:rgb(194 65 12/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.shadow,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.group:hover .group-hover\:opacity-100{opacity:1}:is([dir=rtl] .rtl\:divide-x-reverse)>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:1}:is(.dark .dark\:divide-white\/20)>:not([hidden])~:not([hidden]){border-color:#fff3}:is(.dark .dark\:border-blue-600){--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity))}:is(.dark .dark\:border-gray-600){--tw-border-opacity:1;border-color:rgba(var(--gray-600),var(--tw-border-opacity))}:is(.dark .dark\:border-green-600){--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity))}:is(.dark .dark\:border-orange-600){--tw-border-opacity:1;border-color:rgb(234 88 12/var(--tw-border-opacity))}:is(.dark .dark\:border-red-600){--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity))}:is(.dark .dark\:border-white\/5){border-color:#ffffff0d}:is(.dark .dark\:bg-blue-100\/10){background-color:#dbeafe1a}:is(.dark .dark\:bg-gray-100\/10){background-color:rgba(var(--gray-100),.1)}:is(.dark .dark\:bg-gray-700){--tw-bg-opacity:1;background-color:rgba(var(--gray-700),var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800){--tw-bg-opacity:1;background-color:rgba(var(--gray-800),var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-900){--tw-bg-opacity:1;background-color:rgba(var(--gray-900),var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-900\/20){background-color:rgba(var(--gray-900),.2)}:is(.dark .dark\:bg-green-100\/10){background-color:#dcfce71a}:is(.dark .dark\:bg-orange-100\/10){background-color:#ffedd51a}:is(.dark .dark\:bg-red-100\/10){background-color:#fee2e21a}:is(.dark .dark\:bg-transparent){background-color:initial}:is(.dark .dark\:text-blue-400){--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}:is(.dark .dark\:text-gray-200){--tw-text-opacity:1;color:rgba(var(--gray-200),var(--tw-text-opacity))}:is(.dark .dark\:text-gray-300){--tw-text-opacity:1;color:rgba(var(--gray-300),var(--tw-text-opacity))}:is(.dark .dark\:text-gray-400){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity))}:is(.dark .dark\:text-green-400){--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity))}:is(.dark .dark\:text-orange-400){--tw-text-opacity:1;color:rgb(251 146 60/var(--tw-text-opacity))}:is(.dark .dark\:text-red-400){--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity))}:is(.dark .dark\:ring-white\/20){--tw-ring-color:#fff3} \ No newline at end of file diff --git a/resources/views/components/badge.blade.php b/resources/views/components/badge.blade.php index e23c8bd..d5cb790 100644 --- a/resources/views/components/badge.blade.php +++ b/resources/views/components/badge.blade.php @@ -2,6 +2,8 @@ use Filament\Support\Contracts\HasColor; use Filament\Support\Contracts\HasLabel; use Filament\Support\Contracts\HasIcon; + + $isHtmlAllowed = $field->isHtmlAllowed(); @endphp
@@ -10,8 +12,13 @@ - {{ $label }} + @if ($isHtmlAllowed) + {!! $label !!} + @else + {{ $label }} + @endif @endforeach @elseif($field->is('enum')) @@ -25,16 +32,25 @@ class="w-fit" :color="$color" :icon="$icon" class="w-fit" + :tooltip="$label" > - {{ $label }} + @if ($isHtmlAllowed) + {!! $label !!} + @else + {{ $label }} + @endif @else - {{ $value }} + @if ($isHtmlAllowed) + {!! $value !!} + @else + {{ $value }} + @endif @endif -
diff --git a/resources/views/components/default.blade.php b/resources/views/components/default.blade.php index 205727d..1a5648e 100644 --- a/resources/views/components/default.blade.php +++ b/resources/views/components/default.blade.php @@ -1,5 +1,19 @@ +@php + $isHtmlAllowed = $field->isHtmlAllowed(); +@endphp + @if (is_array($value)) -
{{ json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) }}
+
+        @if ($isHtmlAllowed)
+{!! json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) !!}
 @else
-    {{ $value }}
+{{ json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) }}
+@endif
+    
+@else + @if ($isHtmlAllowed) + {!! $value !!} + @else + {{ $value }} + @endif @endif diff --git a/resources/views/components/key-value.blade.php b/resources/views/components/key-value.blade.php index 7fb9ed0..9486010 100644 --- a/resources/views/components/key-value.blade.php +++ b/resources/views/components/key-value.blade.php @@ -1,17 +1,60 @@ -
- - - @foreach ((array) $value as $key => $_value) - - +@if (!empty($value)) + @php + $hasFields = $field->keyValue instanceof \Noxo\FilamentActivityLog\ResourceLogger\Types\KeyValueField; + if ($hasFields) { + $fields = $field->keyValue->getFields(); + } + $isHtmlAllowed = $field->isHtmlAllowed(); + @endphp - - - @endforeach - -
- {{ $key }} - - {{ $_value }} -
-
+ +
+ + @if ($hasFields) + + @foreach ($fields as $key => $keyValueField) + @php + if (!array_key_exists($keyValueField->name, $value)) { + continue; + } + $rawValue = $value[$keyValueField->name]; + $dispayValue = $keyValueField->display($rawValue); + @endphp + + + + + + + @endforeach + + @else + + @foreach ((array) $value as $key => $_value) + + + + + + @endforeach + + @endif +
+ {{ $keyValueField->getLabel() }} + + @if ($isHtmlAllowed) + {!! $dispayValue !!} + @else + {{ $dispayValue }} + @endif +
+ {{ $key }} + + @if ($isHtmlAllowed) + {!! $_value !!} + @else + {{ $_value }} + @endif +
+
+@endif diff --git a/resources/views/components/table.blade.php b/resources/views/components/table.blade.php index 93cd0b4..bd6e329 100644 --- a/resources/views/components/table.blade.php +++ b/resources/views/components/table.blade.php @@ -1,22 +1,40 @@ @if (!empty($value)) - - - @foreach (array_keys($value[0]) as $key) - - {{ $key }} - - @endforeach - + @php + $fields = $field->table->getFields(); + $isHtmlAllowed = $field->isHtmlAllowed(); + @endphp + +
+ + + + @foreach ($fields as $field) + + {{ $field->getLabel() }} + + @endforeach + + - @foreach ($value as $item) - $loop->even])> - @foreach ($item as $_value) - - {{ $_value }} - - @endforeach - - @endforeach - + @foreach ($value as $item) + $loop->even])> + @foreach ($fields as $field) + + @php + $rawValue = $item[$field->name] ?? data_get($item, $field->name); + $dispayValue = $field->display($rawValue); + @endphp + + @if ($isHtmlAllowed) + {!! $dispayValue !!} + @else + {{ $dispayValue }} + @endif + + @endforeach + + @endforeach + +
@endif diff --git a/resources/views/list/tables/default.blade.php b/resources/views/list/tables/default.blade.php index 577a3b0..c51f466 100644 --- a/resources/views/list/tables/default.blade.php +++ b/resources/views/list/tables/default.blade.php @@ -1,12 +1,21 @@ - + - + @lang('filament-activity-log::activities.table.field') - + @lang('filament-activity-log::activities.table.old') - + @lang('filament-activity-log::activities.table.new') @@ -22,10 +31,7 @@ @endphp $loop->even])> - + {{ $field->getLabel() }} @@ -41,17 +47,11 @@ class="px-4 py-2 align-top break-all !whitespace-normal" ]) }} @else - + {{ $field->display($oldValue) }} - + {{ $field->display($newValue) }} @endif diff --git a/resources/views/list/tables/simple.blade.php b/resources/views/list/tables/simple.blade.php index c08f8e5..c052232 100644 --- a/resources/views/list/tables/simple.blade.php +++ b/resources/views/list/tables/simple.blade.php @@ -1,9 +1,15 @@ - + - + @lang('filament-activity-log::activities.table.field') - + @lang('filament-activity-log::activities.table.value') @@ -17,17 +23,11 @@ @endphp $loop->even])> - + {{ $field->getLabel() }} - + {{ $field->display($value) }} diff --git a/src/Loggers/Logger.php b/src/Loggers/Logger.php index 117731f..5c8ba45 100644 --- a/src/Loggers/Logger.php +++ b/src/Loggers/Logger.php @@ -39,9 +39,10 @@ public function __construct(Model $newModel = null, Model $oldModel = null) */ public function through(Closure $callback): static { - $callback(clone $this->oldModel); + $callback(clone $this->newModel); - $this->newModel = $this->oldModel->fresh(); + $this->oldModel = clone $this->newModel; + $this->newModel->refresh(); return $this; } diff --git a/src/ResourceLogger/Concerns/Types/KeyValue.php b/src/ResourceLogger/Concerns/Types/KeyValue.php index 618a161..42f5669 100644 --- a/src/ResourceLogger/Concerns/Types/KeyValue.php +++ b/src/ResourceLogger/Concerns/Types/KeyValue.php @@ -2,17 +2,33 @@ namespace Noxo\FilamentActivityLog\ResourceLogger\Concerns\Types; +use Noxo\FilamentActivityLog\ResourceLogger\Types\KeyValueField; + trait KeyValue { + public ?KeyValueField $keyValue = null; public bool $keyValueDifferenceOnly = true; - public function keyValue(bool $differenceOnly = true): static - { + public function keyValue( + array $fields = [], + bool $differenceOnly = true, + ): static { $this->type('key-value'); $this->view('key-value'); + $this->formatStateUsing('array'); $this->keyValueDifferenceOnly = $differenceOnly; - $this->formatStateUsing('array'); + if (! empty($fields)) { + $this->keyValue = KeyValueField::make($fields); + + $this->resolveStateUsing(function ($record) { + $fields = collect($this->keyValue->getFields()); + + return $fields->mapWithKeys(fn ($field) => [ + $field->name => $field->getStorableValue($record), + ])->toArray(); + }); + } return $this; } @@ -23,14 +39,33 @@ public function resolveKeyValueDifference(mixed $array1, mixed $array2): array return [$array1, $array2]; } - foreach ($array1 as $key1 => $row1) { - foreach ($array2 as $key2 => $row2) { - if ($row1 === $row2) { - unset($array1[$key1], $array2[$key2]); + $diff1 = $this->arrayRecursiveDiff($array1, $array2); + $diff2 = $this->arrayRecursiveDiff($array2, $array1); + + return [$diff1, $diff2]; + } + + private function arrayRecursiveDiff(array $aArray1, array $aArray2): array + { + $aReturn = []; + + foreach ($aArray1 as $mKey => $mValue) { + if (array_key_exists($mKey, $aArray2)) { + if (is_array($mValue)) { + $aRecursiveDiff = $this->arrayRecursiveDiff($mValue, $aArray2[$mKey]); + if (count($aRecursiveDiff)) { + $aReturn[$mKey] = $aRecursiveDiff; + } + } else { + if ($mValue != $aArray2[$mKey]) { + $aReturn[$mKey] = $mValue; + } } + } else { + $aReturn[$mKey] = $mValue; } } - return [$array1, $array2]; + return $aReturn; } } diff --git a/src/ResourceLogger/Concerns/Types/Table.php b/src/ResourceLogger/Concerns/Types/Table.php index 2b5a34a..e638328 100644 --- a/src/ResourceLogger/Concerns/Types/Table.php +++ b/src/ResourceLogger/Concerns/Types/Table.php @@ -2,17 +2,41 @@ namespace Noxo\FilamentActivityLog\ResourceLogger\Concerns\Types; +use Closure; +use Noxo\FilamentActivityLog\ResourceLogger\Types\TableField; + trait Table { + public ?TableField $table; + public bool $tableDifferenceOnly = true; - public function table(bool $differenceOnly = true): static - { + public function table( + array $fields, + Closure $resolveRecords = null, + bool $differenceOnly = true, + ): static { $this->type('table'); $this->view('table'); + $this->table = TableField::make($fields); $this->tableDifferenceOnly = $differenceOnly; $this->formatStateUsing('array'); + $this->resolveStateUsing(function ($record) use ($resolveRecords) { + $records = collect( + is_null($resolveRecords) + ? data_get($record, $this->name) + : $resolveRecords($record) + ); + + $fields = collect($this->table->getFields()); + + return $records->map(function ($record) use ($fields) { + return $fields->mapWithKeys(fn ($field) => [ + $field->name => $field->getStorableValue($record), + ])->toArray(); + })->toArray(); + }); return $this; } diff --git a/src/ResourceLogger/Field.php b/src/ResourceLogger/Field.php index 12becdd..3c56bf3 100644 --- a/src/ResourceLogger/Field.php +++ b/src/ResourceLogger/Field.php @@ -3,6 +3,8 @@ namespace Noxo\FilamentActivityLog\ResourceLogger; use DragonCode\Support\Concerns\Makeable; +use Filament\Forms\Components\Concerns\CanAllowHtml; +use Filament\Support\Concerns\EvaluatesClosures; class Field { @@ -25,6 +27,8 @@ class Field use Concerns\Types\Relation; use Concerns\Types\Table; use Makeable; + use CanAllowHtml; + use EvaluatesClosures; public function __construct(string $name, string $type = null) { diff --git a/src/ResourceLogger/Types/KeyValueField.php b/src/ResourceLogger/Types/KeyValueField.php new file mode 100644 index 0000000..f55a4e7 --- /dev/null +++ b/src/ResourceLogger/Types/KeyValueField.php @@ -0,0 +1,17 @@ +fields($fields); + } +} diff --git a/src/ResourceLogger/Types/TableField.php b/src/ResourceLogger/Types/TableField.php new file mode 100644 index 0000000..25b4c09 --- /dev/null +++ b/src/ResourceLogger/Types/TableField.php @@ -0,0 +1,17 @@ +fields($fields); + } +}