Customers table view mode

This commit is contained in:
FreeScout
2026-10-02 04:22:53 -07:00
parent ae038154db
commit 65e963e163
6 changed files with 167 additions and 11 deletions

View File

@@ -12,6 +12,7 @@ class EncryptCookies extends Middleware
* @var array
*/
protected $except = [
'in_app'
'in_app',
'customers_view',
];
}

View File

@@ -1794,6 +1794,18 @@ class Helper
return (int)$request->cookie('in_app');
}
/**
* Customers list view mode: cards (default) or table.
* The cookie is set from JS (see setCustomersView() in main.js).
*/
public static function getCustomersView($request = null)
{
if (!$request) {
$request = app('request');
}
return $request->cookie('customers_view') == 'table' ? 'table' : 'cards';
}
/**
* Get identifier for queue:work
*/

79
public/css/style.css vendored
View File

@@ -3677,6 +3677,75 @@ ul.sidebar-block-list {
.customers-pager a:hover {
color: #333;
}
/* Customers list: table view */
.customers-view-switch {
color: #93a1af;
cursor: pointer;
margin-left: 6px;
font-size: 13px;
top: 2px;
}
.customers-view-switch:hover {
color: #333;
}
.customers-table {
background-color: #fff;
/* Fixed layout: long values can not stretch the table, they are truncated. */
table-layout: fixed;
width: 100%;
}
.customers-table-col-name {
width: 34%;
}
.customers-table-col-email {
width: 30%;
}
.customers-table-col-phone {
width: 16%;
}
.customers-table-col-company {
width: 20%;
}
.customers-table > thead > tr > th,
.customers-table > tbody > tr > td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.customers-table > thead > tr > th {
color: #93a1af;
font-weight: normal;
border-bottom-width: 1px;
}
.customers-table > tbody > tr > td {
vertical-align: middle;
}
.customers-table-name > a {
color: #2a3b47;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.customers-table-photo {
width: 28px;
height: 28px;
margin-right: 6px;
border-radius: 50%;
object-fit: cover;
}
@media (max-width:582px) {
.customers-table > thead > tr > th:nth-child(n+3),
.customers-table > tbody > tr > td:nth-child(n+3) {
display: none;
}
.customers-table-col-name {
width: 55%;
}
.customers-table-col-email {
width: 45%;
}
}
/*.customers-pager a:hover,
.customers-pager a:active
.customers-pager a:focus, {
@@ -4195,6 +4264,16 @@ a.help-icon:hover {
.text-larger {
font-size: 16.8px;
}
.text-link,
.text-link:focus {
color: #0078D7!important;
text-decoration: none!important;
cursor: pointer;
}
.text-link:hover {
color: #000!important;
text-decoration: none!important;
}
#loader-main {
position: fixed;
display: none;

39
public/js/main.js vendored
View File

@@ -2955,6 +2955,7 @@ function searchInit()
starConversationInit();
customersPagination();
customersViewSwitchInit();
$(".sidebar-menu .menu-link a").filter('[data-filter]').click(function(e){
var trigger = $(this);
@@ -3136,6 +3137,44 @@ function conversationPagination()
});
}
// Customers list view: cards (default) or table.
// The choice is stored in the "customers_view" cookie and used by the server
// when rendering the list (Helper::getCustomersView()).
function setCustomersView(view)
{
view = (view == 'table') ? 'table' : 'cards';
// SameSite=Lax and no Secure flag, so the cookie works on HTTP installations too.
setCookie('customers_view', view, {samesite: 'Lax'});
updateCustomersViewSwitch(view);
loadCustomers();
}
function updateCustomersViewSwitch(view)
{
$('.customers-view-switch').each(function() {
var button = $(this);
var is_table = (view == 'table');
var title = is_table ? button.attr('data-title-cards') : button.attr('data-title-table');
// Icon shows the view the user will switch to.
button.toggleClass('glyphicon-th-large', is_table)
.toggleClass('glyphicon-th-list', !is_table)
.attr('title', title)
.attr('data-original-title', title);
});
}
function customersViewSwitchInit()
{
$('.customers-view-switch').click(function(e) {
setCustomersView(getCookie('customers_view') == 'table' ? 'cards' : 'table');
// The switch is inside the tab link.
e.preventDefault();
e.stopPropagation();
});
}
function customersPagination()
{
$(".table-customers .pager-nav").click(function(e){

View File

@@ -157,7 +157,7 @@
@if (Eventy::filter('search.is_tab_visible', true, App\Conversation::SEARCH_MODE_CONV))
<li @if ($mode == App\Conversation::SEARCH_MODE_CONV)class="active search-tab-conv"@endif><a href="{{ \Helper::fixProtocol(request()->fullUrlWithQuery(['mode' => App\Conversation::SEARCH_MODE_CONV])) }}">{{ __('Conversations') }} <b>({{ $conversations->total() }})</b>@action('search.conversations_tab_append', $filters, $conversations->total())</a></li>
@endif
<li @if ($mode == App\Conversation::SEARCH_MODE_CUSTOMERS)class="active"@endif><a href="{{ \Helper::fixProtocol(request()->fullUrlWithQuery(['mode' => App\Conversation::SEARCH_MODE_CUSTOMERS])) }}">{{ __('Customers') }} <b>({{ $customers->total() }})</b></a></li>
<li @if ($mode == App\Conversation::SEARCH_MODE_CUSTOMERS)class="active"@endif><a href="@if ($mode == App\Conversation::SEARCH_MODE_CUSTOMERS)#@else{{ \Helper::fixProtocol(request()->fullUrlWithQuery(['mode' => App\Conversation::SEARCH_MODE_CUSTOMERS])) }}@endif">{{ __('Customers') }} <b>({{ $customers->total() }})</b>@if ($mode == App\Conversation::SEARCH_MODE_CUSTOMERS) <i class="text-link glyphicon @if (\Helper::getCustomersView() == 'table') glyphicon-th-large @else glyphicon-th-list @endif customers-view-switch" role="button" data-title-table="{{ __('Table View') }}" data-title-cards="{{ __('Cards View') }}" title="@if (\Helper::getCustomersView() == 'table'){{ __('Cards View') }}@else{{ __('Table View') }}@endif" data-toggle="tooltip"></i>@endif</a></li>
</ul>
@if ($mode == App\Conversation::SEARCH_MODE_CONV)
@include('conversations/conversations_table', ['mailbox' => $search_mailbox, 'params' => ['target_blank' => false, 'show_mailbox' => (count(Auth::user()->mailboxesCanView(true)) > 1)]])

View File

@@ -1,15 +1,40 @@
@if (count($customers))
<div class="table-customers" data-page="{{ (int)request()->get('page', 1) }}">
<div class="container">
<div class="card-list margin-top">
@foreach ($customers as $customer)
<a href="{{ Eventy::filter('customer.card.url', route('customers.update', ['id' => $customer->id]), $customer) }}" class="card hover-shade" @action('customer.card.link', $customer) >
<img src="{{ $customer->getPhotoUrl() }}" />
<h4 title="{{ $customer->first_name }} {{ $customer->last_name }}">{{ $customer->first_name }} {{ $customer->last_name }}</h4>
<p class="text-truncate"><small>{{ $customer->getEmailOrPhone() }}</small></p>
</a>
@endforeach
</div>
@if (\Helper::getCustomersView() == 'table')
<table class="table table-striped table-hover customers-table margin-top">
<thead>
<tr>
<th class="customers-table-col-name">{{ __('Name') }}</th>
<th class="customers-table-col-email">{{ __('Email') }}</th>
<th class="customers-table-col-phone">{{ __('Phone') }}</th>
<th class="customers-table-col-company">{{ __('Company') }}</th>
</tr>
</thead>
<tbody>
@foreach ($customers as $customer)
<tr>
<td class="customers-table-name">
<a href="{{ Eventy::filter('customer.card.url', route('customers.update', ['id' => $customer->id]), $customer) }}" @action('customer.card.link', $customer) title="{{ $customer->first_name }} {{ $customer->last_name }}"><img src="{{ $customer->getPhotoUrl() }}" class="customers-table-photo" /> {{ $customer->first_name }} {{ $customer->last_name }}</a>
</td>
<td title="{{ $customer->getMainEmail() }}">{{ $customer->getMainEmail() }}</td>
<td title="{{ $customer->getMainPhoneNumber() }}">{{ $customer->getMainPhoneNumber() }}</td>
<td title="{{ $customer->company }}">{{ $customer->company }}</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="card-list margin-top">
@foreach ($customers as $customer)
<a href="{{ Eventy::filter('customer.card.url', route('customers.update', ['id' => $customer->id]), $customer) }}" class="card hover-shade" @action('customer.card.link', $customer) >
<img src="{{ $customer->getPhotoUrl() }}" />
<h4 title="{{ $customer->first_name }} {{ $customer->last_name }}">{{ $customer->first_name }} {{ $customer->last_name }}</h4>
<p class="text-truncate"><small>{{ $customer->getEmailOrPhone() }}</small></p>
</a>
@endforeach
</div>
@endif
</div>
@if ($customers->lastPage() > 1)