├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
├── postcss.config.cjs
├── resources
├── css
│ └── index.css
├── dist
│ └── .gitkeep
├── js
│ └── index.js
└── views
│ ├── components
│ ├── empty-state.blade.php
│ └── section.blade.php
│ ├── infolists
│ └── components
│ │ ├── activity-date.blade.php
│ │ ├── activity-description.blade.php
│ │ ├── activity-icon.blade.php
│ │ ├── activity-section.blade.php
│ │ └── activity-title.blade.php
│ └── pages
│ └── view-activities.blade.php
└── src
├── ActivityTimelineServiceProvider.php
├── Components
├── ActivityDate.php
├── ActivityDescription.php
├── ActivityIcon.php
├── ActivitySection.php
└── ActivityTitle.php
├── Concerns
├── CanModifyState.php
├── HasEmptyState.php
└── HasSetting.php
├── Enums
└── IconAnimation.php
└── Pages
└── ActivityTimelinePage.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `activity-timeline` will be documented in this file.
4 |
5 | ## v1.2.12 - 2025-05-15
6 |
7 | ### What's Changed
8 |
9 | * Added modify_state to activity_date by @albertobenavides in https://github.com/199ocero/activity-timeline/pull/38
10 |
11 | ### New Contributors
12 |
13 | * @albertobenavides made their first contribution in https://github.com/199ocero/activity-timeline/pull/38
14 |
15 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.11...v1.2.12
16 |
17 | ## v1.2.11 - 2025-02-27
18 |
19 | ### What's Changed
20 |
21 | * add support for laravel 12 by @atmonshi in https://github.com/199ocero/activity-timeline/pull/37
22 | * Added allow_html and modify_state handlers by @dotmot in https://github.com/199ocero/activity-timeline/pull/36
23 |
24 | ### New Contributors
25 |
26 | * @dotmot made their first contribution in https://github.com/199ocero/activity-timeline/pull/36
27 |
28 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.10...v1.2.11
29 |
30 | ## v1.2.10 - 2024-12-07
31 |
32 | ### What's Changed
33 |
34 | * Fix formatting of null values by @randomnetcat in https://github.com/199ocero/activity-timeline/pull/33
35 | * Add possibility to animate icon by @agencetwogether in https://github.com/199ocero/activity-timeline/pull/32
36 |
37 | ### New Contributors
38 |
39 | * @randomnetcat made their first contribution in https://github.com/199ocero/activity-timeline/pull/33
40 | * @agencetwogether made their first contribution in https://github.com/199ocero/activity-timeline/pull/32
41 |
42 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.9...v1.2.10
43 |
44 | ## v1.2.9 - 2024-09-04
45 |
46 | ### What's Changed
47 |
48 | * Display changes from null to a value by @pablo-gonzalez-helpwan in https://github.com/199ocero/activity-timeline/pull/30
49 |
50 | ### New Contributors
51 |
52 | * @pablo-gonzalez-helpwan made their first contribution in https://github.com/199ocero/activity-timeline/pull/30
53 |
54 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.8...v1.2.9
55 |
56 | ## v1.2.8 - 2024-05-23
57 |
58 | ### What's Changed
59 |
60 | * Fix error when model column is array for old value by @SujalRatnaTamrakar in https://github.com/199ocero/activity-timeline/pull/27
61 |
62 | ### New Contributors
63 |
64 | * @SujalRatnaTamrakar made their first contribution in https://github.com/199ocero/activity-timeline/pull/27
65 |
66 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.7...v1.2.8
67 |
68 | ## v1.2.7 - 2024-05-02
69 |
70 | ### What's Changed
71 |
72 | * Fix error when model column is array by @henryavila in https://github.com/199ocero/activity-timeline/pull/26
73 |
74 | ### New Contributors
75 |
76 | * @henryavila made their first contribution in https://github.com/199ocero/activity-timeline/pull/26
77 |
78 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.6...v1.2.7
79 |
80 | ## v1.2.6 - 2024-03-07
81 |
82 | ### What's Changed
83 |
84 | * Add placeholder for null values and display logs for null to not-null changes by @RixzZ in https://github.com/199ocero/activity-timeline/pull/22
85 |
86 | ### New Contributors
87 |
88 | * @RixzZ made their first contribution in https://github.com/199ocero/activity-timeline/pull/22
89 |
90 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.5...v1.2.6
91 |
92 | ## v1.2.5 - 2024-02-28
93 |
94 | ### What's Changed
95 |
96 | * Add CanAllowHtml and CanModifyState by @Puralogica in https://github.com/199ocero/activity-timeline/pull/20
97 | * add Laravel 11 support by @atmonshi in https://github.com/199ocero/activity-timeline/pull/21
98 |
99 | ### New Contributors
100 |
101 | * @Puralogica made their first contribution in https://github.com/199ocero/activity-timeline/pull/20
102 |
103 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.4...v1.2.5
104 |
105 | ## v1.2.4 - 2024-02-03
106 |
107 | ### What's Changed
108 |
109 | * Moved activity query out of private function by @bilaliqbalr in https://github.com/199ocero/activity-timeline/pull/19
110 |
111 | ### New Contributors
112 |
113 | * @bilaliqbalr made their first contribution in https://github.com/199ocero/activity-timeline/pull/19
114 |
115 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.3...v1.2.4
116 |
117 | ## v1.2.3 - 2024-01-30
118 |
119 | ### What's Changed
120 |
121 | * Eager load `subject` to prevent N+1 problem by @iRaziul in https://github.com/199ocero/activity-timeline/pull/18
122 |
123 | ### New Contributors
124 |
125 | * @iRaziul made their first contribution in https://github.com/199ocero/activity-timeline/pull/18
126 |
127 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.2...v1.2.3
128 |
129 | ## v1.2.2 - 2024-01-20
130 |
131 | ### What's Changed
132 |
133 | * improve section UI and add collapse effect by @atmonshi in https://github.com/199ocero/activity-timeline/pull/13
134 | * add support to pass any `extraAttributes` by @atmonshi in https://github.com/199ocero/activity-timeline/pull/14
135 | * added heading visible and docu by @199ocero in https://github.com/199ocero/activity-timeline/pull/15
136 |
137 | ### New Contributors
138 |
139 | * @atmonshi made their first contribution in https://github.com/199ocero/activity-timeline/pull/13
140 |
141 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.1...v1.2.2
142 |
143 | ## v1.2.1 - 2024-01-19
144 |
145 | ### What's Changed
146 |
147 | * added css hook classes and minor bug fixes by @199ocero in https://github.com/199ocero/activity-timeline/pull/12
148 |
149 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.2.0...v1.2.1
150 |
151 | ## v1.2.0 - 2024-01-17
152 |
153 | ### What's Changed
154 |
155 | * adding support for spatie activity log by @199ocero in https://github.com/199ocero/activity-timeline/pull/9
156 |
157 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.1.0...v1.2.0
158 |
159 | ## v1.1.0 - 2024-01-14
160 |
161 | ### What's Changed
162 |
163 | * Added empty state by @199ocero in https://github.com/199ocero/activity-timeline/pull/8
164 |
165 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.0.2...v1.1.0
166 |
167 | ## v1.0.2 - 2024-01-08
168 |
169 | ### What's Changed
170 |
171 | * fix allow html always true by @199ocero in https://github.com/199ocero/activity-timeline/pull/5
172 | * added docs for using record method by @199ocero in https://github.com/199ocero/activity-timeline/pull/6
173 |
174 | ### New Contributors
175 |
176 | * @199ocero made their first contribution in https://github.com/199ocero/activity-timeline/pull/5
177 |
178 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.0.1...v1.0.2
179 |
180 | ## v1.0.1 - 2024-01-07
181 |
182 | **Full Changelog**: https://github.com/199ocero/activity-timeline/compare/v1.0.0...v1.0.1
183 |
184 | ## v1.0.0 - 2024-01-06
185 |
186 | First release
187 |
188 | ## 1.0.0 - 202X-XX-XX
189 |
190 | - initial release
191 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) jaocero <199ocero@gmail.com>
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Activity Timeline
2 |
3 |
4 |
5 | 
6 |
7 |
8 |
9 | [](https://packagist.org/packages/jaocero/activity-timeline)
10 | [](https://packagist.org/packages/jaocero/activity-timeline)
11 |
12 | Add timelines to custom pages or infolist entries effortlessly. Plus, it teams up smoothly with Spatie Activitylog for easy tracking.
13 |
14 | ## Installation
15 |
16 | You can install the package via composer:
17 |
18 | ```bash
19 | composer require jaocero/activity-timeline
20 | ```
21 |
22 | To adhere to Filament's theming approach, you'll be required to employ a personalized theme in order to utilize this plugin.
23 |
24 | > **Custom Theme Installation** > [Filament Docs](https://filamentphp.com/docs/3.x/panels/themes#creating-a-custom-theme)
25 |
26 | Add the plugin's views to your `tailwind.config.js` file.
27 |
28 | ```js
29 | content: [
30 | ...'./vendor/jaocero/activity-timeline/resources/views/**/*.blade.php',
31 | ]
32 | ```
33 |
34 | ## Usage
35 |
36 | This plugin is already accessible within the Infolists builder and now supports both the `->state([])` and `->record()` methods.
37 |
38 | ```php
39 | use JaOcero\ActivityTimeline\Enums\IconAnimation;
40 |
41 | public function activityTimelineInfolist(Infolist $infolist): Infolist
42 | {
43 | return $infolist
44 | ->state([
45 | 'activities' => [
46 | [
47 | 'title' => "Published Article 🔥 - Published with Laravel Filament and Tailwind CSS",
48 | 'description' => "Approved and published. Here is the link.",
49 | 'status' => 'published',
50 | 'created_at' => now()->addDays(8),
51 | ],
52 | [
53 | 'title' => 'Reviewing Article - Final Touches',
54 | 'description' => "Reviewing the article and making it ready for publication.",
55 | 'status' => '',
56 | 'created_at' => now()->addDays(5),
57 | ],
58 | [
59 | 'title' => "Drafting Article - Make it ready for review",
60 | 'description' => 'Drafting the article and making it ready for review.',
61 | 'status' => 'drafting',
62 | 'created_at' => now()->addDays(2),
63 | ],
64 | [
65 | 'title' => 'Ideation - Looking for Ideas 🤯',
66 | 'description' => 'Idea for my article.',
67 | 'status' => 'ideation',
68 | 'created_at' => now()->subDays(7),
69 | ]
70 | ]
71 | ])
72 | ->schema([
73 |
74 | /*
75 | You should enclose the entire components within a personalized "ActivitySection" entry.
76 | This section functions identically to the repeater entry; you simply have to provide the array state's key.
77 | */
78 |
79 | ActivitySection::make('activities')
80 | ->label('My Activities')
81 | ->description('These are the activities that have been recorded.')
82 | ->schema([
83 | ActivityTitle::make('title')
84 | ->placeholder('No title is set')
85 | ->allowHtml(), // Be aware that you will need to ensure that the HTML is safe to render, otherwise your application will be vulnerable to XSS attacks.
86 | ActivityDescription::make('description')
87 | ->placeholder('No description is set')
88 | ->allowHtml(),
89 | ActivityDate::make('created_at')
90 | ->date('F j, Y', 'Asia/Manila')
91 | ->placeholder('No date is set.'),
92 | ActivityIcon::make('status')
93 | ->icon(fn (string | null $state): string | null => match ($state) {
94 | 'ideation' => 'heroicon-m-light-bulb',
95 | 'drafting' => 'heroicon-m-bolt',
96 | 'reviewing' => 'heroicon-m-document-magnifying-glass',
97 | 'published' => 'heroicon-m-rocket-launch',
98 | default => null,
99 | })
100 | /*
101 | You can animate icon with ->animation() method.
102 | Possible values : IconAnimation::Ping, IconAnimation::Pulse, IconAnimation::Bounce, IconAnimation::Spin or a Closure
103 | */
104 | ->animation(IconAnimation::Ping)
105 | ->color(fn (string | null $state): string | null => match ($state) {
106 | 'ideation' => 'purple',
107 | 'drafting' => 'info',
108 | 'reviewing' => 'warning',
109 | 'published' => 'success',
110 | default => 'gray',
111 | }),
112 | ])
113 | ->showItemsCount(2) // Show up to 2 items
114 | ->showItemsLabel('View Old') // Show "View Old" as link label
115 | ->showItemsIcon('heroicon-m-chevron-down') // Show button icon
116 | ->showItemsColor('gray') // Show button color and it supports all colors
117 | ->aside(true)
118 | ->headingVisible(true) // make heading visible or not
119 | ->extraAttributes(['class'=>'my-new-class']) // add extra class
120 | ]);
121 | }
122 | ```
123 |
124 | When utilizing the `->record()` function, you provide your model in a manner similar to the code showcased below:
125 |
126 | ```php
127 | protected $activities;
128 |
129 | public function __construct()
130 | {
131 | $this->activities = User::query()->with('activities')->where('id', auth()->user()->id)->first();
132 | }
133 |
134 | public function activityTimelineInfolist(Infolist $infolist): Infolist
135 | {
136 | return $infolist
137 | ->record($this->activities)
138 | // ... remaining code
139 | }
140 | ```
141 |
142 | Sometimes, when we don't have any info to show to users, it's important to improve their experience by displaying something. So, I include an empty state, like the one in the [Filament Table Empty State](https://filamentphp.com/docs/3.x/tables/empty-state).
143 |
144 | ```php
145 | public function activityTimelineInfolist(Infolist $infolist): Infolist
146 | {
147 | return $infolist
148 | ->state([
149 | 'activities' => []
150 | )]
151 | ->schema([
152 | ActivitySection::make('activities')
153 | // ... other code
154 | ->emptyStateHeading('No activities yet.')
155 | ->emptyStateDescription('Check back later for activities that have been recorded.')
156 | ->emptyStateIcon('heroicon-o-bolt-slash')
157 | ])
158 | }
159 | ```
160 |
161 | ## Usage with Spatie Activity Log Package
162 |
163 | This plugin works with [spatie/laravel-activitylog](https://github.com/spatie/laravel-activitylog), making it easy to log user actions in your app. It can also automatically log model events, storing everything in the `activity_log` table. To use the plugin, just install `spatie/laravel-activitylog`, set it up, and you're good to go.
164 |
165 | ### Creating a Custom Page
166 |
167 | You are required to create a custom page within your `resources` to display all activities based on the record passed to the route.
168 |
169 | ```php
170 | php artisan make:filament-page ViewOrderActivities --resource=OrderResource --type=custom
171 | ```
172 |
173 | ### Including the Page in your Resource Class
174 |
175 | Simply include the custom page in the `getPages()` method so that we can access it.
176 |
177 | ```php
178 | public static function getPages(): array
179 | {
180 | return [
181 | // ... other pages
182 | // The format of route doesn't matter, as long as it includes the route parameter {record}.
183 | 'activities' => Pages\ViewOrderActivities::route('/order/{record}/activities'),
184 | ];
185 | }
186 | ```
187 |
188 | In the `actions` method of your table, include an additional custom action. This action should redirect users to the custom page we've generated earlier.
189 |
190 | ```php
191 | public static function table(Table $table): Table
192 | {
193 | return $table
194 | ->columns([
195 | // ...
196 | ])
197 | ->filters([
198 | // ...
199 | ])
200 | ->actions([
201 | Tables\Actions\Action::make('view_activities')
202 | ->label('Activities')
203 | ->icon('heroicon-m-bolt')
204 | ->color('purple')
205 | ->url(fn ($record) => OrderResource::getUrl('activities', ['record' => $record])),
206 | ])
207 | ->bulkActions([
208 | // ...
209 | ]);
210 | }
211 | ```
212 |
213 | ### Setting up your Custom Page
214 |
215 | Changes are needed in your custom page. Instead of extending using the regular `Page` class will make use of a specific class called `ActivityTimelinePage` provided by the plugin. Additionally, you should include your resource class.
216 |
217 | ```php
218 | use App\Filament\Resources\OrderResource;
219 | use JaOcero\ActivityTimeline\Pages\ActivityTimelinePage;
220 |
221 | class ViewOrderActivities extends ActivityTimelinePage
222 | {
223 | protected static string $resource = OrderResource::class;
224 | }
225 | ```
226 |
227 | ### Configuration
228 |
229 | Behind the scenes, the plugin utilizes the previously mentioned infolists entry. We only modify the properties/data, but the logic remains unchanged.
230 |
231 | ```php
232 | use App\Filament\Resources\OrderResource;
233 | use JaOcero\ActivityTimeline\Pages\ActivityTimelinePage;
234 |
235 | class ViewOrderActivities extends ActivityTimelinePage
236 | {
237 | protected static string $resource = OrderResource::class;
238 |
239 | protected function configuration(): array
240 | {
241 | return [
242 | 'activity_section' => [
243 | 'label' => 'Activities', // label for the section
244 | 'description' => 'These are the activities that have been recorded.', // description for the section
245 | 'show_items_count' => 0, // show the number of items to be shown
246 | 'show_items_label' => 'Show more', // show button label
247 | 'show_items_icon' => 'heroicon-o-chevron-down', // show button icon,
248 | 'show_items_color' => 'gray', // show button color,
249 | 'aside' => true, // show the section in the aside
250 | 'empty_state_heading' => 'No activities yet', // heading for the empty state
251 | 'empty_state_description' => 'Check back later for activities that have been recorded.', // description for the empty state
252 | 'empty_state_icon' => 'heroicon-o-bolt-slash', // icon for the empty state
253 | 'heading_visible' => true, // show the heading
254 | 'extra_attributes' => [], // extra attributes
255 | ],
256 | 'activity_title' => [
257 | 'placeholder' => 'No title is set', // this will show when there is no title
258 | 'allow_html' => true, // set true to allow html in the title
259 |
260 | /**
261 | * You are free to adjust the state before displaying it on your page.
262 | * Take note that the state returns these data below:
263 | * [
264 | * 'log_name' => $activity->log_name,
265 | * 'description' => $activity->description,
266 | * 'subject' => $activity->subject,
267 | * 'event' => $activity->event,
268 | * 'causer' => $activity->causer,
269 | * 'properties' => json_decode($activity->properties, true),
270 | * 'batch_uuid' => $activity->batch_uuid,
271 | * ]
272 |
273 | * If you wish to make modifications, please refer to the default code in the HasSetting trait.
274 | */
275 |
276 | // 'modify_state' => function (array $state) {
277 | //
278 | // }
279 |
280 | ],
281 | 'activity_description' => [
282 | 'placeholder' => 'No description is set', // this will show when there is no description
283 | 'allow_html' => true, // set true to allow html in the description
284 |
285 | /**
286 | * You are free to adjust the state before displaying it on your page.
287 | * Take note that the state returns these data below:
288 | * [
289 | * 'log_name' => $activity->log_name,
290 | * 'description' => $activity->description,
291 | * 'subject' => $activity->subject,
292 | * 'event' => $activity->event,
293 | * 'causer' => $activity->causer,
294 | * 'properties' => json_decode($activity->properties, true),
295 | * 'batch_uuid' => $activity->batch_uuid,
296 | * ]
297 |
298 | * If you wish to make modifications, please refer to the default code in the HasSetting trait.
299 | */
300 |
301 | // 'modify_state' => function (array $state) {
302 | //
303 | // }
304 |
305 | ],
306 | 'activity_date' => [
307 | 'name' => 'created_at', // or updated_at
308 | 'date' => 'F j, Y g:i A', // date format
309 | 'placeholder' => 'No date is set', // this will show when there is no date
310 | 'modify_state' => function ($state) {
311 | return new HtmlString($state);
312 | }
313 | ],
314 | 'activity_icon' => [
315 | 'icon' => fn (string | null $state): string | null => match ($state) {
316 | /**
317 | * 'event_name' => 'heroicon-o-calendar',
318 | * ... and more
319 | */
320 | default => null
321 | },
322 | 'color' => fn (string | null $state): string | null => match ($state) {
323 | /**
324 | * 'event_name' => 'primary',
325 | * ... and more
326 | */
327 | default => null
328 | },
329 | ]
330 | ];
331 | }
332 | }
333 | ```
334 |
335 | ## Style customization
336 |
337 | Similar to Filament, this plugin also includes CSS `hook` classes that enable the customization of different HTML elements through CSS.
338 |
339 | ```css
340 | .fi-timeline-section {
341 | @apply bg-transparent !important;
342 | }
343 | ```
344 |
345 | This plugin comes with numerous CSS `hook` classes. For a straightforward approach, consider using your browser's developer tools to carefully examine the element and identify these classes.
346 |
347 | That's all! If you encounter any issues or have features you'd like to discuss, feel free to chat with me in our Discord channel.
348 |
349 | ## Changelog
350 |
351 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
352 |
353 | ## Contributing
354 |
355 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.
356 |
357 | ## Security Vulnerabilities
358 |
359 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
360 |
361 | ## Credits
362 |
363 | - [Jay-Are Ocero](https://github.com/199ocero)
364 | - [All Contributors](../../contributors)
365 |
366 | ## License
367 |
368 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
369 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jaocero/activity-timeline",
3 | "description": "Add timelines to custom pages or infolist entries effortlessly. Plus, it teams up smoothly with Spatie Activitylog for easy tracking.",
4 | "keywords": [
5 | "jaocero",
6 | "laravel",
7 | "activity-timeline",
8 | "timeline",
9 | "filament-infolists",
10 | "filamentphp"
11 | ],
12 | "homepage": "https://github.com/jaocero/activity-timeline",
13 | "support": {
14 | "issues": "https://github.com/jaocero/activity-timeline/issues",
15 | "source": "https://github.com/jaocero/activity-timeline"
16 | },
17 | "license": "MIT",
18 | "authors": [
19 | {
20 | "name": "Jay-Are Ocero",
21 | "email": "199ocero@gmail.com",
22 | "role": "Developer"
23 | }
24 | ],
25 | "require": {
26 | "php": "^8.1",
27 | "filament/filament": "^3.0",
28 | "spatie/laravel-package-tools": "^1.15.0",
29 | "illuminate/contracts": "^10.0|^11.0|^12.0"
30 | },
31 | "require-dev": {
32 | "nunomaduro/collision": "^7.9",
33 | "orchestra/testbench": "^8.0|^9.0",
34 | "pestphp/pest": "^2.0",
35 | "pestphp/pest-plugin-arch": "^2.0",
36 | "pestphp/pest-plugin-laravel": "^2.0"
37 | },
38 | "autoload": {
39 | "psr-4": {
40 | "JaOcero\\ActivityTimeline\\": "src/",
41 | "JaOcero\\ActivityTimeline\\Database\\Factories\\": "database/factories/"
42 | }
43 | },
44 | "autoload-dev": {
45 | "psr-4": {
46 | "JaOcero\\ActivityTimeline\\Tests\\": "tests/"
47 | }
48 | },
49 | "scripts": {
50 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi",
51 | "test": "vendor/bin/pest",
52 | "test-coverage": "vendor/bin/pest --coverage"
53 | },
54 | "config": {
55 | "sort-packages": true,
56 | "allow-plugins": {
57 | "pestphp/pest-plugin": true,
58 | "phpstan/extension-installer": true
59 | }
60 | },
61 | "extra": {
62 | "laravel": {
63 | "providers": [
64 | "JaOcero\\ActivityTimeline\\ActivityTimelineServiceProvider"
65 | ]
66 | }
67 | },
68 | "minimum-stability": "dev",
69 | "prefer-stable": true
70 | }
71 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | "postcss-import": {},
4 | "tailwindcss/nesting": {},
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/resources/css/index.css:
--------------------------------------------------------------------------------
1 | @import '../../vendor/filament/filament/resources/css/theme.css';
2 |
--------------------------------------------------------------------------------
/resources/dist/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/199ocero/activity-timeline/c68f78fba3be717f4b6e3cb8be03dc13bb906b44/resources/dist/.gitkeep
--------------------------------------------------------------------------------
/resources/js/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/199ocero/activity-timeline/c68f78fba3be717f4b6e3cb8be03dc13bb906b44/resources/js/index.js
--------------------------------------------------------------------------------
/resources/views/components/empty-state.blade.php:
--------------------------------------------------------------------------------
1 | @props([
2 | 'description' => null,
3 | 'heading',
4 | 'icon',
5 | ])
6 |
7 | class(['fi-timeline-empty-state px-6 py-12']) }}>
8 |
9 |
10 |
12 |
13 |
14 |
class(['fi-timeline-empty-state-heading text-base font-semibold leading-6 text-gray-950 dark:text-white']) }}>
16 | {{ $heading }}
17 |
18 |
19 | @if ($description)
20 |
class(['fi-timeline-empty-state-description text-sm text-gray-500 dark:text-gray-400']) }}>
22 | {{ $description }}
23 |
24 | @endif
25 |
26 |
27 |
--------------------------------------------------------------------------------
/resources/views/components/section.blade.php:
--------------------------------------------------------------------------------
1 | @props(['heading', 'headingVisible' => true, 'description' => null, 'aside' => true, 'extraAttributes' => []])
2 |
3 | merge($extraAttributes, escape: false)->class([
5 | 'fi-timeline-section',
6 | 'grid items-start grid-cols-1 gap-x-6 gap-y-4 md:grid-cols-3' => $aside && $headingVisible,
7 | 'bg-white shadow-sm rounded-xl ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10' => !$aside,
8 | ]) }}>
9 |
10 | @if ($headingVisible)
11 | !$aside,
14 | 'flex flex-col gap-3 overflow-hidden sm:flex-row sm:items-center' => $aside,
15 | ])>
16 |
17 |
21 |
24 |
25 |
26 | @endif
27 |
28 | @if ($aside)
29 |
31 | {{ $slot }}
32 |
33 | @else
34 | $headingVisible,
37 | ])>
38 | {{ $slot }}
39 |
40 | @endif
41 |
42 |
--------------------------------------------------------------------------------
/resources/views/infolists/components/activity-date.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if ($isHtmlAllowed())
3 | {!! $getModifiedState() ?? (!is_array($getState()) ? $getState() ?? $getPlaceholder() : null) !!}
4 | @else
5 | {{ $getModifiedState() ?? (!is_array($getState()) ? $getState() ?? $getPlaceholder() : null) }}
6 | @endif
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/infolists/components/activity-description.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if ($isHtmlAllowed())
3 | {!! $getModifiedState() ?? (!is_array($getState()) ? $getState() ?? $getPlaceholder() : null) !!}
4 | @else
5 | {{ $getModifiedState() ?? (!is_array($getState()) ? $getState() ?? $getPlaceholder() : null) }}
6 | @endif
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/infolists/components/activity-icon.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | use JaOcero\ActivityTimeline\Enums\IconAnimation;
3 | @endphp
4 | @if ($icon = $getIcon($getState()))
5 | @php
6 | $color = $getColor($getState()) ?? 'gray';
7 | $animation = $getAnimation($getState());
8 | @endphp
9 |
10 | 'animate-spin',
14 | IconAnimation::Ping, 'ping' => 'animate-ping',
15 | IconAnimation::Pulse, 'pulse' => 'animate-pulse',
16 | IconAnimation::Bounce, 'bounce' => 'animate-bounce',
17 | default => $animation,
18 | },
19 | ])>
20 | 'bg-gray-100',
24 | default => 'fi-color-custom bg-custom-100',
25 | },
26 | ]) @style([
27 | \Filament\Support\get_color_css_variables($color, shades: [100, 800], alias: 'infolists::components.icon-entry.item') => $color !== 'gray',
28 | ])>
29 | 'fi-color-gray text-gray-400 dark:text-gray-500',
33 | default => 'fi-color-custom text-custom-500 dark:text-custom-400',
34 | },
35 | ]) @style([
36 | \Filament\Support\get_color_css_variables($color, shades: [400, 500], alias: 'infolists::components.icon-entry.item') => $color !== 'gray',
37 | ]) />
38 |
39 |
40 | @else
41 |
44 | @endif
45 |
--------------------------------------------------------------------------------
/resources/views/infolists/components/activity-section.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $getLabel() ?? $getHeading() }}
4 |
5 |
6 |
7 | {{ $getDescription() }}
8 |
9 |
10 | @if (count($childComponentContainers = $getChildComponentContainers()) &&
11 | count($childComponentContainers[0]->getComponents()) > 0)
12 | @php
13 | $childItemsCount = count($childComponentContainers);
14 | $showItemsCount = $getShowItemsCount() ?? $childItemsCount;
15 | @endphp
16 |
17 |
18 | @foreach ($childComponentContainers as $index => $container)
19 | @php
20 | $activityComponents = [
21 | 'activityIcon' => null,
22 | 'activityTitle' => null,
23 | 'activityDate' => null,
24 | 'activityDescription' => null,
25 | ];
26 |
27 | foreach ($container->getComponents() as $component) {
28 | $viewIdentifier = $component->getViewIdentifier();
29 | if (array_key_exists($viewIdentifier, $activityComponents)) {
30 | $activityComponents[$viewIdentifier] = $component;
31 | }
32 | }
33 |
34 | extract($activityComponents);
35 | @endphp
36 |
37 |
38 |
40 |
41 |
42 |
43 |
!$loop->last,
46 | ])>
47 | {{ $activityIcon }}
48 |
49 |
50 |
!$loop->last,
54 | 'mb-0' => $loop->last,
55 | ])>
56 |
!$isAside(),
59 | 'flex-col items-start space-y-1 lg:space-y-0 lg:items-center lg:flex-row lg:justify-between lg:space-x-5' => $isAside(),
60 | ])>
61 |
62 | {{ $activityTitle }}
63 |
64 | {{ $activityDate }}
65 |
66 |
67 |
68 | {{ $activityDescription }}
69 |
70 |
71 |
72 |
73 |
74 | @endforeach
75 |
76 |
77 | @php
78 | $icon = $getShowItemsIcon();
79 | $label = $getShowItemsLabel();
80 | $color = $getShowItemsColor();
81 | @endphp
82 |
84 | {{ $label }}
85 |
86 |
87 |
88 |
89 | @else
90 |
91 | @endif
92 |
93 |
--------------------------------------------------------------------------------
/resources/views/infolists/components/activity-title.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if ($isHtmlAllowed())
3 | {!! $getModifiedState() ?? (!is_array($getState()) ? $getState() ?? $getPlaceholder() : null) !!}
4 | @else
5 | {{ $getModifiedState() ?? (!is_array($getState()) ? $getState() ?? $getPlaceholder() : null) }}
6 | @endif
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/pages/view-activities.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $this->activityInfolist }}
3 |
4 |
--------------------------------------------------------------------------------
/src/ActivityTimelineServiceProvider.php:
--------------------------------------------------------------------------------
1 | name(static::$name)
29 | ->hasCommands($this->getCommands())
30 | ->hasInstallCommand(function (InstallCommand $command) {
31 | $command->askToStarRepoOnGitHub('jaocero/activity-timeline');
32 | });
33 |
34 | if (file_exists($package->basePath('/../resources/views'))) {
35 | $package->hasViews(static::$viewNamespace);
36 | }
37 | }
38 |
39 | public function packageRegistered(): void {}
40 |
41 | public function packageBooted(): void
42 | {
43 | // Asset Registration
44 | FilamentAsset::register(
45 | $this->getAssets(),
46 | $this->getAssetPackageName()
47 | );
48 |
49 | FilamentAsset::registerScriptData(
50 | $this->getScriptData(),
51 | $this->getAssetPackageName()
52 | );
53 |
54 | // Icon Registration
55 | FilamentIcon::register($this->getIcons());
56 | }
57 |
58 | protected function getAssetPackageName(): ?string
59 | {
60 | return 'jaocero/activity-timeline';
61 | }
62 |
63 | /**
64 | * @return array
65 | */
66 | protected function getAssets(): array
67 | {
68 | return [
69 | // AlpineComponent::make('activity-timeline', __DIR__ . '/../resources/dist/components/activity-timeline.js'),
70 | // Css::make('activity-timeline-styles', __DIR__.'/../resources/dist/activity-timeline.css'),
71 | // Js::make('activity-timeline-scripts', __DIR__.'/../resources/dist/activity-timeline.js'),
72 | ];
73 | }
74 |
75 | /**
76 | * @return array
77 | */
78 | protected function getCommands(): array
79 | {
80 | return [];
81 | }
82 |
83 | /**
84 | * @return array
85 | */
86 | protected function getIcons(): array
87 | {
88 | return [];
89 | }
90 |
91 | /**
92 | * @return array
93 | */
94 | protected function getRoutes(): array
95 | {
96 | return [];
97 | }
98 |
99 | /**
100 | * @return array
101 | */
102 | protected function getScriptData(): array
103 | {
104 | return [];
105 | }
106 |
107 | /**
108 | * @return array
109 | */
110 | protected function getMigrations(): array
111 | {
112 | return [];
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Components/ActivityDate.php:
--------------------------------------------------------------------------------
1 | dateFormat = $format;
28 | $this->dateTimezone = $timezone;
29 |
30 | return $this;
31 | }
32 |
33 | public function getDate($value): ?string
34 | {
35 | $date = Carbon::parse($value)
36 | ->setTimezone($this->getTimezone());
37 |
38 | if ($this->getFormat() != null) {
39 | $this->date = $date->translatedFormat($this->getFormat());
40 | } else {
41 | $this->date = $date;
42 | }
43 |
44 | return $this->evaluate($this->date);
45 | }
46 |
47 | public function getFormat(): ?string
48 | {
49 | return $this->evaluate($this->dateFormat);
50 | }
51 |
52 | public function getTimezone(): ?string
53 | {
54 | return $this->evaluate($this->dateTimezone) ?? config('app.timezone');
55 | }
56 |
57 | public function getViewIdentifier(): string
58 | {
59 | return $this->viewIdentifier;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Components/ActivityDescription.php:
--------------------------------------------------------------------------------
1 | viewIdentifier;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Components/ActivityIcon.php:
--------------------------------------------------------------------------------
1 | animation = $animation;
20 |
21 | return $this;
22 | }
23 |
24 | public function getAnimation(mixed $state): IconAnimation|string|null
25 | {
26 | return $this->evaluate($this->animation, [
27 | 'state' => $state,
28 | ]);
29 | }
30 |
31 | public function getViewIdentifier(): string
32 | {
33 | return $this->viewIdentifier;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Components/ActivitySection.php:
--------------------------------------------------------------------------------
1 | description = $description;
34 |
35 | return $this;
36 | }
37 |
38 | public function aside(bool|Closure|null $condition = true): static
39 | {
40 | $this->isAside = $condition;
41 |
42 | return $this;
43 | }
44 |
45 | public function showItemsCount(int|Closure $items): static
46 | {
47 | $this->showItemsCount = $items;
48 |
49 | return $this;
50 | }
51 |
52 | public function showItemsLabel(string|Closure $label): static
53 | {
54 | $this->showItemsLabel = $label;
55 |
56 | return $this;
57 | }
58 |
59 | public function showItemsIcon(string|Closure|null $icon = null): static
60 | {
61 | $this->showItemsIcon = $icon;
62 |
63 | return $this;
64 | }
65 |
66 | public function showItemsColor(string|Closure $color): static
67 | {
68 | $this->showItemsColor = $color;
69 |
70 | return $this;
71 | }
72 |
73 | public function headingVisible(bool|Closure|null $condition = true): static
74 | {
75 | $this->isHeadingVisible = $condition;
76 |
77 | return $this;
78 | }
79 |
80 | public function isAside(): bool
81 | {
82 | return (bool) ($this->evaluate($this->isAside) ?? false);
83 | }
84 |
85 | public function getDescription(): ?string
86 | {
87 | return $this->evaluate($this->description);
88 | }
89 |
90 | public function getShowItemsCount(): ?int
91 | {
92 | $showItemsCount = $this->evaluate($this->showItemsCount);
93 |
94 | if ($showItemsCount == 0) {
95 | return null;
96 | }
97 |
98 | return $showItemsCount;
99 | }
100 |
101 | public function getShowItemsLabel(): string
102 | {
103 | return $this->evaluate($this->showItemsLabel) ?? 'Show More';
104 | }
105 |
106 | public function getShowItemsIcon(): ?string
107 | {
108 | return $this->evaluate($this->showItemsIcon);
109 | }
110 |
111 | public function getShowItemsColor(): string
112 | {
113 | return $this->evaluate($this->showItemsColor) ?? 'gray';
114 | }
115 |
116 | public function isHeadingVisible(): bool
117 | {
118 | return (bool) ($this->evaluate($this->isHeadingVisible) ?? true);
119 | }
120 |
121 | /**
122 | * @return array
123 | */
124 | public function getChildComponentContainers(bool $withHidden = false): array
125 | {
126 | if ((! $withHidden) && $this->isHidden()) {
127 | return [];
128 | }
129 |
130 | $containers = [];
131 |
132 | foreach ($this->getState() ?? [] as $itemKey => $itemData) {
133 | $container = $this
134 | ->getChildComponentContainer()
135 | ->getClone()
136 | ->statePath($itemKey)
137 | ->inlineLabel(false);
138 |
139 | if ($itemData instanceof Model) {
140 | $container->record($itemData);
141 | }
142 |
143 | $containers[$itemKey] = $container;
144 | }
145 |
146 | return $containers;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Components/ActivityTitle.php:
--------------------------------------------------------------------------------
1 | viewIdentifier;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Concerns/CanModifyState.php:
--------------------------------------------------------------------------------
1 | state = $callback;
15 |
16 | return $this;
17 | }
18 |
19 | public function getModifiedState(): ?HtmlString
20 | {
21 | return $this->evaluate($this->state);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Concerns/HasEmptyState.php:
--------------------------------------------------------------------------------
1 | emptyStateDescription = $description;
22 |
23 | return $this;
24 | }
25 |
26 | public function emptyStateHeading(string|Htmlable|Closure|null $heading): static
27 | {
28 | $this->emptyStateHeading = $heading;
29 |
30 | return $this;
31 | }
32 |
33 | public function emptyStateIcon(string|Closure|null $icon): static
34 | {
35 | $this->emptyStateIcon = $icon;
36 |
37 | return $this;
38 | }
39 |
40 | public function getEmptyStateDescription(): string|Htmlable|null
41 | {
42 | return $this->evaluate($this->emptyStateDescription);
43 | }
44 |
45 | public function getEmptyStateHeading(): string|Htmlable
46 | {
47 | return $this->evaluate($this->emptyStateHeading) ?? 'No '.str($this->getName())->title();
48 | }
49 |
50 | public function getEmptyStateIcon(): string
51 | {
52 | return $this->evaluate($this->emptyStateIcon) ?? 'heroicon-o-x-mark';
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Concerns/HasSetting.php:
--------------------------------------------------------------------------------
1 | [
21 | 'label' => 'Activities',
22 | 'description' => 'These are the activities that have been recorded.',
23 | 'show_items_count' => 0,
24 | 'show_items_label' => 'Show more',
25 | 'show_items_icon' => 'heroicon-o-chevron-right',
26 | 'show_items_color' => 'gray',
27 | 'aside' => true,
28 | 'empty_state_heading' => 'No activities yet',
29 | 'empty_state_description' => 'Check back later for activities that have been recorded.',
30 | 'empty_state_icon' => 'heroicon-o-bolt-slash',
31 | 'heading_visible' => true,
32 | 'extra_attributes' => [],
33 | ],
34 | 'activity_title' => [
35 | 'placeholder' => 'No title is set',
36 | 'allow_html' => true,
37 | ],
38 | 'activity_description' => [
39 | 'placeholder' => 'No description is set',
40 | 'allow_html' => true,
41 | ],
42 | 'activity_date' => [
43 | 'name' => 'created_at',
44 | 'date' => 'F j, Y g:i A',
45 | 'placeholder' => 'No date is set',
46 | ],
47 | 'activity_icon' => [
48 | 'icon' => fn (?string $state): ?string => match ($state) {
49 | default => null
50 | },
51 | 'color' => fn (?string $state): ?string => match ($state) {
52 | default => null
53 | },
54 | ],
55 | ];
56 | }
57 |
58 | public function activityInfolist(Infolist $infolist): Infolist
59 | {
60 | $activityTitle = $this->modifiedState()['activity_title']['modify_state'];
61 | $activityDescription = $this->modifiedState()['activity_description']['modify_state'];
62 | $activityDate = $this->modifiedState()['activity_date']['modify_state'];
63 |
64 | if (isset($this->configuration()['activity_title']['modify_state'])) {
65 | $activityTitle = $this->configuration()['activity_title']['modify_state'];
66 | }
67 |
68 | if (isset($this->configuration()['activity_description']['modify_state'])) {
69 | $activityDescription = $this->configuration()['activity_description']['modify_state'];
70 | }
71 | if (isset($this->configuration()['activity_date']['modify_state'])) {
72 | $activityDate = $this->configuration()['activity_date']['modify_state'];
73 | }
74 |
75 | return $infolist
76 | ->state([
77 | 'activities' => $this->getActivityLogRecord(),
78 | ])
79 | ->schema([
80 | ActivitySection::make('activities')
81 | ->label($this->configuration()['activity_section']['label'])
82 | ->description($this->configuration()['activity_section']['description'])
83 | ->schema([
84 | ActivityTitle::make('activityData')
85 | ->placeholder($this->configuration()['activity_title']['placeholder'])
86 | ->allowHtml($this->configuration()['activity_title']['allow_html'])
87 | ->modifyState($activityTitle),
88 | ActivityDescription::make('activityData')
89 | ->placeholder($this->configuration()['activity_description']['placeholder'])
90 | ->allowHtml($this->configuration()['activity_description']['allow_html'])
91 | ->modifyState($activityDescription),
92 | ActivityDate::make($this->configuration()['activity_date']['name'])
93 | ->date($this->configuration()['activity_date']['date'])
94 | ->placeholder($this->configuration()['activity_date']['placeholder'])
95 | ->allowHtml($this->configuration()['activity_description']['allow_html'])
96 | ->modifyState($activityDate),
97 | ActivityIcon::make('event')
98 | ->icon($this->configuration()['activity_icon']['icon'])
99 | ->color($this->configuration()['activity_icon']['color']),
100 | ])
101 | ->showItemsCount($this->configuration()['activity_section']['show_items_count'])
102 | ->showItemsLabel($this->configuration()['activity_section']['show_items_label'])
103 | ->showItemsIcon($this->configuration()['activity_section']['show_items_icon'])
104 | ->showItemsColor($this->configuration()['activity_section']['show_items_color'])
105 | ->aside($this->configuration()['activity_section']['aside'])
106 | ->emptyStateHeading($this->configuration()['activity_section']['empty_state_heading'])
107 | ->emptyStateDescription($this->configuration()['activity_section']['empty_state_description'])
108 | ->emptyStateIcon($this->configuration()['activity_section']['empty_state_icon'])
109 | ->headingVisible($this->configuration()['activity_section']['heading_visible'])
110 | ->extraAttributes($this->configuration()['activity_section']['extra_attributes']),
111 | ])
112 | ->columns(1);
113 | }
114 |
115 | protected function getActivites(): \Illuminate\Database\Eloquent\Collection
116 | {
117 | $activityModelClass = config('activitylog.activity_model');
118 | $activityModel = new $activityModelClass;
119 |
120 | return $activityModel::query()
121 | ->with(['causer', 'subject'])
122 | ->where('subject_id', $this->record->id)
123 | ->where('subject_type', get_class($this->record))
124 | ->orderBy('created_at', 'desc')
125 | ->get();
126 | }
127 |
128 | private function getActivityLogRecord(): Collection
129 | {
130 | $activities = $this->getActivites();
131 |
132 | $activities->transform(function ($activity) {
133 |
134 | $activity->activityData = [
135 | 'log_name' => $activity->log_name,
136 | 'description' => $activity->description,
137 | 'subject' => $activity->subject,
138 | 'event' => $activity->event,
139 | 'causer' => $activity->causer,
140 | 'properties' => json_decode($activity->properties, true),
141 | 'batch_uuid' => $activity->batch_uuid,
142 | ];
143 |
144 | return $activity;
145 | });
146 |
147 | return $activities;
148 | }
149 |
150 | private static function formatValue($value)
151 | {
152 | if ($value === null) {
153 | return '—';
154 | }
155 | if (is_array($value)) {
156 | return json_encode($value);
157 | }
158 |
159 | return $value;
160 | }
161 |
162 | private function modifiedState(): array
163 | {
164 | return [
165 | 'activity_title' => [
166 | 'modify_state' => function (array $state) {
167 | if ($state['description'] == $state['event']) {
168 | $className = Str::lower(Str::snake(class_basename($state['subject']), ' '));
169 | $causerName = $state['causer']->name ?? $state['causer']->first_name ?? $state['causer']->last_name ?? $state['causer']->username ?? 'Unknown';
170 |
171 | return new HtmlString(sprintf('The %s was %s by %s.', $className, $state['event'], $causerName));
172 | }
173 |
174 | return new HtmlString($state['description']);
175 | },
176 | ],
177 | 'activity_description' => [
178 | 'modify_state' => function (array $state) {
179 |
180 | $properties = $state['properties'];
181 |
182 | if (! empty($properties) && isset($properties['old']) && isset($properties['attributes'])) {
183 |
184 | $oldValues = $properties['old'];
185 | $newValues = $properties['attributes'];
186 |
187 | $changes = [];
188 |
189 | foreach ($newValues as $key => $newValue) {
190 | $oldValue = self::formatValue($oldValues[$key] ?? null);
191 | $newValue = self::formatValue($newValue);
192 |
193 | if ($oldValue != $newValue) {
194 | $changes[] = "- {$key} from ".htmlspecialchars($oldValue).' to '.htmlspecialchars($newValue).'';
195 | }
196 | }
197 |
198 | $causerName = $state['causer']->name ?? $state['causer']->first_name ?? $state['causer']->last_name ?? $state['causer']->username ?? 'Unknown';
199 |
200 | return new HtmlString(sprintf('%s %s the following:
%s', $causerName, $state['event'], implode('
', $changes)));
201 | }
202 |
203 | return null;
204 | },
205 | ],
206 | 'activity_date' => [
207 | 'modify_state' => function ($state) {
208 | return new HtmlString($state);
209 | },
210 | ],
211 | ];
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/src/Enums/IconAnimation.php:
--------------------------------------------------------------------------------
1 | record = $this->resolveRecord($record);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------