Co nowego:
- Wsparcie Active Directory dla LDAP (obok LLDAP/OpenLDAP), przełącznik typu
  katalogu w Admin > Integracje.
- Wyszukiwarka klientów dla operatora (Operator > Klienci).
- Stronicowanie kolejki operatora (50/stronę) i dashboardu klienta (20/stronę).
- Globalna wyszukiwarka zgłoszeń (Ctrl+K/Cmd+K) z operatorami w stylu Gmaila
  (od:, temat:, treść:, numer:), plus przycisk "Szukaj" w panelu bocznym.
- Ostatnio przeglądane zgłoszenia w panelu bocznym operatora.
- Przeprojektowany pasek nawigacji: suwak Klient/Operator/Administrator
  zamiast rozwijanego menu, bogatsze menu profilu (nazwa/e-mail/role),
  dynamiczne tytuły kart przeglądarki na każdej podstronie.
- Narzędzie do jednorazowego importu historii zgłoszeń z Heska 3.x
  (scripts/hesk-import/).
- Poprawka: paginacja pokazywała surowe klucze tłumaczeń zamiast tekstu
  (brakujący lang/pl/pagination.php).

Zaktualizowana dokumentacja: README, CLAUDE.md, install.md, ARCHITECTURE.md,
CHANGELOG.md, wiki/*.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 00:58:35 +02:00
parent 7a8cf2037c
commit 03c6ec7cae
57 changed files with 2029 additions and 162 deletions

View File

@@ -97,6 +97,26 @@ class Ticket extends Model
return $this->watchers()->where('users.id', $user->id)->exists();
}
public function viewers(): BelongsToMany
{
return $this->belongsToMany(User::class, 'ticket_views')->withPivot('viewed_at');
}
/**
* Bumps viewed_at for an existing view rather than duplicating it sync()
* updates pivot columns on already-attached rows, not just new ones.
*
* Formatted explicitly with microseconds: a plain Carbon instance gets
* bound through the connection's default date format (whole seconds,
* regardless of the column's own declared precision), so two views in
* the same second would otherwise tie and silently fall back to sorting
* by row id instead of actual recency.
*/
public function recordViewBy(User $user): void
{
$this->viewers()->syncWithoutDetaching([$user->id => ['viewed_at' => now()->format('Y-m-d H:i:s.u')]]);
}
public function status(): BelongsTo
{
return $this->belongsTo(Status::class, 'status_key');

View File

@@ -189,6 +189,13 @@ class User extends Authenticatable implements LdapAuthenticatable
return $this->belongsToMany(Ticket::class, 'ticket_watchers');
}
public function recentlyViewedTickets(): BelongsToMany
{
return $this->belongsToMany(Ticket::class, 'ticket_views')
->withPivot('viewed_at')
->orderByPivot('viewed_at', 'desc');
}
public function ticketsAssigned(): HasMany
{
return $this->hasMany(Ticket::class, 'assignee_id');