dbviewer 0.5.6 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/views/dbviewer/tables/index.html.erb +1 -7
- data/app/views/dbviewer/tables/show.html.erb +1003 -0
- data/app/views/layouts/dbviewer/application.html.erb +4 -0
- data/app/views/layouts/dbviewer/shared/_sidebar.html.erb +95 -113
- data/lib/dbviewer/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3385fea543f18e7af2bd9a924b5095f76235612d569fc1e1a828222b8b1a3b53
|
4
|
+
data.tar.gz: 762f1ee1b3774c9186cbdac735beb0717c6a3feb64ffecd7833ec8732ce91898
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1552e63407dd92eb3bfa821a4712f6cb57b5c68462c6028a9e3debe14f2e72ce073c631ec1616aa50a5158939ae667899ef4999c2ac5feff8a6f22edd783b08
|
7
|
+
data.tar.gz: 2852a40ddec1a2a86d2b9764bf527fd21dd140aa57a7ef36da6d7dcf312362c6b24b753c6badd04aee89328557136c954adbad95a442540a233f53affac5489c
|
@@ -40,13 +40,7 @@
|
|
40
40
|
<% @tables.each do |table| %>
|
41
41
|
<tr>
|
42
42
|
<td class="fw-medium">
|
43
|
-
|
44
|
-
# Include creation filter params in table links
|
45
|
-
filter_params = {}
|
46
|
-
filter_params[:creation_filter_start] = session[:creation_filter_start] if session[:creation_filter_start].present?
|
47
|
-
filter_params[:creation_filter_end] = session[:creation_filter_end] if session[:creation_filter_end].present?
|
48
|
-
%>
|
49
|
-
<%= link_to table[:name], table_path(table[:name], filter_params), class: "text-decoration-none" %>
|
43
|
+
<%= link_to table[:name], table_path(table[:name]), class: "text-decoration-none" %>
|
50
44
|
</td>
|
51
45
|
<td class="text-end">
|
52
46
|
<span class="badge bg-secondary-subtle"><%= table[:record_count] %></span>
|
@@ -1525,3 +1525,1006 @@
|
|
1525
1525
|
return section;
|
1526
1526
|
}
|
1527
1527
|
</script>
|
1528
|
+
|
1529
|
+
<!-- Floating Creation Filter - Only visible on desktop and on table details page -->
|
1530
|
+
<% if has_timestamp_column?(@table_name) %>
|
1531
|
+
<div class="floating-creation-filter d-none d-lg-block">
|
1532
|
+
<button class="btn btn-primary btn-lg shadow-lg floating-filter-btn"
|
1533
|
+
type="button"
|
1534
|
+
data-bs-toggle="offcanvas"
|
1535
|
+
data-bs-target="#creationFilterOffcanvas"
|
1536
|
+
aria-controls="creationFilterOffcanvas"
|
1537
|
+
title="Creation Date Filter">
|
1538
|
+
<i class="bi bi-calendar-range"></i>
|
1539
|
+
<% if @creation_filter_start.present? || @creation_filter_end.present? %>
|
1540
|
+
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-success">
|
1541
|
+
<i class="bi bi-check"></i>
|
1542
|
+
</span>
|
1543
|
+
<% end %>
|
1544
|
+
</button>
|
1545
|
+
</div>
|
1546
|
+
|
1547
|
+
<!-- Creation Filter Offcanvas -->
|
1548
|
+
<div class="offcanvas offcanvas-end" tabindex="-1" id="creationFilterOffcanvas" aria-labelledby="creationFilterOffcanvasLabel">
|
1549
|
+
<div class="offcanvas-header">
|
1550
|
+
<h5 class="offcanvas-title" id="creationFilterOffcanvasLabel">
|
1551
|
+
<i class="bi bi-calendar-range me-2"></i>Creation Date Filter
|
1552
|
+
</h5>
|
1553
|
+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
1554
|
+
</div>
|
1555
|
+
<div class="offcanvas-body">
|
1556
|
+
<form id="floatingCreationFilterForm" action="<%= request.path %>" method="get" class="mb-0">
|
1557
|
+
<!-- Preserve existing query parameters -->
|
1558
|
+
<input type="hidden" name="page" value="<%= @current_page %>">
|
1559
|
+
<input type="hidden" name="per_page" value="<%= @per_page %>">
|
1560
|
+
<input type="hidden" name="order_by" value="<%= @order_by %>">
|
1561
|
+
<input type="hidden" name="order_direction" value="<%= @order_direction %>">
|
1562
|
+
|
1563
|
+
<!-- Preserve column filters -->
|
1564
|
+
<% @column_filters.each do |key, value| %>
|
1565
|
+
<% unless key.to_s.start_with?('created_at') %>
|
1566
|
+
<input type="hidden" name="column_filters[<%= key %>]" value="<%= value %>">
|
1567
|
+
<% end %>
|
1568
|
+
<% end %>
|
1569
|
+
|
1570
|
+
<div class="mb-3">
|
1571
|
+
<p class="text-muted small">
|
1572
|
+
<i class="bi bi-info-circle me-1"></i>
|
1573
|
+
Filter records by their creation date and time. This applies to the <code>created_at</code> column.
|
1574
|
+
</p>
|
1575
|
+
</div>
|
1576
|
+
|
1577
|
+
<!-- Date range picker -->
|
1578
|
+
<div class="mb-3">
|
1579
|
+
<label for="floatingCreationFilterRange" class="form-label">Date Range</label>
|
1580
|
+
<input type="text"
|
1581
|
+
id="floatingCreationFilterRange"
|
1582
|
+
name="creation_filter_range"
|
1583
|
+
class="form-control"
|
1584
|
+
placeholder="Select date range..."
|
1585
|
+
readonly>
|
1586
|
+
<!-- Hidden inputs for form submission -->
|
1587
|
+
<input type="hidden" id="creation_filter_start" name="creation_filter_start" value="<%= @creation_filter_start %>">
|
1588
|
+
<input type="hidden" id="creation_filter_end" name="creation_filter_end" value="<%= @creation_filter_end %>">
|
1589
|
+
</div>
|
1590
|
+
|
1591
|
+
<!-- Quick preset buttons -->
|
1592
|
+
<div class="mb-3">
|
1593
|
+
<label class="form-label">Quick Presets</label>
|
1594
|
+
<div class="d-grid gap-1">
|
1595
|
+
<button type="button" class="btn btn-outline-secondary btn-sm preset-btn" data-preset="lastminute">
|
1596
|
+
<i class="bi bi-clock me-1"></i>Last Minute
|
1597
|
+
</button>
|
1598
|
+
<button type="button" class="btn btn-outline-secondary btn-sm preset-btn" data-preset="last5minutes">
|
1599
|
+
<i class="bi bi-clock-history me-1"></i>Last 5 Minutes
|
1600
|
+
</button>
|
1601
|
+
<button type="button" class="btn btn-outline-secondary btn-sm preset-btn" data-preset="today">
|
1602
|
+
<i class="bi bi-calendar-day me-1"></i>Today
|
1603
|
+
</button>
|
1604
|
+
<button type="button" class="btn btn-outline-secondary btn-sm preset-btn" data-preset="yesterday">
|
1605
|
+
<i class="bi bi-calendar-minus me-1"></i>Yesterday
|
1606
|
+
</button>
|
1607
|
+
<button type="button" class="btn btn-outline-secondary btn-sm preset-btn" data-preset="last7days">
|
1608
|
+
<i class="bi bi-calendar-week me-1"></i>Last 7 Days
|
1609
|
+
</button>
|
1610
|
+
<button type="button" class="btn btn-outline-secondary btn-sm preset-btn" data-preset="last30days">
|
1611
|
+
<i class="bi bi-calendar-month me-1"></i>Last 30 Days
|
1612
|
+
</button>
|
1613
|
+
<button type="button" class="btn btn-outline-secondary btn-sm preset-btn" data-preset="thismonth">
|
1614
|
+
<i class="bi bi-calendar3 me-1"></i>This Month
|
1615
|
+
</button>
|
1616
|
+
</div>
|
1617
|
+
</div>
|
1618
|
+
|
1619
|
+
<div class="d-grid gap-2">
|
1620
|
+
<button type="submit" class="btn btn-primary">
|
1621
|
+
<i class="bi bi-funnel me-1"></i>Apply Filter
|
1622
|
+
</button>
|
1623
|
+
<% if @creation_filter_start.present? || @creation_filter_end.present? %>
|
1624
|
+
<%
|
1625
|
+
# Preserve other query params when clearing creation filter
|
1626
|
+
clear_params = {
|
1627
|
+
clear_creation_filter: true,
|
1628
|
+
page: @current_page,
|
1629
|
+
per_page: @per_page,
|
1630
|
+
order_by: @order_by,
|
1631
|
+
order_direction: @order_direction
|
1632
|
+
}
|
1633
|
+
# Add column filters except created_at ones
|
1634
|
+
@column_filters.each do |key, value|
|
1635
|
+
unless key.to_s.start_with?('created_at')
|
1636
|
+
clear_params["column_filters[#{key}]"] = value
|
1637
|
+
end
|
1638
|
+
end
|
1639
|
+
%>
|
1640
|
+
<a href="<%= request.path %>?<%= clear_params.to_query %>" class="btn btn-outline-secondary">
|
1641
|
+
<i class="bi bi-x-circle me-1"></i>Clear Filter
|
1642
|
+
</a>
|
1643
|
+
<% end %>
|
1644
|
+
</div>
|
1645
|
+
|
1646
|
+
<div class="mt-3">
|
1647
|
+
<% if @creation_filter_start.present? || @creation_filter_end.present? %>
|
1648
|
+
<div class="alert alert-success alert-sm">
|
1649
|
+
<i class="bi bi-check-circle-fill me-1"></i>
|
1650
|
+
<strong>Filter Active:</strong> Showing records
|
1651
|
+
<% if @creation_filter_start.present? && @creation_filter_end.present? %>
|
1652
|
+
between <%= DateTime.parse(@creation_filter_start).strftime("%b %d, %Y %I:%M %p") %>
|
1653
|
+
and <%= DateTime.parse(@creation_filter_end).strftime("%b %d, %Y %I:%M %p") %>
|
1654
|
+
<% elsif @creation_filter_start.present? %>
|
1655
|
+
from <%= DateTime.parse(@creation_filter_start).strftime("%b %d, %Y %I:%M %p") %> onwards
|
1656
|
+
<% elsif @creation_filter_end.present? %>
|
1657
|
+
up to <%= DateTime.parse(@creation_filter_end).strftime("%b %d, %Y %I:%M %p") %>
|
1658
|
+
<% end %>
|
1659
|
+
|
1660
|
+
<% if @current_page == 1 && @records && @records.rows && @records.rows.empty? %>
|
1661
|
+
<div class="mt-2 text-warning">
|
1662
|
+
<i class="bi bi-exclamation-triangle-fill me-1"></i>
|
1663
|
+
No records match the current filter criteria.
|
1664
|
+
</div>
|
1665
|
+
<% end %>
|
1666
|
+
</div>
|
1667
|
+
<% end %>
|
1668
|
+
</div>
|
1669
|
+
</form>
|
1670
|
+
</div>
|
1671
|
+
</div>
|
1672
|
+
|
1673
|
+
<style>
|
1674
|
+
/* Floating creation filter button */
|
1675
|
+
.floating-creation-filter {
|
1676
|
+
position: fixed;
|
1677
|
+
bottom: 30px;
|
1678
|
+
right: 30px;
|
1679
|
+
z-index: 1050;
|
1680
|
+
}
|
1681
|
+
|
1682
|
+
.floating-filter-btn {
|
1683
|
+
width: 60px;
|
1684
|
+
height: 60px;
|
1685
|
+
border-radius: 50%;
|
1686
|
+
display: flex;
|
1687
|
+
align-items: center;
|
1688
|
+
justify-content: center;
|
1689
|
+
font-size: 1.2rem;
|
1690
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
1691
|
+
border: none;
|
1692
|
+
position: relative;
|
1693
|
+
background: var(--bs-primary);
|
1694
|
+
color: white;
|
1695
|
+
box-shadow: 0 4px 12px rgba(var(--bs-primary-rgb), 0.4);
|
1696
|
+
}
|
1697
|
+
|
1698
|
+
.floating-filter-btn:hover {
|
1699
|
+
transform: translateY(-3px) scale(1.05);
|
1700
|
+
box-shadow: 0 8px 25px rgba(var(--bs-primary-rgb), 0.5) !important;
|
1701
|
+
background: var(--bs-primary) !important;
|
1702
|
+
color: white !important;
|
1703
|
+
}
|
1704
|
+
|
1705
|
+
.floating-filter-btn:active {
|
1706
|
+
transform: translateY(-1px) scale(1.02);
|
1707
|
+
transition: all 0.1s ease;
|
1708
|
+
}
|
1709
|
+
|
1710
|
+
.floating-filter-btn:focus {
|
1711
|
+
outline: 2px solid rgba(var(--bs-primary-rgb), 0.5);
|
1712
|
+
outline-offset: 2px;
|
1713
|
+
}
|
1714
|
+
|
1715
|
+
/* Badge for active filter indicator */
|
1716
|
+
.floating-filter-btn .badge {
|
1717
|
+
font-size: 0.6rem;
|
1718
|
+
width: 18px;
|
1719
|
+
height: 18px;
|
1720
|
+
display: flex;
|
1721
|
+
align-items: center;
|
1722
|
+
justify-content: center;
|
1723
|
+
background: var(--bs-success) !important;
|
1724
|
+
animation: pulse 2s infinite;
|
1725
|
+
}
|
1726
|
+
|
1727
|
+
@keyframes pulse {
|
1728
|
+
0% { transform: scale(1); }
|
1729
|
+
50% { transform: scale(1.1); }
|
1730
|
+
100% { transform: scale(1); }
|
1731
|
+
}
|
1732
|
+
|
1733
|
+
/* Better datetime input styling for the floating filter */
|
1734
|
+
#creationFilterOffcanvas .form-control {
|
1735
|
+
border-radius: 6px;
|
1736
|
+
border: 1px solid var(--bs-border-color);
|
1737
|
+
background-color: var(--bs-body-bg);
|
1738
|
+
color: var(--bs-body-color);
|
1739
|
+
transition: all 0.15s ease-in-out;
|
1740
|
+
}
|
1741
|
+
|
1742
|
+
#creationFilterOffcanvas .form-control:focus {
|
1743
|
+
border-color: var(--bs-primary);
|
1744
|
+
box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.25);
|
1745
|
+
background-color: var(--bs-body-bg);
|
1746
|
+
}
|
1747
|
+
|
1748
|
+
/* Offcanvas enhancements */
|
1749
|
+
#creationFilterOffcanvas {
|
1750
|
+
backdrop-filter: blur(10px);
|
1751
|
+
}
|
1752
|
+
|
1753
|
+
#creationFilterOffcanvas .offcanvas-header {
|
1754
|
+
background: var(--bs-body-bg);
|
1755
|
+
border-bottom: 1px solid var(--bs-border-color);
|
1756
|
+
padding: 1.25rem;
|
1757
|
+
}
|
1758
|
+
|
1759
|
+
#creationFilterOffcanvas .offcanvas-body {
|
1760
|
+
background: var(--bs-body-bg);
|
1761
|
+
padding: 1.25rem;
|
1762
|
+
}
|
1763
|
+
|
1764
|
+
#creationFilterOffcanvas .offcanvas-title {
|
1765
|
+
color: var(--bs-body-color);
|
1766
|
+
font-weight: 600;
|
1767
|
+
}
|
1768
|
+
|
1769
|
+
/* Dark mode specific enhancements */
|
1770
|
+
[data-bs-theme="dark"] .floating-filter-btn {
|
1771
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(var(--bs-primary-rgb), 0.2);
|
1772
|
+
}
|
1773
|
+
|
1774
|
+
[data-bs-theme="dark"] .floating-filter-btn:hover {
|
1775
|
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(var(--bs-primary-rgb), 0.3) !important;
|
1776
|
+
}
|
1777
|
+
|
1778
|
+
[data-bs-theme="dark"] #creationFilterOffcanvas .offcanvas-header {
|
1779
|
+
background: var(--bs-dark);
|
1780
|
+
border-bottom-color: var(--bs-border-color-translucent);
|
1781
|
+
}
|
1782
|
+
|
1783
|
+
[data-bs-theme="dark"] #creationFilterOffcanvas .offcanvas-body {
|
1784
|
+
background: var(--bs-dark);
|
1785
|
+
}
|
1786
|
+
|
1787
|
+
[data-bs-theme="dark"] #creationFilterOffcanvas .form-control {
|
1788
|
+
background-color: var(--bs-body-bg);
|
1789
|
+
border-color: var(--bs-border-color-translucent);
|
1790
|
+
}
|
1791
|
+
|
1792
|
+
[data-bs-theme="dark"] #creationFilterOffcanvas .form-control:focus {
|
1793
|
+
background-color: var(--bs-body-bg);
|
1794
|
+
border-color: var(--bs-primary);
|
1795
|
+
}
|
1796
|
+
|
1797
|
+
/* Date range picker styling */
|
1798
|
+
#floatingCreationFilterRange {
|
1799
|
+
cursor: pointer;
|
1800
|
+
background-color: var(--bs-body-bg);
|
1801
|
+
color: var(--bs-body-color);
|
1802
|
+
border: 1px solid var(--bs-border-color);
|
1803
|
+
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, background-color 0.15s ease-in-out;
|
1804
|
+
}
|
1805
|
+
|
1806
|
+
#floatingCreationFilterRange:focus {
|
1807
|
+
border-color: var(--bs-primary);
|
1808
|
+
box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.25);
|
1809
|
+
background-color: var(--bs-body-bg);
|
1810
|
+
}
|
1811
|
+
|
1812
|
+
#floatingCreationFilterRange::placeholder {
|
1813
|
+
color: var(--bs-secondary-color);
|
1814
|
+
opacity: 0.7;
|
1815
|
+
}
|
1816
|
+
|
1817
|
+
/* Enhanced dark mode support for input */
|
1818
|
+
[data-bs-theme="dark"] #floatingCreationFilterRange {
|
1819
|
+
background-color: var(--bs-body-bg);
|
1820
|
+
border-color: var(--bs-border-color-translucent);
|
1821
|
+
color: var(--bs-body-color);
|
1822
|
+
}
|
1823
|
+
|
1824
|
+
[data-bs-theme="dark"] #floatingCreationFilterRange:focus {
|
1825
|
+
background-color: var(--bs-body-bg);
|
1826
|
+
border-color: var(--bs-primary);
|
1827
|
+
box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.25);
|
1828
|
+
}
|
1829
|
+
|
1830
|
+
/* Preset buttons styling */
|
1831
|
+
.preset-btn {
|
1832
|
+
font-size: 0.8rem;
|
1833
|
+
padding: 0.35rem 0.75rem;
|
1834
|
+
text-align: left;
|
1835
|
+
justify-content: flex-start;
|
1836
|
+
border: 1px solid var(--bs-border-color);
|
1837
|
+
background-color: var(--bs-body-bg);
|
1838
|
+
color: var(--bs-body-color);
|
1839
|
+
transition: all 0.2s ease;
|
1840
|
+
position: relative;
|
1841
|
+
overflow: hidden;
|
1842
|
+
}
|
1843
|
+
|
1844
|
+
.preset-btn::before {
|
1845
|
+
content: '';
|
1846
|
+
position: absolute;
|
1847
|
+
top: 0;
|
1848
|
+
left: -100%;
|
1849
|
+
width: 100%;
|
1850
|
+
height: 100%;
|
1851
|
+
background: linear-gradient(90deg, transparent, rgba(var(--bs-primary-rgb), 0.1), transparent);
|
1852
|
+
transition: left 0.3s ease;
|
1853
|
+
}
|
1854
|
+
|
1855
|
+
.preset-btn:hover {
|
1856
|
+
background-color: var(--bs-primary);
|
1857
|
+
color: white;
|
1858
|
+
border-color: var(--bs-primary);
|
1859
|
+
transform: translateY(-1px);
|
1860
|
+
box-shadow: 0 2px 4px rgba(var(--bs-primary-rgb), 0.2);
|
1861
|
+
}
|
1862
|
+
|
1863
|
+
.preset-btn:hover::before {
|
1864
|
+
left: 100%;
|
1865
|
+
}
|
1866
|
+
|
1867
|
+
.preset-btn:active {
|
1868
|
+
transform: translateY(0);
|
1869
|
+
box-shadow: 0 1px 2px rgba(var(--bs-primary-rgb), 0.2);
|
1870
|
+
}
|
1871
|
+
|
1872
|
+
.preset-btn i {
|
1873
|
+
opacity: 0.8;
|
1874
|
+
transition: opacity 0.2s ease;
|
1875
|
+
}
|
1876
|
+
|
1877
|
+
.preset-btn:hover i {
|
1878
|
+
opacity: 1;
|
1879
|
+
}
|
1880
|
+
|
1881
|
+
/* Dark mode enhancements for preset buttons */
|
1882
|
+
[data-bs-theme="dark"] .preset-btn {
|
1883
|
+
background-color: var(--bs-dark);
|
1884
|
+
border-color: var(--bs-border-color-translucent);
|
1885
|
+
color: var(--bs-body-color);
|
1886
|
+
}
|
1887
|
+
|
1888
|
+
[data-bs-theme="dark"] .preset-btn:hover {
|
1889
|
+
background-color: var(--bs-primary);
|
1890
|
+
color: white;
|
1891
|
+
border-color: var(--bs-primary);
|
1892
|
+
box-shadow: 0 2px 8px rgba(var(--bs-primary-rgb), 0.3);
|
1893
|
+
}
|
1894
|
+
|
1895
|
+
/* Flatpickr theme adjustments */
|
1896
|
+
.flatpickr-calendar {
|
1897
|
+
border-radius: 8px;
|
1898
|
+
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.2);
|
1899
|
+
border: 1px solid var(--bs-border-color);
|
1900
|
+
background: var(--bs-body-bg);
|
1901
|
+
font-family: var(--bs-body-font-family);
|
1902
|
+
}
|
1903
|
+
|
1904
|
+
.flatpickr-months {
|
1905
|
+
background: var(--bs-body-bg);
|
1906
|
+
border-bottom: 1px solid var(--bs-border-color);
|
1907
|
+
border-radius: 8px 8px 0 0;
|
1908
|
+
}
|
1909
|
+
|
1910
|
+
.flatpickr-month {
|
1911
|
+
color: var(--bs-body-color);
|
1912
|
+
fill: var(--bs-body-color);
|
1913
|
+
}
|
1914
|
+
|
1915
|
+
.flatpickr-current-month {
|
1916
|
+
color: var(--bs-body-color);
|
1917
|
+
}
|
1918
|
+
|
1919
|
+
.flatpickr-current-month .flatpickr-monthDropdown-month {
|
1920
|
+
background: var(--bs-body-bg);
|
1921
|
+
color: var(--bs-body-color);
|
1922
|
+
}
|
1923
|
+
|
1924
|
+
.flatpickr-weekdays {
|
1925
|
+
background: var(--bs-body-bg);
|
1926
|
+
}
|
1927
|
+
|
1928
|
+
.flatpickr-weekday {
|
1929
|
+
color: var(--bs-secondary-color);
|
1930
|
+
font-weight: 600;
|
1931
|
+
font-size: 0.75rem;
|
1932
|
+
}
|
1933
|
+
|
1934
|
+
.flatpickr-day {
|
1935
|
+
color: var(--bs-body-color);
|
1936
|
+
border-radius: 4px;
|
1937
|
+
transition: all 0.2s ease;
|
1938
|
+
}
|
1939
|
+
|
1940
|
+
.flatpickr-day:hover {
|
1941
|
+
background: var(--bs-primary-bg-subtle);
|
1942
|
+
color: var(--bs-primary);
|
1943
|
+
border-color: var(--bs-primary-border-subtle);
|
1944
|
+
}
|
1945
|
+
|
1946
|
+
.flatpickr-day.selected {
|
1947
|
+
background: var(--bs-primary);
|
1948
|
+
color: white;
|
1949
|
+
border-color: var(--bs-primary);
|
1950
|
+
box-shadow: 0 2px 4px rgba(var(--bs-primary-rgb), 0.3);
|
1951
|
+
}
|
1952
|
+
|
1953
|
+
.flatpickr-day.selected:hover {
|
1954
|
+
background: var(--bs-primary);
|
1955
|
+
color: white;
|
1956
|
+
}
|
1957
|
+
|
1958
|
+
.flatpickr-day.inRange {
|
1959
|
+
background: var(--bs-primary-bg-subtle);
|
1960
|
+
color: var(--bs-primary);
|
1961
|
+
border-color: transparent;
|
1962
|
+
}
|
1963
|
+
|
1964
|
+
.flatpickr-day.startRange {
|
1965
|
+
background: var(--bs-primary);
|
1966
|
+
color: white;
|
1967
|
+
border-radius: 4px 0 0 4px;
|
1968
|
+
}
|
1969
|
+
|
1970
|
+
.flatpickr-day.endRange {
|
1971
|
+
background: var(--bs-primary);
|
1972
|
+
color: white;
|
1973
|
+
border-radius: 0 4px 4px 0;
|
1974
|
+
}
|
1975
|
+
|
1976
|
+
.flatpickr-day.startRange.endRange {
|
1977
|
+
border-radius: 4px;
|
1978
|
+
}
|
1979
|
+
|
1980
|
+
.flatpickr-day.today {
|
1981
|
+
border-color: var(--bs-primary);
|
1982
|
+
color: var(--bs-primary);
|
1983
|
+
font-weight: 600;
|
1984
|
+
}
|
1985
|
+
|
1986
|
+
.flatpickr-day.today:hover {
|
1987
|
+
background: var(--bs-primary);
|
1988
|
+
color: white;
|
1989
|
+
}
|
1990
|
+
|
1991
|
+
.flatpickr-day.disabled {
|
1992
|
+
color: var(--bs-secondary-color);
|
1993
|
+
opacity: 0.5;
|
1994
|
+
}
|
1995
|
+
|
1996
|
+
.flatpickr-time {
|
1997
|
+
background: var(--bs-body-bg);
|
1998
|
+
border-radius: 0 0 8px 8px;
|
1999
|
+
}
|
2000
|
+
|
2001
|
+
.flatpickr-time input {
|
2002
|
+
background: var(--bs-body-bg);
|
2003
|
+
color: var(--bs-body-color);
|
2004
|
+
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
2005
|
+
}
|
2006
|
+
|
2007
|
+
.flatpickr-time input:focus {
|
2008
|
+
border-color: var(--bs-primary);
|
2009
|
+
box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.25);
|
2010
|
+
outline: 0;
|
2011
|
+
}
|
2012
|
+
|
2013
|
+
.flatpickr-time .flatpickr-time-separator {
|
2014
|
+
color: var(--bs-body-color);
|
2015
|
+
}
|
2016
|
+
|
2017
|
+
.flatpickr-prev-month,
|
2018
|
+
.flatpickr-next-month {
|
2019
|
+
color: var(--bs-body-color);
|
2020
|
+
fill: var(--bs-body-color);
|
2021
|
+
transition: color 0.2s ease;
|
2022
|
+
}
|
2023
|
+
|
2024
|
+
.flatpickr-prev-month:hover,
|
2025
|
+
.flatpickr-next-month:hover {
|
2026
|
+
color: var(--bs-primary);
|
2027
|
+
fill: var(--bs-primary);
|
2028
|
+
}
|
2029
|
+
|
2030
|
+
/* Dark mode specific enhancements */
|
2031
|
+
[data-bs-theme="dark"] .flatpickr-calendar {
|
2032
|
+
background: var(--bs-dark);
|
2033
|
+
border-color: var(--bs-border-color-translucent);
|
2034
|
+
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.4);
|
2035
|
+
}
|
2036
|
+
|
2037
|
+
[data-bs-theme="dark"] .flatpickr-months {
|
2038
|
+
background: var(--bs-dark);
|
2039
|
+
border-bottom-color: var(--bs-border-color-translucent);
|
2040
|
+
}
|
2041
|
+
|
2042
|
+
[data-bs-theme="dark"] .flatpickr-weekdays {
|
2043
|
+
background: var(--bs-dark);
|
2044
|
+
}
|
2045
|
+
|
2046
|
+
/* Enhanced dark mode day styling for better contrast */
|
2047
|
+
[data-bs-theme="dark"] .flatpickr-day {
|
2048
|
+
color: #e9ecef !important;
|
2049
|
+
background: transparent;
|
2050
|
+
border: 1px solid transparent;
|
2051
|
+
}
|
2052
|
+
|
2053
|
+
[data-bs-theme="dark"] .flatpickr-day:hover {
|
2054
|
+
background: rgba(var(--bs-primary-rgb), 0.25) !important;
|
2055
|
+
color: #ffffff !important;
|
2056
|
+
border-color: rgba(var(--bs-primary-rgb), 0.4);
|
2057
|
+
}
|
2058
|
+
|
2059
|
+
[data-bs-theme="dark"] .flatpickr-day.inRange {
|
2060
|
+
background: rgba(var(--bs-primary-rgb), 0.2) !important;
|
2061
|
+
color: #ffffff !important;
|
2062
|
+
border-color: transparent;
|
2063
|
+
}
|
2064
|
+
|
2065
|
+
[data-bs-theme="dark"] .flatpickr-day.selected {
|
2066
|
+
background: var(--bs-primary) !important;
|
2067
|
+
color: #ffffff !important;
|
2068
|
+
border-color: var(--bs-primary);
|
2069
|
+
box-shadow: 0 2px 6px rgba(var(--bs-primary-rgb), 0.4);
|
2070
|
+
}
|
2071
|
+
|
2072
|
+
[data-bs-theme="dark"] .flatpickr-day.selected:hover {
|
2073
|
+
background: var(--bs-primary) !important;
|
2074
|
+
color: #ffffff !important;
|
2075
|
+
}
|
2076
|
+
|
2077
|
+
[data-bs-theme="dark"] .flatpickr-day.startRange {
|
2078
|
+
background: var(--bs-primary) !important;
|
2079
|
+
color: #ffffff !important;
|
2080
|
+
border-radius: 4px 0 0 4px;
|
2081
|
+
}
|
2082
|
+
|
2083
|
+
[data-bs-theme="dark"] .flatpickr-day.endRange {
|
2084
|
+
background: var(--bs-primary) !important;
|
2085
|
+
color: #ffffff !important;
|
2086
|
+
border-radius: 0 4px 4px 0;
|
2087
|
+
}
|
2088
|
+
|
2089
|
+
[data-bs-theme="dark"] .flatpickr-day.startRange.endRange {
|
2090
|
+
border-radius: 4px;
|
2091
|
+
}
|
2092
|
+
|
2093
|
+
[data-bs-theme="dark"] .flatpickr-day.today {
|
2094
|
+
border-color: var(--bs-primary) !important;
|
2095
|
+
color: var(--bs-primary) !important;
|
2096
|
+
font-weight: 600;
|
2097
|
+
background: rgba(var(--bs-primary-rgb), 0.1);
|
2098
|
+
}
|
2099
|
+
|
2100
|
+
[data-bs-theme="dark"] .flatpickr-day.today:hover {
|
2101
|
+
background: var(--bs-primary) !important;
|
2102
|
+
color: #ffffff !important;
|
2103
|
+
}
|
2104
|
+
|
2105
|
+
[data-bs-theme="dark"] .flatpickr-day.disabled {
|
2106
|
+
color: #6c757d !important;
|
2107
|
+
opacity: 0.4;
|
2108
|
+
background: transparent !important;
|
2109
|
+
}
|
2110
|
+
|
2111
|
+
[data-bs-theme="dark"] .flatpickr-day.disabled:hover {
|
2112
|
+
background: transparent !important;
|
2113
|
+
color: #6c757d !important;
|
2114
|
+
cursor: not-allowed;
|
2115
|
+
}
|
2116
|
+
|
2117
|
+
/* Dark mode other day states */
|
2118
|
+
[data-bs-theme="dark"] .flatpickr-day.nextMonthDay,
|
2119
|
+
[data-bs-theme="dark"] .flatpickr-day.prevMonthDay {
|
2120
|
+
color: #6c757d !important;
|
2121
|
+
opacity: 0.6;
|
2122
|
+
}
|
2123
|
+
|
2124
|
+
[data-bs-theme="dark"] .flatpickr-day.nextMonthDay:hover,
|
2125
|
+
[data-bs-theme="dark"] .flatpickr-day.prevMonthDay:hover {
|
2126
|
+
background: rgba(var(--bs-primary-rgb), 0.15) !important;
|
2127
|
+
color: #adb5bd !important;
|
2128
|
+
}
|
2129
|
+
|
2130
|
+
[data-bs-theme="dark"] .flatpickr-time {
|
2131
|
+
background: var(--bs-dark);
|
2132
|
+
border-top-color: var(--bs-border-color-translucent);
|
2133
|
+
}
|
2134
|
+
|
2135
|
+
[data-bs-theme="dark"] .flatpickr-time input {
|
2136
|
+
background: var(--bs-body-bg);
|
2137
|
+
border-color: var(--bs-border-color-translucent);
|
2138
|
+
color: #e9ecef !important;
|
2139
|
+
}
|
2140
|
+
|
2141
|
+
.flatpickr-calendar.hasTime .flatpickr-time {
|
2142
|
+
border-top: var(--bs-border-color-translucent);
|
2143
|
+
}
|
2144
|
+
|
2145
|
+
.flatpickr-next-month {
|
2146
|
+
color: var(--bs-body-color);
|
2147
|
+
fill: var(--bs-body-color);
|
2148
|
+
}
|
2149
|
+
|
2150
|
+
span.flatpickr-weekday {
|
2151
|
+
color: var(--bs-secondary-color);
|
2152
|
+
font-weight: 600;
|
2153
|
+
font-size: 0.75rem;
|
2154
|
+
}
|
2155
|
+
|
2156
|
+
[data-bs-theme="dark"] .flatpickr-time input:focus {
|
2157
|
+
border-color: var(--bs-primary);
|
2158
|
+
background: var(--bs-body-bg);
|
2159
|
+
color: #ffffff !important;
|
2160
|
+
}
|
2161
|
+
|
2162
|
+
[data-bs-theme="dark"] .flatpickr-time .flatpickr-time-separator {
|
2163
|
+
color: #e9ecef !important;
|
2164
|
+
}
|
2165
|
+
|
2166
|
+
/* Better focus states */
|
2167
|
+
.flatpickr-day:focus {
|
2168
|
+
outline: 2px solid var(--bs-primary);
|
2169
|
+
outline-offset: -2px;
|
2170
|
+
z-index: 10;
|
2171
|
+
}
|
2172
|
+
|
2173
|
+
/* Animation for calendar appearance */
|
2174
|
+
.flatpickr-calendar.open {
|
2175
|
+
animation: flatpickr-slideDown 0.2s ease-out;
|
2176
|
+
}
|
2177
|
+
|
2178
|
+
@keyframes flatpickr-slideDown {
|
2179
|
+
from {
|
2180
|
+
opacity: 0;
|
2181
|
+
transform: translateY(-10px) scale(0.98);
|
2182
|
+
}
|
2183
|
+
to {
|
2184
|
+
opacity: 1;
|
2185
|
+
transform: translateY(0) scale(1);
|
2186
|
+
}
|
2187
|
+
}
|
2188
|
+
|
2189
|
+
/* Small alert styling */
|
2190
|
+
.alert-sm {
|
2191
|
+
padding: 0.5rem;
|
2192
|
+
font-size: 0.875rem;
|
2193
|
+
}
|
2194
|
+
|
2195
|
+
/* Responsive adjustments - hide on smaller screens */
|
2196
|
+
@media (max-width: 991.98px) {
|
2197
|
+
.floating-creation-filter {
|
2198
|
+
display: none !important;
|
2199
|
+
}
|
2200
|
+
}
|
2201
|
+
|
2202
|
+
/* Flatpickr positioning and responsive adjustments */
|
2203
|
+
.flatpickr-calendar.arrowTop:before,
|
2204
|
+
.flatpickr-calendar.arrowTop:after {
|
2205
|
+
border-bottom-color: var(--bs-border-color);
|
2206
|
+
}
|
2207
|
+
|
2208
|
+
.flatpickr-calendar.arrowBottom:before,
|
2209
|
+
.flatpickr-calendar.arrowBottom:after {
|
2210
|
+
border-top-color: var(--bs-border-color);
|
2211
|
+
}
|
2212
|
+
|
2213
|
+
[data-bs-theme="dark"] .flatpickr-calendar.arrowTop:before,
|
2214
|
+
[data-bs-theme="dark"] .flatpickr-calendar.arrowTop:after {
|
2215
|
+
border-bottom-color: var(--bs-border-color-translucent);
|
2216
|
+
}
|
2217
|
+
|
2218
|
+
[data-bs-theme="dark"] .flatpickr-calendar.arrowBottom:before,
|
2219
|
+
[data-bs-theme="dark"] .flatpickr-calendar.arrowBottom:after {
|
2220
|
+
border-top-color: var(--bs-border-color-translucent);
|
2221
|
+
}
|
2222
|
+
|
2223
|
+
/* Ensure calendar stays within viewport on mobile */
|
2224
|
+
@media (max-width: 576px) {
|
2225
|
+
.flatpickr-calendar {
|
2226
|
+
max-width: calc(100vw - 20px);
|
2227
|
+
font-size: 14px;
|
2228
|
+
}
|
2229
|
+
|
2230
|
+
.flatpickr-day {
|
2231
|
+
height: 35px;
|
2232
|
+
line-height: 35px;
|
2233
|
+
}
|
2234
|
+
|
2235
|
+
.flatpickr-time input {
|
2236
|
+
font-size: 14px;
|
2237
|
+
}
|
2238
|
+
}
|
2239
|
+
|
2240
|
+
/* Improved accessibility */
|
2241
|
+
.flatpickr-calendar {
|
2242
|
+
font-family: inherit;
|
2243
|
+
}
|
2244
|
+
|
2245
|
+
.flatpickr-day[aria-label] {
|
2246
|
+
position: relative;
|
2247
|
+
}
|
2248
|
+
|
2249
|
+
/* Smooth scroll behavior for time inputs */
|
2250
|
+
.flatpickr-time input {
|
2251
|
+
scroll-behavior: smooth;
|
2252
|
+
}
|
2253
|
+
|
2254
|
+
/* Enhanced visual feedback for interactive elements */
|
2255
|
+
.flatpickr-prev-month,
|
2256
|
+
.flatpickr-next-month {
|
2257
|
+
border-radius: 4px;
|
2258
|
+
padding: 4px;
|
2259
|
+
margin: 2px;
|
2260
|
+
}
|
2261
|
+
|
2262
|
+
.flatpickr-prev-month:hover,
|
2263
|
+
.flatpickr-next-month:hover {
|
2264
|
+
background: rgba(var(--bs-primary-rgb), 0.1);
|
2265
|
+
}
|
2266
|
+
</style>
|
2267
|
+
|
2268
|
+
<script>
|
2269
|
+
document.addEventListener('DOMContentLoaded', function() {
|
2270
|
+
// Initialize Flatpickr date range picker
|
2271
|
+
const dateRangeInput = document.getElementById('floatingCreationFilterRange');
|
2272
|
+
const startHidden = document.getElementById('creation_filter_start');
|
2273
|
+
const endHidden = document.getElementById('creation_filter_end');
|
2274
|
+
|
2275
|
+
if (dateRangeInput && typeof flatpickr !== 'undefined') {
|
2276
|
+
console.log('Flatpickr library loaded, initializing date range picker');
|
2277
|
+
// Store the Flatpickr instance in a variable accessible to all handlers
|
2278
|
+
let fp;
|
2279
|
+
|
2280
|
+
// Function to initialize Flatpickr
|
2281
|
+
function initializeFlatpickr(theme) {
|
2282
|
+
// Determine theme based on current document theme or passed parameter
|
2283
|
+
const currentTheme = theme || (document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'light');
|
2284
|
+
|
2285
|
+
const config = {
|
2286
|
+
mode: 'range',
|
2287
|
+
enableTime: true,
|
2288
|
+
dateFormat: 'Y-m-d H:i',
|
2289
|
+
time_24hr: true,
|
2290
|
+
allowInput: false,
|
2291
|
+
clickOpens: true,
|
2292
|
+
theme: currentTheme,
|
2293
|
+
animate: true,
|
2294
|
+
position: 'auto',
|
2295
|
+
static: false,
|
2296
|
+
appendTo: document.body, // Ensure it renders above other elements
|
2297
|
+
locale: {
|
2298
|
+
rangeSeparator: ' to ',
|
2299
|
+
weekdays: {
|
2300
|
+
shorthand: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
2301
|
+
longhand: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
2302
|
+
},
|
2303
|
+
months: {
|
2304
|
+
shorthand: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
2305
|
+
longhand: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
|
2306
|
+
}
|
2307
|
+
},
|
2308
|
+
onOpen: function(selectedDates, dateStr, instance) {
|
2309
|
+
// Add a slight delay to apply theme-specific styling after calendar opens
|
2310
|
+
setTimeout(() => {
|
2311
|
+
const calendar = instance.calendarContainer;
|
2312
|
+
if (calendar) {
|
2313
|
+
// Apply theme-specific class for additional styling control
|
2314
|
+
calendar.classList.add(`flatpickr-${currentTheme}`);
|
2315
|
+
|
2316
|
+
// Ensure proper z-index for offcanvas overlay
|
2317
|
+
calendar.style.zIndex = '1070';
|
2318
|
+
|
2319
|
+
// Add elegant entrance animation
|
2320
|
+
calendar.classList.add('open');
|
2321
|
+
}
|
2322
|
+
}, 10);
|
2323
|
+
},
|
2324
|
+
onClose: function(selectedDates, dateStr, instance) {
|
2325
|
+
const calendar = instance.calendarContainer;
|
2326
|
+
if (calendar) {
|
2327
|
+
calendar.classList.remove('open');
|
2328
|
+
}
|
2329
|
+
},
|
2330
|
+
onChange: function(selectedDates, dateStr, instance) {
|
2331
|
+
console.log('Date range changed:', selectedDates);
|
2332
|
+
|
2333
|
+
if (selectedDates.length === 2) {
|
2334
|
+
// Format dates for hidden inputs (Rails expects ISO format)
|
2335
|
+
startHidden.value = selectedDates[0].toISOString().slice(0, 16);
|
2336
|
+
endHidden.value = selectedDates[1].toISOString().slice(0, 16);
|
2337
|
+
|
2338
|
+
// Update display with elegant formatting
|
2339
|
+
const formatOptions = {
|
2340
|
+
year: 'numeric',
|
2341
|
+
month: 'short',
|
2342
|
+
day: 'numeric',
|
2343
|
+
hour: '2-digit',
|
2344
|
+
minute: '2-digit',
|
2345
|
+
hour12: false
|
2346
|
+
};
|
2347
|
+
|
2348
|
+
const startFormatted = selectedDates[0].toLocaleDateString('en-US', formatOptions);
|
2349
|
+
const endFormatted = selectedDates[1].toLocaleDateString('en-US', formatOptions);
|
2350
|
+
dateRangeInput.value = `${startFormatted} to ${endFormatted}`;
|
2351
|
+
|
2352
|
+
} else if (selectedDates.length === 1) {
|
2353
|
+
startHidden.value = selectedDates[0].toISOString().slice(0, 16);
|
2354
|
+
endHidden.value = '';
|
2355
|
+
|
2356
|
+
const formatOptions = {
|
2357
|
+
year: 'numeric',
|
2358
|
+
month: 'short',
|
2359
|
+
day: 'numeric',
|
2360
|
+
hour: '2-digit',
|
2361
|
+
minute: '2-digit',
|
2362
|
+
hour12: false
|
2363
|
+
};
|
2364
|
+
|
2365
|
+
const startFormatted = selectedDates[0].toLocaleDateString('en-US', formatOptions);
|
2366
|
+
dateRangeInput.value = `${startFormatted} (select end date)`;
|
2367
|
+
|
2368
|
+
} else {
|
2369
|
+
startHidden.value = '';
|
2370
|
+
endHidden.value = '';
|
2371
|
+
dateRangeInput.value = '';
|
2372
|
+
}
|
2373
|
+
}
|
2374
|
+
};
|
2375
|
+
|
2376
|
+
return flatpickr(dateRangeInput, config);
|
2377
|
+
}
|
2378
|
+
|
2379
|
+
// Initialize date range picker
|
2380
|
+
fp = initializeFlatpickr();
|
2381
|
+
|
2382
|
+
// Set initial values if they exist
|
2383
|
+
if (startHidden.value || endHidden.value) {
|
2384
|
+
const dates = [];
|
2385
|
+
if (startHidden.value) {
|
2386
|
+
dates.push(new Date(startHidden.value));
|
2387
|
+
}
|
2388
|
+
if (endHidden.value) {
|
2389
|
+
dates.push(new Date(endHidden.value));
|
2390
|
+
}
|
2391
|
+
fp.setDate(dates);
|
2392
|
+
}
|
2393
|
+
|
2394
|
+
// Preset button functionality
|
2395
|
+
const presetButtons = document.querySelectorAll('.preset-btn');
|
2396
|
+
presetButtons.forEach(button => {
|
2397
|
+
button.addEventListener('click', function(event) {
|
2398
|
+
event.preventDefault(); // Prevent any form submission
|
2399
|
+
|
2400
|
+
const preset = this.getAttribute('data-preset');
|
2401
|
+
const now = new Date();
|
2402
|
+
let startDate, endDate;
|
2403
|
+
|
2404
|
+
console.log('Preset button clicked:', preset); // Debug log
|
2405
|
+
|
2406
|
+
switch (preset) {
|
2407
|
+
case 'lastminute':
|
2408
|
+
startDate = new Date(now);
|
2409
|
+
startDate.setMinutes(startDate.getMinutes() - 1);
|
2410
|
+
endDate = new Date(now);
|
2411
|
+
break;
|
2412
|
+
case 'last5minutes':
|
2413
|
+
startDate = new Date(now);
|
2414
|
+
startDate.setMinutes(startDate.getMinutes() - 5);
|
2415
|
+
endDate = new Date(now);
|
2416
|
+
break;
|
2417
|
+
case 'today':
|
2418
|
+
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
|
2419
|
+
endDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
|
2420
|
+
break;
|
2421
|
+
case 'yesterday':
|
2422
|
+
const yesterday = new Date(now);
|
2423
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
2424
|
+
startDate = new Date(yesterday.getFullYear(), yesterday.getMonth(), yesterday.getDate(), 0, 0, 0);
|
2425
|
+
endDate = new Date(yesterday.getFullYear(), yesterday.getMonth(), yesterday.getDate(), 23, 59, 59);
|
2426
|
+
break;
|
2427
|
+
case 'last7days':
|
2428
|
+
startDate = new Date(now);
|
2429
|
+
startDate.setDate(startDate.getDate() - 7);
|
2430
|
+
startDate.setHours(0, 0, 0, 0);
|
2431
|
+
endDate = new Date(now);
|
2432
|
+
endDate.setHours(23, 59, 59, 999);
|
2433
|
+
break;
|
2434
|
+
case 'last30days':
|
2435
|
+
startDate = new Date(now);
|
2436
|
+
startDate.setDate(startDate.getDate() - 30);
|
2437
|
+
startDate.setHours(0, 0, 0, 0);
|
2438
|
+
endDate = new Date(now);
|
2439
|
+
endDate.setHours(23, 59, 59, 999);
|
2440
|
+
break;
|
2441
|
+
case 'thismonth':
|
2442
|
+
startDate = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0);
|
2443
|
+
endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
|
2444
|
+
break;
|
2445
|
+
}
|
2446
|
+
|
2447
|
+
if (startDate && endDate && fp) {
|
2448
|
+
console.log('Setting dates:', startDate, endDate); // Debug log
|
2449
|
+
fp.setDate([startDate, endDate]);
|
2450
|
+
|
2451
|
+
// Also update the hidden inputs directly as a fallback
|
2452
|
+
startHidden.value = startDate.toISOString().slice(0, 16);
|
2453
|
+
endHidden.value = endDate.toISOString().slice(0, 16);
|
2454
|
+
|
2455
|
+
// Update the display value
|
2456
|
+
const formattedStart = startDate.toLocaleDateString() + ' ' + startDate.toLocaleTimeString();
|
2457
|
+
const formattedEnd = endDate.toLocaleDateString() + ' ' + endDate.toLocaleTimeString();
|
2458
|
+
dateRangeInput.value = formattedStart + ' to ' + formattedEnd;
|
2459
|
+
} else {
|
2460
|
+
console.error('Failed to set dates - startDate:', startDate, 'endDate:', endDate, 'fp:', fp);
|
2461
|
+
}
|
2462
|
+
});
|
2463
|
+
});
|
2464
|
+
|
2465
|
+
// Listen for theme changes and update Flatpickr theme
|
2466
|
+
document.addEventListener('dbviewerThemeChanged', function(e) {
|
2467
|
+
const newTheme = e.detail.theme === 'dark' ? 'dark' : 'light';
|
2468
|
+
console.log('Theme changed to:', newTheme);
|
2469
|
+
|
2470
|
+
// Destroy and recreate with new theme
|
2471
|
+
if (fp) {
|
2472
|
+
const currentDates = fp.selectedDates;
|
2473
|
+
fp.destroy();
|
2474
|
+
fp = initializeFlatpickr(newTheme);
|
2475
|
+
|
2476
|
+
// Restore previous values if they existed
|
2477
|
+
if (currentDates && currentDates.length > 0) {
|
2478
|
+
fp.setDate(currentDates);
|
2479
|
+
}
|
2480
|
+
}
|
2481
|
+
});
|
2482
|
+
|
2483
|
+
// Also listen for direct data-bs-theme attribute changes using MutationObserver
|
2484
|
+
const themeObserver = new MutationObserver(function(mutations) {
|
2485
|
+
mutations.forEach(function(mutation) {
|
2486
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'data-bs-theme') {
|
2487
|
+
const newTheme = document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'light';
|
2488
|
+
console.log('Theme attribute changed to:', newTheme);
|
2489
|
+
|
2490
|
+
if (fp) {
|
2491
|
+
const currentDates = fp.selectedDates;
|
2492
|
+
fp.destroy();
|
2493
|
+
fp = initializeFlatpickr(newTheme);
|
2494
|
+
|
2495
|
+
// Restore previous values if they existed
|
2496
|
+
if (currentDates && currentDates.length > 0) {
|
2497
|
+
fp.setDate(currentDates);
|
2498
|
+
}
|
2499
|
+
}
|
2500
|
+
}
|
2501
|
+
});
|
2502
|
+
});
|
2503
|
+
|
2504
|
+
// Start observing theme changes
|
2505
|
+
themeObserver.observe(document.documentElement, {
|
2506
|
+
attributes: true,
|
2507
|
+
attributeFilter: ['data-bs-theme']
|
2508
|
+
});
|
2509
|
+
} else {
|
2510
|
+
console.error('Date range picker initialization failed:', {
|
2511
|
+
dateRangeInput: !!dateRangeInput,
|
2512
|
+
flatpickr: typeof flatpickr !== 'undefined'
|
2513
|
+
});
|
2514
|
+
}
|
2515
|
+
|
2516
|
+
// Close offcanvas after form submission
|
2517
|
+
const form = document.getElementById('floatingCreationFilterForm');
|
2518
|
+
if (form) {
|
2519
|
+
form.addEventListener('submit', function() {
|
2520
|
+
const offcanvas = bootstrap.Offcanvas.getInstance(document.getElementById('creationFilterOffcanvas'));
|
2521
|
+
if (offcanvas) {
|
2522
|
+
setTimeout(() => {
|
2523
|
+
offcanvas.hide();
|
2524
|
+
}, 100);
|
2525
|
+
}
|
2526
|
+
});
|
2527
|
+
}
|
2528
|
+
});
|
2529
|
+
</script>
|
2530
|
+
<% end %>
|
@@ -23,6 +23,8 @@
|
|
23
23
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
24
24
|
<!-- Bootstrap Icons -->
|
25
25
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
26
|
+
<!-- Flatpickr CSS for Date Range Picker -->
|
27
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
26
28
|
<!-- Google Fonts -->
|
27
29
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
|
28
30
|
<!-- Chart.js for Data Visualization -->
|
@@ -1407,6 +1409,8 @@
|
|
1407
1409
|
|
1408
1410
|
<!-- Bootstrap JS -->
|
1409
1411
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
1412
|
+
<!-- Flatpickr JS for Date Range Picker -->
|
1413
|
+
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
1410
1414
|
|
1411
1415
|
<!-- Dark Mode Toggle Script -->
|
1412
1416
|
<script>
|
@@ -4,95 +4,7 @@
|
|
4
4
|
id="tableSearch" placeholder="Filter tables..." aria-label="Filter tables">
|
5
5
|
</div>
|
6
6
|
|
7
|
-
|
8
|
-
<div class="accordion accordion-flush" id="creationFilterAccordion">
|
9
|
-
<div class="accordion-item border-0">
|
10
|
-
<h2 class="accordion-header" id="creationFilterHeading">
|
11
|
-
<button class="accordion-button p-2 collapsed" type="button" data-bs-toggle="collapse"
|
12
|
-
data-bs-target="#creationFilterCollapse" aria-expanded="false"
|
13
|
-
aria-controls="creationFilterCollapse">
|
14
|
-
<i class="bi bi-calendar-range me-2"></i> Creation Filter
|
15
|
-
<% if @creation_filter_start.present? || @creation_filter_end.present? %>
|
16
|
-
<% if @table_name.present? && has_timestamp_column?(@table_name) %>
|
17
|
-
<span class="badge bg-success ms-2">Active</span>
|
18
|
-
<% else %>
|
19
|
-
<span class="badge bg-secondary ms-2">Set</span>
|
20
|
-
<% end %>
|
21
|
-
<% end %>
|
22
|
-
</button>
|
23
|
-
</h2>
|
24
|
-
<div id="creationFilterCollapse" class="accordion-collapse collapse" aria-labelledby="creationFilterHeading">
|
25
|
-
<div class="accordion-body p-2">
|
26
|
-
<form id="creationFilterForm" action="<%= request.path %>" method="get" class="mb-0">
|
27
|
-
<!-- Preserve existing query parameters -->
|
28
|
-
<input type="hidden" name="page" value="<%= @current_page %>">
|
29
|
-
<input type="hidden" name="per_page" value="<%= @per_page %>">
|
30
|
-
<input type="hidden" name="order_by" value="<%= @order_by %>">
|
31
|
-
<input type="hidden" name="order_direction" value="<%= @order_direction %>">
|
32
|
-
|
33
|
-
<!-- Datetime range fields -->
|
34
|
-
<div class="mb-2">
|
35
|
-
<label for="creationFilterStart" class="form-label mb-1 small">Start Date/Time</label>
|
36
|
-
<input type="datetime-local" id="creationFilterStart" name="creation_filter_start"
|
37
|
-
class="form-control form-control-sm" value="<%= @creation_filter_start %>">
|
38
|
-
</div>
|
39
|
-
<div class="mb-2">
|
40
|
-
<label for="creationFilterEnd" class="form-label mb-1 small">End Date/Time</label>
|
41
|
-
<input type="datetime-local" id="creationFilterEnd" name="creation_filter_end"
|
42
|
-
class="form-control form-control-sm" value="<%= @creation_filter_end %>">
|
43
|
-
</div>
|
44
|
-
|
45
|
-
<div class="d-flex justify-content-between">
|
46
|
-
<button type="submit" class="btn btn-primary btn-sm">Apply</button>
|
47
|
-
<% if @creation_filter_start.present? || @creation_filter_end.present? %>
|
48
|
-
<%
|
49
|
-
# Preserve other query params when clearing creation filter
|
50
|
-
clear_params = {
|
51
|
-
clear_creation_filter: true,
|
52
|
-
page: @current_page,
|
53
|
-
per_page: @per_page,
|
54
|
-
order_by: @order_by,
|
55
|
-
order_direction: @order_direction
|
56
|
-
}
|
57
|
-
%>
|
58
|
-
<a href="<%= request.path %>?<%= clear_params.to_query %>" class="btn btn-outline-secondary btn-sm">Clear</a>
|
59
|
-
<% end %>
|
60
|
-
</div>
|
61
|
-
<div class="mt-2 small">
|
62
|
-
<% if @table_name.present? && has_timestamp_column?(@table_name) && (@creation_filter_start.present? || @creation_filter_end.present?) %>
|
63
|
-
<div class="text-success">
|
64
|
-
<i class="bi bi-check-circle-fill"></i>
|
65
|
-
Filter active on this table.
|
66
|
-
<% if @current_page == 1 && @records && @records.rows && @records.rows.empty? %>
|
67
|
-
<div class="alert alert-warning p-1 mt-2 small">
|
68
|
-
<i class="bi bi-exclamation-triangle-fill"></i>
|
69
|
-
No records match the filter criteria.
|
70
|
-
</div>
|
71
|
-
<% end %>
|
72
|
-
</div>
|
73
|
-
<% elsif @table_name.present? && (@creation_filter_start.present? || @creation_filter_end.present?) %>
|
74
|
-
<div class="text-warning">
|
75
|
-
<i class="bi bi-exclamation-circle-fill"></i>
|
76
|
-
This table has no <code>created_at</code> column.
|
77
|
-
</div>
|
78
|
-
<% elsif !@table_name.present? && (@creation_filter_start.present? || @creation_filter_end.present?) %>
|
79
|
-
<div class="text-info">
|
80
|
-
<i class="bi bi-info-circle-fill"></i>
|
81
|
-
Filter will be applied on tables with <code>created_at</code> column.
|
82
|
-
</div>
|
83
|
-
<% else %>
|
84
|
-
<div class="text-muted">
|
85
|
-
<i class="bi bi-info-circle-fill"></i>
|
86
|
-
Filters apply to tables with a <code>created_at</code> column.
|
87
|
-
</div>
|
88
|
-
<% end %>
|
89
|
-
</div>
|
90
|
-
</form>
|
91
|
-
</div>
|
92
|
-
</div>
|
93
|
-
</div>
|
94
|
-
</div>
|
95
|
-
</div>
|
7
|
+
|
96
8
|
|
97
9
|
<!-- Add custom styling for datetime inputs -->
|
98
10
|
<style>
|
@@ -207,6 +119,13 @@
|
|
207
119
|
<script>
|
208
120
|
document.addEventListener('DOMContentLoaded', function() {
|
209
121
|
const searchInput = document.getElementById('tableSearch');
|
122
|
+
const sidebarContent = document.querySelector('.dbviewer-sidebar-content');
|
123
|
+
|
124
|
+
// Storage keys for persistence
|
125
|
+
const STORAGE_KEYS = {
|
126
|
+
searchFilter: 'dbviewer_sidebar_search_filter',
|
127
|
+
scrollPosition: 'dbviewer_sidebar_scroll_position'
|
128
|
+
};
|
210
129
|
|
211
130
|
if (searchInput) {
|
212
131
|
// Debounce function to limit how often the filter runs
|
@@ -228,6 +147,9 @@
|
|
228
147
|
const tableItems = document.querySelectorAll('#tablesList .list-group-item-action');
|
229
148
|
let visibleCount = 0;
|
230
149
|
|
150
|
+
// Save the current search filter to localStorage
|
151
|
+
localStorage.setItem(STORAGE_KEYS.searchFilter, searchInput.value);
|
152
|
+
|
231
153
|
tableItems.forEach(function(item) {
|
232
154
|
// Get the table name from the title attribute for more accurate matching
|
233
155
|
const tableName = (item.getAttribute('title') || item.textContent).trim().toLowerCase();
|
@@ -266,26 +188,7 @@
|
|
266
188
|
}
|
267
189
|
}, 150); // Debounce for 150ms
|
268
190
|
|
269
|
-
// Set up
|
270
|
-
searchInput.addEventListener('input', filterTables);
|
271
|
-
searchInput.addEventListener('keyup', function(e) {
|
272
|
-
filterTables();
|
273
|
-
|
274
|
-
// Add keyboard navigation for the filtered list
|
275
|
-
if (e.key === 'Enter' || e.key === 'ArrowDown') {
|
276
|
-
e.preventDefault();
|
277
|
-
// Focus the first visible table item (not having d-none class)
|
278
|
-
const firstVisibleItem = document.querySelector('#tablesList .list-group-item-action:not(.d-none)');
|
279
|
-
if (firstVisibleItem) {
|
280
|
-
firstVisibleItem.focus();
|
281
|
-
// Make sure the item is visible in the scrollable area
|
282
|
-
firstVisibleItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
283
|
-
}
|
284
|
-
}
|
285
|
-
});
|
286
|
-
searchInput.addEventListener('search', filterTables); // For clearing via the "x" in some browsers
|
287
|
-
|
288
|
-
// Clear the search box when clicking the X button
|
191
|
+
// Set up clear button first
|
289
192
|
const clearButton = document.createElement('button');
|
290
193
|
clearButton.type = 'button';
|
291
194
|
clearButton.className = 'btn btn-sm btn-link position-absolute';
|
@@ -301,6 +204,8 @@
|
|
301
204
|
clearButton.innerHTML = '<i class="bi bi-x-circle"></i>';
|
302
205
|
clearButton.addEventListener('click', function() {
|
303
206
|
searchInput.value = '';
|
207
|
+
// Clear the saved filter from localStorage
|
208
|
+
localStorage.removeItem(STORAGE_KEYS.searchFilter);
|
304
209
|
// Call filter directly without debouncing for immediate feedback
|
305
210
|
filterTables();
|
306
211
|
this.style.display = 'none';
|
@@ -314,13 +219,90 @@
|
|
314
219
|
searchInput.addEventListener('input', function() {
|
315
220
|
clearButton.style.display = this.value ? 'block' : 'none';
|
316
221
|
});
|
222
|
+
}
|
223
|
+
|
224
|
+
// Restore saved search filter on page load and apply it immediately
|
225
|
+
const savedFilter = localStorage.getItem(STORAGE_KEYS.searchFilter);
|
226
|
+
if (savedFilter) {
|
227
|
+
searchInput.value = savedFilter;
|
228
|
+
// Show clear button immediately when filter is restored
|
229
|
+
clearButton.style.display = 'block';
|
230
|
+
// Apply filter immediately without debouncing to prevent blinking
|
231
|
+
const query = savedFilter.toLowerCase();
|
232
|
+
const tableItems = document.querySelectorAll('#tablesList .list-group-item-action');
|
233
|
+
let visibleCount = 0;
|
317
234
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
235
|
+
tableItems.forEach(function(item) {
|
236
|
+
const tableName = (item.getAttribute('title') || item.textContent).trim().toLowerCase();
|
237
|
+
const displayedText = item.textContent.trim().toLowerCase();
|
238
|
+
|
239
|
+
if (tableName.includes(query) || displayedText.includes(query)) {
|
240
|
+
item.classList.remove('d-none');
|
241
|
+
visibleCount++;
|
242
|
+
} else {
|
243
|
+
item.classList.add('d-none');
|
244
|
+
}
|
245
|
+
});
|
246
|
+
|
247
|
+
// Update the tables count immediately
|
248
|
+
const tableCountElement = document.getElementById('table-count');
|
249
|
+
if (tableCountElement) {
|
250
|
+
tableCountElement.textContent = visibleCount;
|
251
|
+
}
|
252
|
+
|
253
|
+
// Handle no results message immediately
|
254
|
+
let noResultsEl = document.getElementById('dbviewer-no-filter-results');
|
255
|
+
if (visibleCount === 0 && query !== '') {
|
256
|
+
if (!noResultsEl) {
|
257
|
+
noResultsEl = document.createElement('div');
|
258
|
+
noResultsEl.id = 'dbviewer-no-filter-results';
|
259
|
+
noResultsEl.className = 'list-group-item text-muted text-center py-3';
|
260
|
+
noResultsEl.innerHTML = '<i class="bi bi-search me-1"></i> No tables match "<span class="fw-bold"></span>"';
|
261
|
+
document.getElementById('tablesList').appendChild(noResultsEl);
|
262
|
+
}
|
263
|
+
noResultsEl.querySelector('.fw-bold').textContent = query;
|
264
|
+
noResultsEl.style.display = 'block';
|
265
|
+
} else if (noResultsEl) {
|
266
|
+
noResultsEl.style.display = 'none';
|
322
267
|
}
|
323
268
|
}
|
269
|
+
|
270
|
+
// Restore saved scroll position on page load
|
271
|
+
if (sidebarContent) {
|
272
|
+
const savedScrollPosition = localStorage.getItem(STORAGE_KEYS.scrollPosition);
|
273
|
+
if (savedScrollPosition) {
|
274
|
+
// Use requestAnimationFrame to ensure DOM is fully rendered
|
275
|
+
requestAnimationFrame(() => {
|
276
|
+
sidebarContent.scrollTop = parseInt(savedScrollPosition, 10);
|
277
|
+
});
|
278
|
+
}
|
279
|
+
|
280
|
+
// Save scroll position on scroll
|
281
|
+
const saveScrollPosition = debounce(function() {
|
282
|
+
localStorage.setItem(STORAGE_KEYS.scrollPosition, sidebarContent.scrollTop);
|
283
|
+
}, 100);
|
284
|
+
|
285
|
+
sidebarContent.addEventListener('scroll', saveScrollPosition);
|
286
|
+
}
|
287
|
+
|
288
|
+
// Set up event listeners for the search input
|
289
|
+
searchInput.addEventListener('input', filterTables);
|
290
|
+
searchInput.addEventListener('keyup', function(e) {
|
291
|
+
filterTables();
|
292
|
+
|
293
|
+
// Add keyboard navigation for the filtered list
|
294
|
+
if (e.key === 'Enter' || e.key === 'ArrowDown') {
|
295
|
+
e.preventDefault();
|
296
|
+
// Focus the first visible table item (not having d-none class)
|
297
|
+
const firstVisibleItem = document.querySelector('#tablesList .list-group-item-action:not(.d-none)');
|
298
|
+
if (firstVisibleItem) {
|
299
|
+
firstVisibleItem.focus();
|
300
|
+
// Make sure the item is visible in the scrollable area
|
301
|
+
firstVisibleItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
302
|
+
}
|
303
|
+
}
|
304
|
+
});
|
305
|
+
searchInput.addEventListener('search', filterTables); // For clearing via the "x" in some browsers
|
324
306
|
}
|
325
307
|
});
|
326
308
|
</script>
|
data/lib/dbviewer/version.rb
CHANGED