feat: replace the food table by a better ui
This commit is contained in:
+4
-27
@@ -183,33 +183,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="food-table-section">
|
<div class="section-title">Food Log</div>
|
||||||
<div class="table-title">Food Log</div>
|
|
||||||
<div class="table-wrapper">
|
<div class="food-cards-container" id="foodCardsContainer">
|
||||||
<table class="food-table">
|
<!-- Food cards will be added here -->
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="hidden-column">ID</th>
|
|
||||||
<th>Time</th>
|
|
||||||
<th>Food</th>
|
|
||||||
<th>Grams</th>
|
|
||||||
<th>Calories</th>
|
|
||||||
<th>Protein (g)</th>
|
|
||||||
<th>Fat (g)</th>
|
|
||||||
<th>Carbs (g)</th>
|
|
||||||
<th>Fiber (g)</th>
|
|
||||||
<th>Action</th>
|
|
||||||
<th class="hidden-column">Alkaline</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="foodTableBody">
|
|
||||||
<!-- Food entries will be added here -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="food-cards-container" id="foodCardsContainer">
|
|
||||||
<!-- Food cards will be added here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="calendar-section">
|
<div class="calendar-section">
|
||||||
|
|||||||
Generated
+8
-8
@@ -9,12 +9,12 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"appwrite": "^18.2.0",
|
"appwrite": "^19.0.0",
|
||||||
"sweetalert": "^2.1.2"
|
"sweetalert": "^2.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^7.1.3"
|
"vite": "^7.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
@@ -747,9 +747,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/appwrite": {
|
"node_modules/appwrite": {
|
||||||
"version": "18.2.0",
|
"version": "19.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/appwrite/-/appwrite-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/appwrite/-/appwrite-19.0.0.tgz",
|
||||||
"integrity": "sha512-g7pQpsxqR7+amEIaQLXMN4XzdQKenTHnGdA4s7UUJdZufhlHdJby8895h8z893+S0XipeHZhi0wpxYA2An95Rg==",
|
"integrity": "sha512-guamzST6Fvn/lvuBMLJGzJGL+N+o313j53qyfKM1ZHfcDd1ADnSONgm9DZihRFYBaWtU7s1bwkphkBJWSUwurQ==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/es6-object-assign": {
|
"node_modules/es6-object-assign": {
|
||||||
@@ -999,9 +999,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.4.tgz",
|
||||||
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
|
"integrity": "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+2
-2
@@ -13,10 +13,10 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^7.1.3"
|
"vite": "^7.1.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"appwrite": "^18.2.0",
|
"appwrite": "^19.0.0",
|
||||||
"sweetalert": "^2.1.2"
|
"sweetalert": "^2.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+106
-59
@@ -3,6 +3,7 @@ import { getButtonById, getButtonListByClassName, getDivById, getInputById, show
|
|||||||
import { FoodItem, FoodStorage } from './types.js';
|
import { FoodItem, FoodStorage } from './types.js';
|
||||||
import { AppwriteAuth, AppwriteDB } from './appwrite.js';
|
import { AppwriteAuth, AppwriteDB } from './appwrite.js';
|
||||||
import swal from 'sweetalert';
|
import swal from 'sweetalert';
|
||||||
|
import { Models } from 'appwrite';
|
||||||
|
|
||||||
// App state
|
// App state
|
||||||
let selectedDate = new Date();
|
let selectedDate = new Date();
|
||||||
@@ -11,6 +12,9 @@ let searchTimeout: number | null = null;
|
|||||||
let selectedFood: FoodItem | null = null; // review here
|
let selectedFood: FoodItem | null = null; // review here
|
||||||
let currentHighlightIndex: number = -1;
|
let currentHighlightIndex: number = -1;
|
||||||
let currentResults: FoodItem[] = [];
|
let currentResults: FoodItem[] = [];
|
||||||
|
let totalCaloriesToday: number = 0;
|
||||||
|
let totalGramsToday: number = 0;
|
||||||
|
let totalAlkalineToday: number = 0;
|
||||||
|
|
||||||
// Authentication state
|
// Authentication state
|
||||||
let currentUser: any | null = null;
|
let currentUser: any | null = null;
|
||||||
@@ -283,11 +287,20 @@ function hideLoading() {
|
|||||||
|
|
||||||
// Clear application data
|
// Clear application data
|
||||||
function clearAppData() {
|
function clearAppData() {
|
||||||
getDivById('foodTableBody').innerHTML = '';
|
|
||||||
getDivById('caloriesCounter').textContent = '0';
|
getDivById('caloriesCounter').textContent = '0';
|
||||||
getInputById('foodSearchInput').value = '';
|
getInputById('foodSearchInput').value = '';
|
||||||
getInputById('gramAmount').value = '100';
|
getInputById('gramAmount').value = '100';
|
||||||
|
getDivById('foodCardsContainer').innerHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="empty-state-icon">🍽️</div>
|
||||||
|
<div>No food items logged yet.</div>
|
||||||
|
<div style="margin-top: 8px; font-size: 14px; opacity: 0.7;">Add your first item to get started!</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
selectedFood = null;
|
selectedFood = null;
|
||||||
|
totalCaloriesToday = 0;
|
||||||
|
totalGramsToday = 0;
|
||||||
|
totalAlkalineToday = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFoodData = (grams: number, foodData: FoodItem): FoodItem => {
|
const getFoodData = (grams: number, foodData: FoodItem): FoodItem => {
|
||||||
@@ -537,17 +550,65 @@ function hideSearchResults() {
|
|||||||
currentHighlightIndex = -1;
|
currentHighlightIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleCardHandler(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const card = e.currentTarget as HTMLElement;
|
||||||
|
card.classList.toggle('expanded');
|
||||||
|
|
||||||
|
const cardDetails = card.nextElementSibling as HTMLElement;
|
||||||
|
if (cardDetails && cardDetails.classList.contains('card-details')) {
|
||||||
|
cardDetails.classList.toggle('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCardHandler(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const card = e.currentTarget as HTMLElement;
|
||||||
|
const foodId = card.getAttribute('data-food-id');
|
||||||
|
|
||||||
|
if (foodId) {
|
||||||
|
handleDeleteFood(foodId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function editCardHandler(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const card = e.currentTarget as HTMLElement;
|
||||||
|
const foodId = card.getAttribute('data-food-id');
|
||||||
|
|
||||||
|
if (foodId) {
|
||||||
|
// FIXME: Implement edit functionality
|
||||||
|
// handleEditFood(foodId);
|
||||||
|
window.alert('Edit functionality is not implemented yet.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const addDeleteEvents = () => {
|
const addDeleteEvents = () => {
|
||||||
const elements = getButtonListByClassName('delete-food-entry');
|
// Setup toggle show cards events
|
||||||
Array.from(elements).forEach((el: HTMLButtonElement) => {
|
const cards = getButtonListByClassName('card-header-toggle');
|
||||||
el.addEventListener('click', (e) => {
|
Array.from(cards).forEach((card: HTMLElement) => {
|
||||||
const target = e.target as HTMLElement;
|
card.removeEventListener('click', toggleCardHandler);
|
||||||
const row = target.closest('tr');
|
card.addEventListener('click', toggleCardHandler);
|
||||||
const entryId = row?.querySelector('.hidden-column')?.textContent;
|
});
|
||||||
if (entryId && row) {
|
|
||||||
handleDeleteFood(entryId);
|
// Setup delete card events
|
||||||
}
|
const deleteCards = getButtonListByClassName('btn-delete');
|
||||||
});
|
Array.from(deleteCards).forEach((card: HTMLElement) => {
|
||||||
|
card.removeEventListener('click', deleteCardHandler);
|
||||||
|
card.addEventListener('click', deleteCardHandler);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup edit card events
|
||||||
|
const editCards = getButtonListByClassName('btn-edit');
|
||||||
|
Array.from(editCards).forEach((card: HTMLElement) => {
|
||||||
|
card.removeEventListener('click', editCardHandler);
|
||||||
|
card.addEventListener('click', editCardHandler);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,7 +672,7 @@ const addFood = async () => {
|
|||||||
addFoodToTable(entry, savedEntry.$id);
|
addFoodToTable(entry, savedEntry.$id);
|
||||||
|
|
||||||
// Update totals
|
// Update totals
|
||||||
updateTotalCalories();
|
updateTotalCalories([savedEntry]);
|
||||||
|
|
||||||
// Update total macros
|
// Update total macros
|
||||||
const proteinDiv: HTMLElement = getDivById('proteinValue');
|
const proteinDiv: HTMLElement = getDivById('proteinValue');
|
||||||
@@ -655,7 +716,6 @@ async function loadFoodEntries(date: Date) {
|
|||||||
const entries = await AppwriteDB.getFoodEntries(date);
|
const entries = await AppwriteDB.getFoodEntries(date);
|
||||||
|
|
||||||
// Clear current table
|
// Clear current table
|
||||||
getDivById('foodTableBody').innerHTML = '';
|
|
||||||
getDivById('foodCardsContainer').innerHTML = '';
|
getDivById('foodCardsContainer').innerHTML = '';
|
||||||
|
|
||||||
// Add each entry to table
|
// Add each entry to table
|
||||||
@@ -691,7 +751,7 @@ async function loadFoodEntries(date: Date) {
|
|||||||
addDeleteEvents();
|
addDeleteEvents();
|
||||||
|
|
||||||
// Update total calories
|
// Update total calories
|
||||||
updateTotalCalories();
|
updateTotalCalories(entries);
|
||||||
|
|
||||||
// Update counters
|
// Update counters
|
||||||
let totalCalories = 0;
|
let totalCalories = 0;
|
||||||
@@ -783,7 +843,6 @@ async function handleDeleteFood(documentId: string) {
|
|||||||
await AppwriteDB.deleteFoodEntry(documentId);
|
await AppwriteDB.deleteFoodEntry(documentId);
|
||||||
|
|
||||||
selectDate(selectedDate);
|
selectDate(selectedDate);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Delete food error:', error);
|
console.error('Delete food error:', error);
|
||||||
swal('Oh no!', 'Failed to delete food entry: ' + error.message, 'error');
|
swal('Oh no!', 'Failed to delete food entry: ' + error.message, 'error');
|
||||||
@@ -794,36 +853,18 @@ async function handleDeleteFood(documentId: string) {
|
|||||||
|
|
||||||
// Add food to table (existing function - update to use document ID)
|
// Add food to table (existing function - update to use document ID)
|
||||||
function addFoodToTable(foodData: FoodStorage, documentId: string) {
|
function addFoodToTable(foodData: FoodStorage, documentId: string) {
|
||||||
const tableBody = document.getElementById('foodTableBody');
|
|
||||||
const row = document.createElement('tr');
|
|
||||||
|
|
||||||
row.innerHTML = `
|
|
||||||
<td class="hidden-column">${documentId}</td>
|
|
||||||
<td>${foodData.time || new Date().toLocaleTimeString()}</td>
|
|
||||||
<td>${foodData.name}</td>
|
|
||||||
<td>${foodData.grams}</td>
|
|
||||||
<td>${foodData.calories}</td>
|
|
||||||
<td>${foodData.protein}</td>
|
|
||||||
<td>${foodData.fat}</td>
|
|
||||||
<td>${foodData.carbs}</td>
|
|
||||||
<td>${foodData.fiber}</td>
|
|
||||||
<td><button class="delete-btn delete-food-entry">Delete</button></td>
|
|
||||||
<td class="hidden-column">${foodData.alkaline}</td>
|
|
||||||
`;
|
|
||||||
|
|
||||||
tableBody?.appendChild(row);
|
|
||||||
|
|
||||||
// Setup Delete events
|
|
||||||
addDeleteEvents();
|
|
||||||
|
|
||||||
// New Food card
|
// New Food card
|
||||||
const container = document.getElementById('foodCardsContainer');
|
const container = getDivById('foodCardsContainer');
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'food-card';
|
card.className = 'food-card';
|
||||||
card.setAttribute('data-id', documentId);
|
card.setAttribute('data-id', documentId);
|
||||||
|
|
||||||
|
if (container.innerHTML.includes('empty-state')) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="card-header" onclick="toggleCard(${documentId})">
|
<div class="card-header card-header-toggle">
|
||||||
<div class="card-main-info">
|
<div class="card-main-info">
|
||||||
<div class="food-name-time">
|
<div class="food-name-time">
|
||||||
<div class="food-name">${foodData.name}</div>
|
<div class="food-name">${foodData.name}</div>
|
||||||
@@ -854,42 +895,45 @@ function addFoodToTable(foodData: FoodStorage, documentId: string) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<button class="btn btn-edit" onclick="editFood(${documentId})">Edit</button>
|
<button class="btn btn-edit" data-food-id="${documentId}">Edit</button>
|
||||||
<button class="btn btn-delete" onclick="deleteFood(${documentId})">Delete</button>
|
<button class="btn btn-delete" data-food-id="${documentId}">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
container?.appendChild(card);
|
container?.appendChild(card);
|
||||||
|
|
||||||
|
// Setup Delete events
|
||||||
|
addDeleteEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total calories
|
// Update total calories
|
||||||
function updateTotalCalories() {
|
function updateTotalCalories(entries: Models.DefaultDocument[]) {
|
||||||
const caloriesCells = document.querySelectorAll('#foodTableBody td:nth-child(5)'); // 5th column is calories
|
let total = totalCaloriesToday;
|
||||||
let total = 0;
|
|
||||||
|
|
||||||
caloriesCells.forEach((cell: Element) => {
|
entries.forEach((entry: Models.DefaultDocument) => {
|
||||||
total += parseInt(cell.innerHTML) || 0;
|
total += entry.calories || 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
getDivById('caloriesCounter').textContent = total.toString();
|
getDivById('caloriesCounter').textContent = total.toString();
|
||||||
|
totalCaloriesToday = total;
|
||||||
|
|
||||||
|
// FIX ME: alkaline count
|
||||||
// Update alkaline level
|
// Update alkaline level
|
||||||
const gramsCell = document.querySelectorAll('#foodTableBody td:nth-child(4)'); // 4th is grams
|
let totalGrams = totalGramsToday;
|
||||||
let totalGrams = 0;
|
|
||||||
let indexMap: {index: number, grams: number}[] = [];
|
let indexMap: {index: number, grams: number}[] = [];
|
||||||
gramsCell.forEach((cell: Element, key: number) => {
|
entries.forEach((entry: Models.DefaultDocument, key: number) => {
|
||||||
totalGrams += parseInt(cell.innerHTML) || 0;
|
totalGrams += entry.grams || 0;
|
||||||
indexMap.push({
|
indexMap.push({
|
||||||
index: key,
|
index: key,
|
||||||
grams: parseInt(cell.innerHTML) || 0
|
grams: entry.grams || 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const alkalineCell = document.querySelectorAll('#foodTableBody td:nth-child(11)'); // 11th is alkaline
|
let totalAlkaline = totalAlkalineToday;
|
||||||
let totalAlkaline = 0;
|
entries.forEach((entry: Models.DefaultDocument, key: number) => {
|
||||||
alkalineCell.forEach((cell: Element, key: number) => {
|
if (entry.alkaline) {
|
||||||
if (cell.innerHTML === 'true') {
|
|
||||||
for (let ob of indexMap) {
|
for (let ob of indexMap) {
|
||||||
if (ob.index === key) {
|
if (ob.index === key) {
|
||||||
totalAlkaline += ob.grams;
|
totalAlkaline += ob.grams;
|
||||||
@@ -916,6 +960,9 @@ const nextMonth = () => {
|
|||||||
|
|
||||||
const selectDate = async (date: Date) => {
|
const selectDate = async (date: Date) => {
|
||||||
selectedDate = date;
|
selectedDate = date;
|
||||||
|
totalCaloriesToday = 0;
|
||||||
|
totalGramsToday = 0;
|
||||||
|
totalAlkalineToday = 0;
|
||||||
updateCurrentDate();
|
updateCurrentDate();
|
||||||
await loadFoodEntries(date);
|
await loadFoodEntries(date);
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
@@ -1028,16 +1075,16 @@ function createDayElement(day: number, isOtherMonth: boolean, year: number, mont
|
|||||||
return dayElement;
|
return dayElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleCard(id) {
|
function toggleCard(id: string) {
|
||||||
const card = document.querySelector(`[data-id="${id}"]`);
|
const card = document.querySelector(`[data-id="${id}"]`);
|
||||||
card.classList.toggle('expanded');
|
card?.classList.toggle('expanded');
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteFood(id) {
|
function deleteFood(id: string) {
|
||||||
handleDeleteFood(id);
|
handleDeleteFood(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function editFood(id) {
|
function editFood(id: string) {
|
||||||
alert(`Edit functionality for food item ${id} would be implemented here`);
|
alert(`Edit functionality for food item ${id} would be implemented here`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,10 @@ h1 {
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-header .food-name {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.food-time {
|
.food-time {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
@@ -251,19 +255,20 @@ h1 {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.food-card.expanded .expand-icon {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-details {
|
.card-details {
|
||||||
max-height: 0;
|
display: none;
|
||||||
overflow: hidden;
|
|
||||||
transition: max-height 0.3s ease;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.food-card.expanded .card-details {
|
.card-details.expanded {
|
||||||
max-height: 300px;
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-toggle .expand-icon {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-toggle.expanded .expand-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-content {
|
.details-content {
|
||||||
@@ -288,7 +293,6 @@ h1 {
|
|||||||
.nutrition-label {
|
.nutrition-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
@@ -530,6 +534,7 @@ label {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-header {
|
.calendar-header {
|
||||||
|
|||||||
Reference in New Issue
Block a user