Merge pull request #40 from eitchtee/new_datepicker

feat(datepicker): drop native datepickers in favor of AirDatePicker for better compatibility
This commit is contained in:
Herculino Trotta
2025-01-14 23:49:27 -03:00
committed by GitHub
19 changed files with 545 additions and 35 deletions

View File

@@ -61,7 +61,6 @@ class DynamicModelChoiceField(forms.ModelChoiceField):
self._created_instance = instance self._created_instance = instance
return instance return instance
except Exception as e: except Exception as e:
print(e)
raise ValidationError( raise ValidationError(
self.error_messages["invalid_choice"], code="invalid_choice" self.error_messages["invalid_choice"], code="invalid_choice"
) )

View File

@@ -1,9 +1,11 @@
import datetime import datetime
from django import forms from django import forms
from django.db import models
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.common.widgets.datepicker import AirMonthYearPickerInput
from apps.common.widgets.month_year import MonthYearWidget from apps.common.widgets.month_year import MonthYearWidget
@@ -27,7 +29,7 @@ class MonthYearModelField(models.DateField):
class MonthYearFormField(forms.DateField): class MonthYearFormField(forms.DateField):
widget = MonthYearWidget widget = AirMonthYearPickerInput
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -42,7 +44,11 @@ class MonthYearFormField(forms.DateField):
date = datetime.datetime.strptime(value, "%Y-%m") date = datetime.datetime.strptime(value, "%Y-%m")
return date.replace(day=1).date() return date.replace(day=1).date()
except ValueError: except ValueError:
raise ValidationError(_("Invalid date format. Use YYYY-MM.")) try:
date = datetime.datetime.strptime(value, "%Y-%m-%d")
return date.replace(day=1).date()
except ValueError:
raise ValidationError(_("Invalid date format. Use YYYY-MM."))
def prepare_value(self, value): def prepare_value(self, value):
if isinstance(value, datetime.date): if isinstance(value, datetime.date):

View File

@@ -0,0 +1,32 @@
def django_to_python_datetime(django_format):
mapping = {
# Day
"j": "%d", # Day of the month without leading zeros
"d": "%d", # Day of the month with leading zeros
"D": "%a", # Day of the week, short version
"l": "%A", # Day of the week, full version
# Month
"n": "%m", # Month without leading zeros
"m": "%m", # Month with leading zeros
"M": "%b", # Month, short version
"F": "%B", # Month, full version
# Year
"y": "%y", # Year, 2 digits
"Y": "%Y", # Year, 4 digits
# Time
"g": "%I", # Hour (12-hour), without leading zeros
"G": "%H", # Hour (24-hour), without leading zeros
"h": "%I", # Hour (12-hour), with leading zeros
"H": "%H", # Hour (24-hour), with leading zeros
"i": "%M", # Minutes
"s": "%S", # Seconds
"a": "%p", # am/pm
"A": "%p", # AM/PM
"P": "%I:%M %p",
}
python_format = django_format
for django_code, python_code in mapping.items():
python_format = python_format.replace(django_code, python_code)
return python_format

View File

