This commit is contained in:
2025-06-16 17:38:48 +02:00
parent 3a374baaba
commit d6c7742501
3 changed files with 317 additions and 153 deletions

View File

@@ -8,35 +8,59 @@
@inject IManageDataService ManageData @inject IManageDataService ManageData
@inject IJSRuntime JS @inject IJSRuntime JS
<HeaderLayout Title="@_headerTitle"
ShowFilter="true"
ShowCalendarToggle="true"
OnFilterToggle="ToggleFilter"
OnCalendarToggle="ToggleExpanded"/>
<HeaderLayout Title="@CurrentMonth.ToString("MMMM yyyy", new System.Globalization.CultureInfo("it-IT")).FirstCharToUpper()" <div @ref="weekSliderRef" class="container week-slider @(Expanded ? "expanded" : "") @(SliderAnimation)">
ShowFilter="true" @if (Expanded)
ShowCalendarToggle="true" {
OnFilterToggle="ToggleFilter" <!-- Vista mensile -->
OnCalendarToggle="ToggleExpanded" /> @foreach (var nomeGiorno in GiorniSettimana)
<div @ref="weekSliderRef" class="container week-slider @(Expanded ? "expanded" : "") @(SliderAnimation)">
@if (Expanded)
{ {
<!-- Vista mensile --> <div class="week-day">
@foreach (var nomeGiorno in GiorniSettimana) <div>@nomeGiorno</div>
</div>
}
@foreach (var unused in Enumerable.Range(0, StartOffset))
{
<div class="day" style="visibility: hidden"></div>
}
@if (_isInitialized && _monthDaysData.Length > 0)
{
@for (var d = 1; d <= DaysInMonth; d++)
{ {
<div class="week-day"> var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d);
<div>@nomeGiorno</div> var dayData = _monthDaysData[d - 1];
<div class="day @dayData.CssClass"
@onclick="() => SelezionaDataDalMese(day)">
<div>@d</div>
@if (dayData.HasEvents)
{
<div class="event-dot-container" style="margin-top: 2px;">
@foreach (var cat in dayData.EventCategories)
{
<div class="event-dot @cat.CssClass" title="@cat.Title"></div>
}
</div>
}
</div> </div>
} }
}
@foreach (var unused in Enumerable.Range(0, StartOffset)) else
{ {
<div class="day" style="visibility: hidden"></div> @* Fallback rendering per prima inizializzazione *@
}
@for (var d = 1; d <= DaysInMonth; d++) @for (var d = 1; d <= DaysInMonth; d++)
{ {
var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d); var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d);
var isSelected = IsSameDay(day, SelectedDate); var isSelected = IsSameDay(day, SelectedDate);
var isToday = IsSameDay(day, DateTime.Today); var isToday = IsSameDay(day, DateTime.Today);
var events = ReturnFilteredActivity(day); var events = GetEventsForDay(day);
<div class="day @(isSelected ? "selected" : (isToday ? "today" : ""))" <div class="day @(isSelected ? "selected" : (isToday ? "today" : ""))"
@onclick="() => SelezionaDataDalMese(day)"> @onclick="() => SelezionaDataDalMese(day)">
@@ -52,29 +76,64 @@
} }
</div> </div>
} }
}
@foreach (var unused in Enumerable.Range(0, EndOffset)) @foreach (var unused in Enumerable.Range(0, EndOffset))
{
<div class="day" style="visibility: hidden"></div>
}
}
else
{
<!-- Vista settimanale -->
@if (_isInitialized && _weekDaysData.Length == 7 && _weekDaysData[0].Date != default)
{
@for (int i = 0; i < 7; i++)
{ {
<div class="day" style="visibility: hidden"></div> var dayData = _weekDaysData[i];
var day = dayData.Date;
<div class="week-day">
<div>@dayData.DayName</div>
<div class="day @dayData.CssClass"
@onclick="() => SelezionaData(day)">
<div>@day.Day</div>
@if (dayData.HasEvents)
{
<div class="event-dot-container" style="margin-top: 2px;">
@foreach (var cat in dayData.EventCategories)
{
<div class="event-dot @cat.CssClass" title="@cat.Title"></div>
}
</div>
}
</div>
</div>
} }
} }
else else
{ {
<!-- Vista settimanale --> var start = GetStartOfWeek(SelectedDate);
@foreach (var day in DaysOfWeek) var culture = new System.Globalization.CultureInfo("it-IT");
for (var i = 0; i < 7; i++)
{ {
var day = start.AddDays(i);
var isSelected = IsSameDay(day, SelectedDate); var isSelected = IsSameDay(day, SelectedDate);
var isToday = IsSameDay(day, DateTime.Today); var isToday = IsSameDay(day, DateTime.Today);
var events = GetEventsForDay(day);
<div class="week-day"> <div class="week-day">
<div>@day.ToString("ddd", new System.Globalization.CultureInfo("it-IT"))</div> <div>@day.ToString("ddd", culture)</div>
<div class="day @(isSelected ? "selected" : (isToday ? "today" : ""))" <div class="day @(isSelected ? "selected" : (isToday ? "today" : ""))"
@onclick="() => SelezionaData(day)"> @onclick="() => SelezionaData(day)"
aria-label="@day.ToString("dddd d MMMM", culture)">
<div>@day.Day</div> <div>@day.Day</div>
@if (ReturnFilteredActivity(day).Any()) @if (events.Any())
{ {
<div class="event-dot-container" style="margin-top: 2px;"> <div class="event-dot-container" style="margin-top: 2px;">
@foreach (var cat in ReturnFilteredActivity(day).Select(x => x.Category).Distinct()) @foreach (var cat in events.Select(x => x.Category).Distinct())
{ {
<div class="event-dot @cat.ConvertToHumanReadable()" title="@cat.ConvertToHumanReadable()"></div> <div class="event-dot @cat.ConvertToHumanReadable()" title="@cat.ConvertToHumanReadable()"></div>
} }
@@ -84,22 +143,23 @@
</div> </div>
} }
} }
</div> }
</div>
<div class="container appointments"> <div class="container appointments">
@if (IsLoading) @if (IsLoading)
{ {
<SpinnerLayout FullScreen="false" /> <SpinnerLayout FullScreen="false"/>
} }
else if (FilteredActivities is { Count: > 0 }) else if (FilteredActivities is { Count: > 0 })
{ {
<Virtualize Items="FilteredActivities" Context="activity"> <Virtualize Items="FilteredActivities" Context="activity">
<ActivityCard Activity="activity" ActivityChanged="OnActivityChanged" /> <ActivityCard Activity="activity" ActivityChanged="OnActivityChanged"/>
</Virtualize> </Virtualize>
} }
else else
{ {
<NoDataAvailable Text="Nessuna attività trovata" ImageSource="_content/Template.Shared/images/undraw_file-search_cbur.svg" /> <NoDataAvailable Text="Nessuna attività trovata" ImageSource="_content/Template.Shared/images/undraw_file-search_cbur.svg"/>
} }
</div> </div>
@@ -107,6 +167,19 @@
@code { @code {
// Modelli per ottimizzazione rendering
private record DayData(DateTime Date, string CssClass, bool HasEvents, CategoryData[] EventCategories, string DayName = "");
private record CategoryData(string CssClass, string Title);
// Cache per rendering
private DayData[] _monthDaysData = Array.Empty<DayData>();
private DayData[] _weekDaysData = new DayData[7];
private string _headerTitle = string.Empty;
private Dictionary<DateTime, List<ActivityDTO>> _eventsCache = new();
private Dictionary<DateTime, CategoryData[]> _categoriesCache = new();
private bool _isInitialized = false;
// Stato UI // Stato UI
private bool Expanded { get; set; } = false; private bool Expanded { get; set; } = false;
private string SliderAnimation { get; set; } = string.Empty; private string SliderAnimation { get; set; } = string.Empty;
@@ -141,16 +214,6 @@
} }
} }
// Supporto rendering settimana
private IEnumerable<DateTime> DaysOfWeek
{
get
{
var start = GetStartOfWeek(SelectedDate);
return Enumerable.Range(0, 7).Select(i => start.AddDays(i));
}
}
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
@@ -162,17 +225,141 @@
_internalMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1); _internalMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1);
await LoadMonthData(); await LoadMonthData();
if (!Expanded) PrepareRenderingData();
ApplyFilter(); _isInitialized = true;
ApplyFilter();
StateHasChanged(); StateHasChanged();
} }
} }
// Metodo per preparare i dati di rendering una sola volta
private void PrepareRenderingData()
{
PrepareHeaderTitle();
PrepareEventsCache();
if (Expanded)
{
PrepareMonthDaysData();
}
else
{
PrepareWeekDaysData();
}
}
private void PrepareHeaderTitle()
{
_headerTitle = CurrentMonth.ToString("MMMM yyyy", new System.Globalization.CultureInfo("it-IT")).FirstCharToUpper();
}
private void PrepareEventsCache()
{
_eventsCache.Clear();
_categoriesCache.Clear();
// Raggruppa le attività per data
var activitiesByDate = MonthActivities
.GroupBy(x => (x.EffectiveDate ?? x.EstimatedDate!).Value.Date)
.ToDictionary(g => g.Key, g => g.ToList());
foreach (var (date, activities) in activitiesByDate)
{
_eventsCache[date] = activities;
// Pre-calcola le categorie per ogni giorno
var categories = activities
.Select(x => x.Category)
.Distinct()
.Select(cat => new CategoryData(cat.ConvertToHumanReadable(), cat.ConvertToHumanReadable()))
.ToArray();
_categoriesCache[date] = categories;
}
}
private void PrepareMonthDaysData()
{
_monthDaysData = new DayData[DaysInMonth];
var today = DateTime.Today;
for (int d = 1; d <= DaysInMonth; d++)
{
var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d);
var isSelected = day.Date == SelectedDate.Date;
var isToday = day.Date == today;
var cssClass = isSelected ? "selected" : (isToday ? "today" : "");
var hasEvents = _eventsCache.ContainsKey(day.Date);
var eventCategories = hasEvents ? GetFilteredCategoriesForDay(day.Date) : Array.Empty<CategoryData>();
_monthDaysData[d - 1] = new DayData(day, cssClass, eventCategories.Length > 0, eventCategories);
}
}
private void PrepareWeekDaysData()
{
var start = GetStartOfWeek(SelectedDate);
var today = DateTime.Today;
var culture = new System.Globalization.CultureInfo("it-IT");
for (int i = 0; i < 7; i++)
{
var day = start.AddDays(i);
var isSelected = day.Date == SelectedDate.Date;
var isToday = day.Date == today;
var cssClass = isSelected ? "selected" : (isToday ? "today" : "");
var dayName = day.ToString("ddd", culture);
var hasEvents = _eventsCache.ContainsKey(day.Date);
var eventCategories = hasEvents ? GetFilteredCategoriesForDay(day.Date) : Array.Empty<CategoryData>();
_weekDaysData[i] = new DayData(day, cssClass, eventCategories.Length > 0, eventCategories, dayName);
}
}
private CategoryData[] GetFilteredCategoriesForDay(DateTime date)
{
if (!_categoriesCache.TryGetValue(date, out var categories))
return Array.Empty<CategoryData>();
if (Filter.ClearFilter)
return categories;
// Applica i filtri alle categorie
var filteredActivities = GetFilteredActivitiesForDay(date);
if (!filteredActivities.Any())
return Array.Empty<CategoryData>();
return filteredActivities
.Select(x => x.Category)
.Distinct()
.Select(cat => new CategoryData(cat.ConvertToHumanReadable(), cat.ConvertToHumanReadable()))
.ToArray();
}
private List<ActivityDTO> GetFilteredActivitiesForDay(DateTime date)
{
if (!_eventsCache.TryGetValue(date, out var activities))
return new List<ActivityDTO>();
if (Filter.ClearFilter)
return activities;
return activities.Where(x =>
(!Filter.Text.IsNullOrEmpty() && x.ActivityDescription != null && x.ActivityDescription.ContainsIgnoreCase(Filter.Text!)) ||
(x.ActivityTypeId != null && !Filter.Type.IsNullOrEmpty() && x.ActivityTypeId.Equals(Filter.Type)) ||
(x.ActivityResultId != null && !Filter.Result.IsNullOrEmpty() && x.ActivityResultId.Equals(Filter.Result)) ||
(x.UserName != null && !Filter.User.IsNullOrEmpty() && Filter.User!.Contains(x.UserName)) ||
(Filter.Category != null && x.Category.Equals(Filter.Category))
).ToList();
}
[JSInvokable] [JSInvokable]
public async Task OnSwipeLeft() public async Task OnSwipeLeft()
{ {
await CambiaPeriodo(1); await CambiaPeriodo(1);
PrepareRenderingData();
StateHasChanged(); StateHasChanged();
if (Expanded) if (Expanded)
{ {
@@ -184,6 +371,7 @@
public async Task OnSwipeRight() public async Task OnSwipeRight()
{ {
await CambiaPeriodo(-1); await CambiaPeriodo(-1);
PrepareRenderingData();
StateHasChanged(); StateHasChanged();
if (Expanded) if (Expanded)
{ {
@@ -210,7 +398,6 @@
{ {
if (Expanded) if (Expanded)
{ {
// Cambio solo il mese visualizzato, NON cambiare SelectedDate
var y = CurrentMonth.Year; var y = CurrentMonth.Year;
var m = CurrentMonth.Month + direzione; var m = CurrentMonth.Month + direzione;
if (m < 1) if (m < 1)
@@ -229,7 +416,6 @@
} }
else else
{ {
// Cambio settimana: aggiorno anche il giorno selezionato
await SelezionaData(SelectedDate.AddDays(7 * direzione)); await SelezionaData(SelectedDate.AddDays(7 * direzione));
_internalMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1); _internalMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1);
} }
@@ -241,16 +427,17 @@
if (Expanded) if (Expanded)
{ {
SliderAnimation = "collapse-animation"; SliderAnimation = "collapse-animation";
StateHasChanged();
Expanded = false; Expanded = false;
} }
else else
{ {
Expanded = true; Expanded = true;
SliderAnimation = "expand-animation"; SliderAnimation = "expand-animation";
StateHasChanged();
} }
PrepareRenderingData();
StateHasChanged();
SliderAnimation = ""; SliderAnimation = "";
StateHasChanged(); StateHasChanged();
} }
@@ -261,7 +448,6 @@
IsLoading = true; IsLoading = true;
StateHasChanged(); StateHasChanged();
// Carica tutte le attività del mese corrente visualizzato
var start = CurrentMonth; var start = CurrentMonth;
var end = start.AddDays(DaysInMonth - 1); var end = start.AddDays(DaysInMonth - 1);
var activities = await ManageData.GetActivity(x => var activities = await ManageData.GetActivity(x =>
@@ -269,6 +455,7 @@
(x.EffectiveDate >= start && x.EffectiveDate <= end)); (x.EffectiveDate >= start && x.EffectiveDate <= end));
MonthActivities = activities.OrderBy(x => x.EffectiveDate ?? x.EstimatedDate).ToList(); MonthActivities = activities.OrderBy(x => x.EffectiveDate ?? x.EstimatedDate).ToList();
PrepareRenderingData();
IsLoading = false; IsLoading = false;
StateHasChanged(); StateHasChanged();
} }
@@ -277,7 +464,6 @@
private async Task SelezionaData(DateTime day) private async Task SelezionaData(DateTime day)
{ {
SelectedDate = day; SelectedDate = day;
StateHasChanged();
var cacheInternalMonth = _internalMonth; var cacheInternalMonth = _internalMonth;
_internalMonth = new DateTime(day.Year, day.Month, 1); _internalMonth = new DateTime(day.Year, day.Month, 1);
@@ -286,6 +472,10 @@
{ {
await LoadMonthData(); await LoadMonthData();
} }
else
{
PrepareRenderingData();
}
ApplyFilter(); ApplyFilter();
StateHasChanged(); StateHasChanged();
@@ -295,12 +485,14 @@
private async Task SelezionaDataDalMese(DateTime day) private async Task SelezionaDataDalMese(DateTime day)
{ {
SelectedDate = day; SelectedDate = day;
ApplyFilter();
// Chiudi la vista mese e passa alla settimana, con animazione
SliderAnimation = "collapse-animation"; SliderAnimation = "collapse-animation";
StateHasChanged();
Expanded = false; Expanded = false;
_internalMonth = new DateTime(day.Year, day.Month, 1); // Sync il mese visualizzato _internalMonth = new DateTime(day.Year, day.Month, 1);
PrepareRenderingData();
ApplyFilter();
StateHasChanged();
SliderAnimation = ""; SliderAnimation = "";
StateHasChanged(); StateHasChanged();
} }
@@ -315,7 +507,7 @@
} }
private List<ActivityDTO> GetEventsForDay(DateTime day) private List<ActivityDTO> GetEventsForDay(DateTime day)
=> MonthActivities?.Where(x => (x.EffectiveDate ?? x.EstimatedDate) == day.Date).ToList() ?? []; => _eventsCache.TryGetValue(day.Date, out var events) ? events : new List<ActivityDTO>();
public void Dispose() public void Dispose()
{ {
@@ -330,6 +522,7 @@
if (indexActivity != null && !newActivity.IsNullOrEmpty()) if (indexActivity != null && !newActivity.IsNullOrEmpty())
{ {
MonthActivities![indexActivity.Value] = newActivity[0]; MonthActivities![indexActivity.Value] = newActivity[0];
PrepareRenderingData(); // Ricalcola i dati di rendering
} }
ApplyFilter(); ApplyFilter();
@@ -344,40 +537,25 @@
private void ApplyFilter() private void ApplyFilter()
{ {
FilteredActivities = GetEventsForDay(SelectedDate); FilteredActivities = GetFilteredActivitiesForDay(SelectedDate);
if (!Filter.ClearFilter) // Aggiorna i dati di rendering se il filtro è cambiato
if (Expanded)
{ {
FilteredActivities = GetEventsForDay(SelectedDate)? PrepareMonthDaysData();
.Where(x => }
(!Filter.Text.IsNullOrEmpty() && x.ActivityDescription != null && x.ActivityDescription.ContainsIgnoreCase(Filter.Text!)) || else
(x.ActivityTypeId != null && !Filter.Type.IsNullOrEmpty() && x.ActivityTypeId.Equals(Filter.Type)) || {
(x.ActivityResultId != null && !Filter.Result.IsNullOrEmpty() && x.ActivityResultId.Equals(Filter.Result)) || PrepareWeekDaysData();
(x.UserName != null && !Filter.User.IsNullOrEmpty() && Filter.User!.Contains(x.UserName)) ||
(Filter.Category != null && x.Category.Equals(Filter.Category))
)
.ToList() ?? [];
} }
StateHasChanged(); StateHasChanged();
} }
// Metodo ottimizzato per il rendering dei filtri
private List<ActivityDTO> ReturnFilteredActivity(DateTime day) private List<ActivityDTO> ReturnFilteredActivity(DateTime day)
{ {
if (!Filter.ClearFilter) return GetFilteredActivitiesForDay(day);
{
return GetEventsForDay(day)?
.Where(x =>
(!Filter.Text.IsNullOrEmpty() && x.ActivityDescription != null && x.ActivityDescription.ContainsIgnoreCase(Filter.Text!)) ||
(x.ActivityTypeId != null && !Filter.Type.IsNullOrEmpty() && x.ActivityTypeId.Equals(Filter.Type)) ||
(x.ActivityResultId != null && !Filter.Result.IsNullOrEmpty() && x.ActivityResultId.Equals(Filter.Result)) ||
(x.UserName != null && !Filter.User.IsNullOrEmpty() && Filter.User!.Contains(x.UserName)) ||
(Filter.Category != null && x.Category.Equals(Filter.Category))
)
.ToList() ?? [];
}
return GetEventsForDay(day);
} }
} }

View File

@@ -10,41 +10,34 @@
} }
else else
{ {
<div class="center-box container d-flex justify-content-center align-items-center min-vh-100"> <div class="login-page">
<div class="row rounded-4 bg-white"> <div class="container container-top-logo">
<span>SalesBook</span>
<div class="appName rounded-4 d-flex justify-content-center align-items-center flex-column"> </div>
<span>Nome App</span> <div class="container container-login">
</div> <div class="login-form-container">
<div class="input-group">
<div class="col-md-6 right-box"> <MudTextField @bind-Value="UserData.Username" Label="Username" Variant="Variant.Outlined"/>
<div class="row align-items-center"> </div>
<div class="input-group mb-2"> <div class="input-group">
<MudTextField @bind-Value="UserData.Username" Label="Username" Variant="Variant.Text"/> <MudTextField InputType="@_passwordInput" @bind-Value="UserData.Password" Label="Password" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@_passwordInputIcon" OnAdornmentClick="ShowPassword" AdornmentAriaLabel="Show Password" />
</div> </div>
<div class="input-group mb-2"> <div class="input-group mb-2">
<MudTextField InputType="@_passwordInput" @bind-Value="UserData.Password" Label="Password" Variant="Variant.Text" Adornment="Adornment.End" AdornmentIcon="@_passwordInputIcon" OnAdornmentClick="ShowPassword" AdornmentAriaLabel="Show Password" /> <MudTextField @bind-Value="UserData.CodHash" Label="Profilo azienda" Variant="Variant.Outlined" />
</div>
<div class="input-group mb-4">
<MudTextField @bind-Value="UserData.CodHash" Label="Profilo azienda" Variant="Variant.Text"/>
</div>
<div class="input-group">
<div class="button-login" @onclick="SignInUser">
<span>Login</span>
</div>
</div>
</div> </div>
<div class="my-4 login-footer"> <MudButton OnClick="SignInUser" Color="Color.Primary" Variant="Variant.Filled">Login</MudButton>
<span>Powered by</span>
<img src="_content/Template.Shared/images/logoIntegry.svg" class="img-fluid" alt="Integry">
</div>
@if (_attemptFailed)
{
<MudAlert Class="my-3" Dense="true" Severity="Severity.Error" Variant="Variant.Filled">@ErrorMessage</MudAlert>
}
</div> </div>
<div class="my-4 login-footer">
<span>Powered by</span>
<img src="_content/Template.Shared/images/logoIntegry.svg" class="img-fluid" alt="Integry">
</div>
@if (_attemptFailed)
{
<MudAlert Class="my-3" Dense="true" Severity="Severity.Error" Variant="Variant.Filled">@ErrorMessage</MudAlert>
}
</div> </div>
</div> </div>
} }

View File

@@ -1,46 +1,44 @@
.center-box { .login-page {
margin-top: -1.1rem !important; /* remove page padding */ height: 100%;
display: flex;
flex-direction: column;
background: hsl(from var(--mud-palette-primary) h s 96%);
} }
.box-area { .container-top-logo > span {
width: 930px; font-size: x-large;
}
.right-box {
padding: 15px 30px 0 30px;
}
::placeholder {
font-size: 16px;
}
.rounded-4 {
border-radius: 20px;
}
.rounded-5 {
border-radius: 30px;
}
.bg-white {
background: var(--mud-palette-surface) !important;
}
.appName {
margin-top: 15px;
font-size: large;
font-weight: 900; font-weight: 900;
color: var(--mud-palette-primary) color: var(--mud-palette-primary)
} }
.button-login { .container-login > span {
font-size: large;
font-weight: 900;
text-align: center; text-align: center;
background-color: var(--mud-palette-primary); }
border-radius: 6px;
padding: .3rem 2rem; .container-top-logo {
width: 100%; height: 35vh;
font-weight: 700; display: flex;
color: var(--mud-palette-appbar-text); align-items: center;
justify-content: center;
}
.login-form-container {
display: flex;
flex-direction: column;
gap: 1rem;
}
.container-login {
background: var(--mud-palette-surface);
border-top-left-radius: 16px;
border-top-right-radius: 16px;
height: 100%;
display: flex;
flex-direction: column;
padding: 4px 16px 16px;
box-shadow: 0 -2px 10px rgba(165, 165, 165, 0.5);
} }
.login-footer { .login-footer {
@@ -58,8 +56,3 @@
height: 15px; height: 15px;
margin-left: 4px; margin-left: 4px;
} }
.container > .bg-white {
box-shadow: var(--card-shadow);
border: 1px solid var(--card-border-color);
}