@@ -0,0 +1,185 @@
import datetime
from django.forms import widgets
from django.utils import formats, translation, dates
from django.utils.formats import get_format
from apps.common.utils.django import django_to_python_datetime
class AirDatePickerInput(widgets.DateInput):
def __init__(
self,
attrs=None,
format=None,
clear_button=True,
auto_close=True,
*args,
**kwargs,
):
attrs = attrs or {}
super().__init__(attrs=attrs, format=format, *args, **kwargs)
self.clear_button = clear_button
self.auto_close = auto_close
def _get_current_language(self):
"""Get current language code in format compatible with AirDatepicker"""
lang_code = translation.get_language()
# AirDatepicker uses simple language codes
return lang_code.split("-")[0]
def build_attrs(self, base_attrs, extra_attrs=None):
attrs = super().build_attrs(base_attrs, extra_attrs)
# Add data attributes for AirDatepicker configuration
attrs["data-auto-close"] = str(self.auto_close).lower()
attrs["data-clear-button"] = str(self.clear_button).lower()
attrs["data-language"] = self._get_current_language()
attrs["data-date-format"] = self.format or get_format(
"SHORT_DATE_FORMAT", use_l10n=True
)
return attrs
def format_value(self, value):
"""Format the value for display in the widget."""
if value:
self.attrs["data-value"] = (
value # We use this to dynamically select the initial date on AirDatePicker
)
if value is None:
return ""
if isinstance(value, (datetime.date, datetime.datetime)):
return formats.date_format(
value, format=self.format or "SHORT_DATE_FORMAT", use_l10n=True
)
return str(value)
class AirDateTimePickerInput(widgets.DateTimeInput):
def __init__(
self,
attrs=None,
format=None,
timepicker=True,
clear_button=True,
auto_close=True,
*args,
**kwargs,
):
attrs = attrs or {}
super().__init__(attrs=attrs, format=format, *args, **kwargs)
self.timepicker = timepicker
self.clear_button = clear_button
self.auto_close = auto_close
def _get_current_language(self):
"""Get current language code in format compatible with AirDatepicker"""
lang_code = translation.get_language()
# AirDatepicker uses simple language codes
return lang_code.split("-")[0]
def build_attrs(self, base_attrs, extra_attrs=None):
attrs = super().build_attrs(base_attrs, extra_attrs)
# Add data attributes for AirDatepicker configuration
attrs["data-timepicker"] = str(self.timepicker).lower()
attrs["data-auto-close"] = str(self.auto_close).lower()
attrs["data-clear-button"] = str(self.clear_button).lower()
attrs["data-language"] = self._get_current_language()
attrs["data-date-format"] = self.format or get_format(
"SHORT_DATETIME_FORMAT", use_l10n=True
)
return attrs
def format_value(self, value):
"""Format the value for display in the widget."""
if value:
self.attrs["data-value"] = (
value # We use this to dynamically select the initial date on AirDatePicker
)
if value is None:
return ""
if isinstance(value, (datetime.date, datetime.datetime)):
return formats.date_format(
value, format=self.format or "SHORT_DATETIME_FORMAT", use_l10n=True
)
return str(value)
def value_from_datadict(self, data, files, name):
"""Parse the datetime string from the form data."""
value = super().value_from_datadict(data, files, name)
if value:
try:
# This does it's best to convert Django's PHP-Style date format to a datetime format and reformat the
# value to be read by Django. Probably could be improved
return datetime.datetime.strptime(
value,
self.format
or django_to_python_datetime(get_format("SHORT_DATETIME_FORMAT")),
).strftime("%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError) as e:
return value
return None
class AirMonthYearPickerInput(AirDatePickerInput):
def __init__(self, attrs=None, format=None, *args, **kwargs):
super().__init__(attrs=attrs, format=format, *args, **kwargs)
# Store the display format for AirDatepicker
self.display_format = "MMMM yyyy"
# Store the Python format for internal use
self.python_format = "%B %Y"
def _get_month_names(self):
"""Get month names using Django's date translation"""
return {dates.MONTHS[i]: i for i in range(1, 13)}
def format_value(self, value):
"""Format the value for display in the widget."""
if value:
self.attrs["data-value"] = (
value # We use this to dynamically select the initial date on AirDatePicker
)
if value is None:
return ""
if isinstance(value, str):
try:
value = datetime.datetime.strptime(value, "%Y-%m-%d").date()
except ValueError:
return value
if isinstance(value, (datetime.datetime, datetime.date)):
# Use Django's date translation
month_name = dates.MONTHS[value.month]
return f"{month_name} {value.year}"
return value
def value_from_datadict(self, data, files, name):
"""Convert the value from the widget format back to a format Django can handle."""
value = super().value_from_datadict(data, files, name)
if value:
try:
# Split the value into month name and year
month_str, year_str = value.rsplit(" ", 1)
year = int(year_str)
# Get month number from translated month name
month_names = self._get_month_names()
month = month_names.get(month_str)
if month and year:
# Return the first day of the month in Django's expected format
return datetime.date(year, month, 1).strftime("%Y-%m-%d")
except (ValueError, KeyError):
return None
return None

View File

@@ -6,9 +6,10 @@ from django.forms import CharField
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.common.widgets.crispy.submit import NoClassSubmit from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDateTimePickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.currencies.models import Currency, ExchangeRate
from apps.common.widgets.tom_select import TomSelect from apps.common.widgets.tom_select import TomSelect
from apps.currencies.models import Currency, ExchangeRate
class CurrencyForm(forms.ModelForm): class CurrencyForm(forms.ModelForm):
@@ -64,9 +65,10 @@ class CurrencyForm(forms.ModelForm):
class ExchangeRateForm(forms.ModelForm): class ExchangeRateForm(forms.ModelForm):
date = forms.DateTimeField( date = forms.DateTimeField(
widget=forms.DateTimeInput( widget=AirDateTimePickerInput(
attrs={"type": "datetime-local"}, format="%Y-%m-%dT%H:%M" clear_button=False,
) ),
label=_("Date"),
) )
class Meta: class Meta:

View File

@@ -1,13 +1,14 @@
from crispy_forms.bootstrap import FormActions from crispy_forms.bootstrap import FormActions
from django import forms
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column from crispy_forms.layout import Layout, Row, Column
from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDatePickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect from apps.common.widgets.tom_select import TomSelect
from apps.dca.models import DCAStrategy, DCAEntry from apps.dca.models import DCAStrategy, DCAEntry
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.crispy.submit import NoClassSubmit
class DCAStrategyForm(forms.ModelForm): class DCAStrategyForm(forms.ModelForm):
@@ -61,7 +62,7 @@ class DCAEntryForm(forms.ModelForm):
"notes", "notes",
] ]
widgets = { widgets = {
"date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"), "date": AirDatePickerInput(clear_button=False),
"notes": forms.Textarea(attrs={"rows": 3}), "notes": forms.Textarea(attrs={"rows": 3}),
} }

View File

@@ -8,6 +8,7 @@ from django_filters import Filter
from apps.accounts.models import Account from apps.accounts.models import Account
from apps.common.fields.month_year import MonthYearFormField from apps.common.fields.month_year import MonthYearFormField
from apps.common.widgets.datepicker import AirDatePickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelectMultiple from apps.common.widgets.tom_select import TomSelectMultiple
from apps.currencies.models import Currency from apps.currencies.models import Currency
@@ -87,13 +88,13 @@ class TransactionsFilter(django_filters.FilterSet):
date_start = django_filters.DateFilter( date_start = django_filters.DateFilter(
field_name="date", field_name="date",
lookup_expr="gte", lookup_expr="gte",
widget=forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"), widget=AirDatePickerInput(),
label=_("Date from"), label=_("Date from"),
) )
date_end = django_filters.DateFilter( date_end = django_filters.DateFilter(
field_name="date", field_name="date",
lookup_expr="lte", lookup_expr="lte",
widget=forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"), widget=AirDatePickerInput(),
label=_("Until"), label=_("Until"),
) )
reference_date_start = MonthYearFilter( reference_date_start = MonthYearFilter(

View File

@@ -16,10 +16,11 @@ from apps.common.fields.forms.dynamic_select import (
DynamicModelChoiceField, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, DynamicModelMultipleChoiceField,
) )
from apps.common.fields.month_year import MonthYearFormField
from apps.common.widgets.crispy.submit import NoClassSubmit from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDatePickerInput, AirMonthYearPickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect from apps.common.widgets.tom_select import TomSelect
from apps.rules.signals import transaction_created, transaction_updated
from apps.transactions.models import ( from apps.transactions.models import (
Transaction, Transaction,
TransactionCategory, TransactionCategory,
@@ -28,7 +29,6 @@ from apps.transactions.models import (
RecurringTransaction, RecurringTransaction,
TransactionEntity, TransactionEntity,
) )
from apps.rules.signals import transaction_created, transaction_updated
class TransactionForm(forms.ModelForm): class TransactionForm(forms.ModelForm):
@@ -59,7 +59,14 @@ class TransactionForm(forms.ModelForm):
label=_("Account"), label=_("Account"),
widget=TomSelect(clear_button=False, group_by="group"), widget=TomSelect(clear_button=False, group_by="group"),
) )
reference_date = MonthYearFormField(label=_("Reference Date"), required=False)
date = forms.DateField(
widget=AirDatePickerInput(clear_button=False), label=_("Date")
)
reference_date = forms.DateField(
widget=AirMonthYearPickerInput(), label=_("Reference Date"), required=False
)
class Meta: class Meta:
model = Transaction model = Transaction
@@ -77,7 +84,6 @@ class TransactionForm(forms.ModelForm):
"entities", "entities",
] ]
widgets = { widgets = {
"date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
"notes": forms.Textarea(attrs={"rows": 3}), "notes": forms.Textarea(attrs={"rows": 3}),
"account": TomSelect(clear_button=False, group_by="group"), "account": TomSelect(clear_button=False, group_by="group"),
} }
@@ -118,8 +124,8 @@ class TransactionForm(forms.ModelForm):
css_class="form-row", css_class="form-row",
), ),
Row( Row(
Column("date", css_class="form-group col-md-6 mb-0"), Column(Field("date"), css_class="form-group col-md-6 mb-0"),
Column("reference_date", css_class="form-group col-md-6 mb-0"), Column(Field("reference_date"), css_class="form-group col-md-6 mb-0"),
css_class="form-row", css_class="form-row",
), ),
"description", "description",
@@ -235,10 +241,13 @@ class TransferForm(forms.Form):
) )
date = forms.DateField( date = forms.DateField(
label=_("Date"), widget=AirDatePickerInput(clear_button=False), label=_("Date")
widget=forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
) )
reference_date = MonthYearFormField(label=_("Reference Date"), required=False)
reference_date = forms.DateField(
widget=AirMonthYearPickerInput(), label=_("Reference Date"), required=False
)
description = forms.CharField(max_length=500, label=_("Description")) description = forms.CharField(max_length=500, label=_("Description"))
notes = forms.CharField( notes = forms.CharField(
required=False, required=False,
@@ -404,7 +413,10 @@ class InstallmentPlanForm(forms.ModelForm):
queryset=TransactionEntity.objects.filter(active=True), queryset=TransactionEntity.objects.filter(active=True),
) )
type = forms.ChoiceField(choices=Transaction.Type.choices) type = forms.ChoiceField(choices=Transaction.Type.choices)
reference_date = MonthYearFormField(label=_("Reference Date"), required=False)
reference_date = forms.DateField(
widget=AirMonthYearPickerInput(), label=_("Reference Date"), required=False
)
class Meta: class Meta:
model = InstallmentPlan model = InstallmentPlan
@@ -424,10 +436,10 @@ class InstallmentPlanForm(forms.ModelForm):
"entities", "entities",
] ]
widgets = { widgets = {
"start_date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
"account": TomSelect(), "account": TomSelect(),
"recurrence": TomSelect(clear_button=False), "recurrence": TomSelect(clear_button=False),
"notes": forms.Textarea(attrs={"rows": 3}), "notes": forms.Textarea(attrs={"rows": 3}),
"start_date": AirDatePickerInput(clear_button=False),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -646,7 +658,6 @@ class RecurringTransactionForm(forms.ModelForm):
queryset=TransactionEntity.objects.filter(active=True), queryset=TransactionEntity.objects.filter(active=True),
) )
type = forms.ChoiceField(choices=Transaction.Type.choices) type = forms.ChoiceField(choices=Transaction.Type.choices)
reference_date = MonthYearFormField(label=_("Reference Date"), required=False)
class Meta: class Meta:
model = RecurringTransaction model = RecurringTransaction
@@ -666,8 +677,9 @@ class RecurringTransactionForm(forms.ModelForm):
"entities", "entities",
] ]
widgets = { widgets = {
"start_date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"), "start_date": AirDatePickerInput(clear_button=False),
"end_date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"), "end_date": AirDatePickerInput(),
"reference_date": AirMonthYearPickerInput(),
"recurrence_type": TomSelect(clear_button=False), "recurrence_type": TomSelect(clear_button=False),
"notes": forms.Textarea( "notes": forms.Textarea(
attrs={ attrs={

View File

@@ -5,6 +5,7 @@
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label={% translate 'Close' %}></button> <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label={% translate 'Close' %}></button>
</div> </div>
<div id="generic-offcanvas-body" class="offcanvas-body" <div id="generic-offcanvas-body" class="offcanvas-body"
_="install init_tom_select"> _="install init_tom_select
install init_datepicker">
{% block body %}{% endblock %} {% block body %}{% endblock %}
</div> </div>

View File

@@ -3,19 +3,18 @@
{% javascript_pack 'bootstrap' attrs="defer" %} {% javascript_pack 'bootstrap' attrs="defer" %}
{% javascript_pack 'sweetalert2' attrs="defer" %} {% javascript_pack 'sweetalert2' attrs="defer" %}
{% javascript_pack 'select' attrs="defer" %} {% javascript_pack 'select' attrs="defer" %}
{% javascript_pack 'datepicker' %}
{% include 'includes/scripts/hyperscript/init_tom_select.html' %} {% include 'includes/scripts/hyperscript/init_tom_select.html' %}
{% include 'includes/scripts/hyperscript/init_date_picker.html' %}
{% include 'includes/scripts/hyperscript/hide_amount.html' %} {% include 'includes/scripts/hyperscript/hide_amount.html' %}
{% include 'includes/scripts/hyperscript/tooltip.html' %} {% include 'includes/scripts/hyperscript/tooltip.html' %}
{% include 'includes/scripts/hyperscript/htmx_error_handler.html' %} {% include 'includes/scripts/hyperscript/htmx_error_handler.html' %}
{% include 'includes/scripts/hyperscript/sounds.html' %} {% include 'includes/scripts/hyperscript/sounds.html' %}
{% include 'includes/scripts/hyperscript/swal.html' %} {% include 'includes/scripts/hyperscript/swal.html' %}
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script>
{% javascript_pack 'htmx' attrs="defer" %} {% javascript_pack 'htmx' attrs="defer" %}
{% javascript_pack 'charts' %} {% javascript_pack 'charts' %}
{#<script src="https://unpkg.com/htmx-ext-alpine-morph@2.0.0/alpine-morph.js"></script>#}
<script> <script>
let tz = Intl.DateTimeFormat().resolvedOptions().timeZone; let tz = Intl.DateTimeFormat().resolvedOptions().timeZone;

View File

@@ -0,0 +1,22 @@
<script type="text/hyperscript">
behavior init_datepicker
init
set datepickers to <.airdatepickerinput/> in me
for x in datepickers
js(it)
DatePicker(it)
end
end
set datepickers to <.airdatetimepickerinput/> in me
for x in datepickers
js(it)
DatePicker(it)
end
end
set datepickers to <.airmonthyearpickerinput/> in me
for x in datepickers
MonthYearPicker(it)
end
end
end
</script>

View File

@@ -8,7 +8,9 @@
{% block title %}{% translate 'Currency Converter' %}{% endblock %} {% block title %}{% translate 'Currency Converter' %}{% endblock %}
{% block content %} {% block content %}
<div class="container px-md-3 py-3 column-gap-5" _="install init_tom_select"> <div class="container px-md-3 py-3 column-gap-5"
_="install init_tom_select
install init_datepicker">
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3"> <div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
<div>{% translate 'Currency Converter' %}</div> <div>{% translate 'Currency Converter' %}</div>
</div> </div>

View File

@@ -124,7 +124,8 @@
<div class="collapse" id="collapse-filter"> <div class="collapse" id="collapse-filter">
<div class="card card-body"> <div class="card card-body">
<form _="on change or submit or search trigger updated on window end <form _="on change or submit or search trigger updated on window end
install init_tom_select" install init_tom_select
install init_datepicker"
id="filter"> id="filter">
{% crispy filter.form %} {% crispy filter.form %}
</form> </form>

View File

@@ -21,7 +21,8 @@
<hr> <hr>
<form hx-get="{% url 'transactions_all_list' %}" hx-trigger="change, submit, search" <form hx-get="{% url 'transactions_all_list' %}" hx-trigger="change, submit, search"
hx-target="#transactions" id="filter" hx-indicator="#transactions" hx-target="#transactions" id="filter" hx-indicator="#transactions"
_="install init_tom_select"> _="install init_tom_select
install init_datepicker">
{% crispy filter.form %} {% crispy filter.form %}
</form> </form>
</div> </div>

View File

@@ -17,6 +17,7 @@
"@babel/preset-env": "^7.16.8", "@babel/preset-env": "^7.16.8",
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"air-datepicker": "^3.5.3",
"alpinejs": "^3.14.1", "alpinejs": "^3.14.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"autosize": "^6.0.1", "autosize": "^6.0.1",
@@ -2703,6 +2704,12 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/air-datepicker": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/air-datepicker/-/air-datepicker-3.5.3.tgz",
"integrity": "sha512-Elf9gLhv/jidN1+TfeRJYMQRUfYx5apXw2dY5DuAMPRnNtQ4Iw9fTTJK772osmXSUB9xQ2Y8Q1Pt6pgBOQLPQw==",
"license": "MIT"
},
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",

View File

@@ -30,6 +30,7 @@
"@babel/preset-env": "^7.16.8", "@babel/preset-env": "^7.16.8",
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"air-datepicker": "^3.5.3",
"alpinejs": "^3.14.1", "alpinejs": "^3.14.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"autosize": "^6.0.1", "autosize": "^6.0.1",

View File

@@ -0,0 +1,148 @@
import AirDatepicker from 'air-datepicker';
import en from 'air-datepicker/locale/en';
import ptBr from 'air-datepicker/locale/pt-BR';
import {createPopper} from '@popperjs/core';
const locales = {
'pt': ptBr,
'en': en
};
function isMobileDevice() {
const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
return mobileRegex.test(navigator.userAgent);
}
function isTouchDevice() {
return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0);
}
function isMobile() {
return isMobileDevice() || isTouchDevice();
}
window.DatePicker = function createDynamicDatePicker(element) {
let isOnMobile = isMobile();
let baseOpts = {
isMobile: isOnMobile,
timepicker: element.dataset.timepicker === 'true',
autoClose: element.dataset.autoClose === 'true',
buttons: element.dataset.clearButton === 'true' ? ['clear', 'today'] : ['today'],
locale: locales[element.dataset.language],
onSelect: ({date, formattedDate, datepicker}) => {
const _event = new CustomEvent("change", {
bubbles: true,
});
datepicker.$el.dispatchEvent(_event);
}
};
const positionConfig = !isOnMobile ? {
position({$datepicker, $target, $pointer, done}) {
let popper = createPopper($target, $datepicker, {
placement: 'bottom',
modifiers: [
{
name: 'flip',
options: {
padding: {
top: 64
}
}
},
{
name: 'offset',
options: {
offset: [0, 20]
}
},
{
name: 'arrow',
options: {
element: $pointer
}
}
]
});
return function completeHide() {
popper.destroy();
done();
};
}
} : {};
let opts = {...baseOpts, ...positionConfig};
if (element.dataset.value) {
opts["selectedDates"] = [element.dataset.value];
opts["startDate"] = [element.dataset.value];
}
return new AirDatepicker(element, opts);
};
window.MonthYearPicker = function createDynamicDatePicker(element) {
let isOnMobile = isMobile();
let baseOpts = {
isMobile: isOnMobile,
view: 'months',
minView: 'months',
dateFormat: 'MMMM yyyy',
autoClose: element.dataset.autoClose === 'true',
buttons: element.dataset.clearButton === 'true' ? ['clear', 'today'] : ['today'],
locale: locales[element.dataset.language],
onSelect: ({date, formattedDate, datepicker}) => {
const _event = new CustomEvent("change", {
bubbles: true,
});
datepicker.$el.dispatchEvent(_event);
}
};
const positionConfig = !isOnMobile ? {
position({$datepicker, $target, $pointer, done}) {
let popper = createPopper($target, $datepicker, {
placement: 'bottom',
modifiers: [
{
name: 'flip',
options: {
padding: {
top: 64
}
}
},
{
name: 'offset',
options: {
offset: [0, 20]
}
},
{
name: 'arrow',
options: {
element: $pointer
}
}
]
});
return function completeHide() {
popper.destroy();
done();
};
}
} : {};
let opts = {...baseOpts, ...positionConfig};
if (element.dataset.value) {
opts["selectedDates"] = [element.dataset.value];
opts["startDate"] = [element.dataset.value];
}
return new AirDatepicker(element, opts);
};

View File

@@ -0,0 +1,89 @@
@import 'air-datepicker/air-datepicker.css';
.air-datepicker-global-container {
z-index: 2000; // Allows the datepicker to be shown on top of offcanvas
}
.air-datepicker {
--adp-accent-color: #fbb700;
--adp-day-name-color: #fbb700;
--adp-background-color: #303030; /* $gray-800 */
--adp-color: #fff;
--adb-color-other-month: #888; /* $gray-600 */
--adp-cell-background-color-selected: #fbb700;
--adp-border-color-inline: #444;
--adp-background-color-selected-other-month-focused: #e6a600; /* Slightly darker than $yellow */
--adp-background-color-selected-other-month: #fbb700;
--adp-color-secondary: #adb5bd; /* $gray-500 */
--adp-background-color-hover: #444;
--adp-background-color-active: #3c3c3c;
--adp-cell-background-color-selected-hover: #e6a600;
--adp-color-other-month: #888; /* $gray-600 */
--adp-color-disabled: #444; /* $gray-700 */
--adp-color-disabled-in-range: #666; /* Between $gray-600 and $gray-700 */
--adp-color-other-month-hover: #ced4da; /* $gray-400 */
--adp-time-track-color: #444; /* $gray-700 */
--adp-time-track-color-hover: #888; /* $gray-600 */
}
.air-datepicker-cell.-selected-,
.air-datepicker-cell.-selected-.-current-,
.-selected-.air-datepicker-cell.-year-.-other-decade-,
.-selected-.air-datepicker-cell.-day-.-other-month-{
color: #222; /* $gray-900 */
}
/* Additional styles for better dark theme integration */
.air-datepicker {
border-color: #444; /* $gray-700 */
}
.air-datepicker-body--day-names {
color: #fbb700; /* $yellow */
}
.air-datepicker-cell:hover {
background-color: #444; /* $gray-700 */
}
.air-datepicker-cell.-current- {
color: #fbb700; /* $yellow */
}
.air-datepicker-cell.-range-from-,
.air-datepicker-cell.-range-to- {
border: 1px solid #fbb700; /* $yellow */
}
.air-datepicker-cell.-range-from-::before,
.air-datepicker-cell.-range-to-::before {
background-color: rgba(251, 183, 0, 0.1); /* $yellow with opacity */
}
.air-datepicker-cell.-in-range- {
background-color: rgba(251, 183, 0, 0.1); /* $yellow with opacity */
}
.air-datepicker-time--row input[type='range']::-webkit-slider-thumb {
background-color: #fbb700; /* $yellow */
}
.air-datepicker-time--row input[type='range']::-moz-range-thumb {
background-color: #fbb700; /* $yellow */
}
.air-datepicker-button,
.air-datepicker-button:hover {
color: #fbb700; /* $yellow */
}
.air-datepicker-button:hover {
background-color: #444; /* $gray-700 */
}
.air-datepicker--pointer:after {
background: #303030
}

View File

@@ -2,6 +2,7 @@
@import "font-awesome.scss"; @import "font-awesome.scss";
@import "tailwind.scss"; @import "tailwind.scss";
@import "bootstrap.scss"; @import "bootstrap.scss";
@import "datepicker.scss";
@import "tom-select.scss"; @import "tom-select.scss";
@import "animations.scss"; @import "animations.scss";
@import "scrollbar.scss"; @import "scrollbar.scss";