diff --git a/backend/automations.json b/backend/automations.json new file mode 100644 index 0000000..1a66100 --- /dev/null +++ b/backend/automations.json @@ -0,0 +1,12 @@ +{ + "automation_example": [ + { + "id": 1, + "device": "absolute device name goes here", + "name": "Name goes here", + "enabled": true, + "triggers": "time goes here (in 24 hour format)", + "action": "function call goes here" + } + ] +} \ No newline at end of file diff --git a/backend/automations.py b/backend/automations.py index e69de29..cc1b2f4 100644 --- a/backend/automations.py +++ b/backend/automations.py @@ -0,0 +1,58 @@ +import devices_json as dj +import schedule + +automationFile = "automations.json" + +import json +import schedule + +automationFile = "automations.json" +updates = [] + + +def loadAutomations(): + try: + with open(automationFile, "r") as JSONfile: + return json.load(JSONfile) + except FileNotFoundError: + updates.append("Error: automations.json not found!") + return {"automations": []} + + +def saveAutomations(data): + with open(automationFile, "w") as JSONfile: + json.dump(data, JSONfile, indent=2) + + +def addAutomation(automation): # Add automation to automations.json + data = loadAutomations() + automations = data.get("automations", []) + automations.append(automation) + saveAutomations(data) + updates.append(f"Added {automation['name']} to the automation table!") + + +# Manipulation of automation schedules down below +def changeAutomationStatus(id): + data = loadAutomations() + automations = data.get("automations", []) + for automation in automations: + if automation["id"] == id: + automation["enabled"] = not automation["enabled"] + saveAutomations(data) + updates.append(f"Changed status of {automation['name']} to {automation['enabled']}!") + return + updates.append("Error: Automation with specified ID not found (backend error!)") + return + +def deleteAutomation(id): + data = loadAutomations() + automations = data.get("automations", []) + for automation in automations: + if automation["id"] == id: + automations.remove(automation) + saveAutomations(data) + updates.append(f"Deleted {automation['name']} from the automation table!") + return + updates.append("Error: Automation with specified ID not found (backend error!)") + return diff --git a/backend/devices_json.py b/backend/devices_json.py index a50e677..042560b 100644 --- a/backend/devices_json.py +++ b/backend/devices_json.py @@ -106,6 +106,9 @@ def sumRating(): devices = data.get("smart_home_devices", []) return sum(device["power_rating"] for device in devices) +def deviceFunctions(): # Returns the list of device functions for scheduling purposes and possibly miscellaneous purposes + return ["Set Oven Timer", "Change Device Status"] + async def updateDevices(): while True: data = loadJSON() diff --git a/backend/fastAPI.py b/backend/fastAPI.py index e6591de..77ef1dd 100644 --- a/backend/fastAPI.py +++ b/backend/fastAPI.py @@ -71,3 +71,8 @@ def get_user_data(): return {"error": f"User database file not found"} except json.JSONDecodeError: return {"error": "Error decoding JSON data"} + +@app.get("/device_functions") +def get_device_functions(): + """Returns the list of device functions.""" + return {"functions": dj.deviceFunctions()} \ No newline at end of file diff --git a/backend/users.py b/backend/users.py new file mode 100644 index 0000000..8f9cac3 --- /dev/null +++ b/backend/users.py @@ -0,0 +1 @@ +selected_user = "" \ No newline at end of file diff --git a/database/.env b/database/.env new file mode 100644 index 0000000..260588c --- /dev/null +++ b/database/.env @@ -0,0 +1,3 @@ +FIREBASE_PROJECT_ID=powerhouse-62f4d +FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+dm+isAoRCwvI\nzuymf7bLtZ0ig1YLLVpcEsG36wX2JjXuvCptgQ35VKUX1rE5t7ChIt7Rv/c8+Z3o\nr1fSCS9Xowq64pmX5MXzyI8WXsolg+zoSGxBo+puwSkqfnfo5ozRaUz7YjkosGIM\nX2+A3+jAf1bge6X3CMXerTer1dr9XqOtA+JQ01rkb47Hvx+ty2KrDuVMw/b0ERbv\nIa5P7e7fVklF7yFNEGdcZck5+X6Dc79otvH1WFg82to2Id42lnt2zvEZRRJdIugU\nDelt41LHGiQ8fZzrUMH2ztSPuDjSOwL1EMscZaIlULOl34uH4yIifnEC4N+X56l9\nmGbFQDvzAgMBAAECggEAXCmSVn5puJqIPsjGYcD+Eq/UlA1KdwkgptoarCUJUvVl\ndprNywQ+X05yvsjQJFY7HZXyjTufZv2AyEYEsGlqY7VM7WbWHNaCZTdA6fm/Ro0C\nd9oHrHGlcKd+hIYC/wuEy5ZxPllmwEYtg8AwhAOVLITR5Llgc1vm55wZCsjmeWBa\n/feT1HVPH550NZyGvnmokrnq74kpEKG/pEHB1cG9hkJOPwLoTbEIE3eHDBI2xSaI\nlGOBNEi21q8isYLXSjPCzHINBr+/59VbkJGMHBgIYUDLgAUdZbAzvL3PQTyPCrPH\nIrCU1lg/KOJNjnsFr1196thtaDyfjwppII02j+0SjQKBgQDiZD1XdDyUiBAb1Zdc\nILvojGRro3clSTZtTVpu+l60IldY4rrQ8uhBnpO0bQQflt0iq8N4IC6QsCAOzK6U\noH+6ZfgYL5AZJKU5D5h+lNdMO963X1M+7NmqiXJygYLrh2wSpXceIjJHh3HxoYn9\npu6J5zgtTbJ/AffNB5aYQZUl9wKBgQDXX0TjHkpGXxrSYgGgWtNu5VfNnbDluKKt\nbQ2s6BNC7H74IGMF4/CmkzJk+u3t4Kqy6fiLcdC11NNbMc1WjOft8X2laGQRxL+8\n6SwwpuAEtYffwv5R8XzhTzaxQ0QykOZHF0KoXU90U52FLhnO4CabD/FBNDTGwyrl\n81WWJItq5QKBgQCkNI6lkyKpnTMJ7Vd2tCt3VfU0eYFe4wZ+KyAP5LuyLkJqrzFP\ngxBhIBMESBo4i2Vj02/Y/oan3YezFFUAapWaboGPkVgUVJcelyhFj9HFcpttJ9Aq\nkQKkBkOjuDpseXIy6B9JFQahCSXlXATJnT3henstqPBx3MyOwxVA5Di5KwKBgHuc\nFD9+LQcCorRIx8IvHtaxDpnWfab7N3Lt+kFY2WU5sw3aGLtSvdX8+Zl3Y4N5xL73\nBk2SP0V9JulaRg38xUiNp519sqF4GMvl0BUEUeKiBZ/pnGmPfisaebt3bfbPrzry\nILHdDDMpomjDstBHWiD5H3Ba9Ed30HE2HdMUmmf5AoGAVXZD00+Z2MPZZXNmIoNt\nl2p5y+M/UCm8GP93287DaqVzeW1kpbwNmMPAgwUr0rrv+k/EpOUpgZbtCmttx3F5\nsTnX7nt3MwDTXUrtRMqVkahzWpf2KlgicQzbeH2CrtvdvLchAry0x3DQ0QAJWZ6O\nSCPTXOwvEU3x63TkwO+Ogbs=\n-----END PRIVATE KEY-----\n" +FIREBASE_CLIENT_EMAIL=firebase-adminsdk-fbsvc@powerhouse-62f4d.iam.gserviceaccount.com diff --git a/database/devices_db.py b/database/devices_db.py index 08d9c67..46f02c0 100644 --- a/database/devices_db.py +++ b/database/devices_db.py @@ -1,63 +1,135 @@ -import firebase_admin -from firebase_admin import credentials -from firebase_admin import firestore +import os +import time import json -import os - +import firebase_admin +from firebase_admin import credentials, firestore +import watchdog +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler from dotenv import load_dotenv +from hashlib import sha256 + +# Load environment variables +load_dotenv() + +# Firebase credentials +FIREBASE_PROJECT_ID = os.getenv('FIREBASE_PROJECT_ID') +FIREBASE_PRIVATE_KEY = os.getenv('FIREBASE_PRIVATE_KEY').replace('\\n', '\n') # Fix newlines +FIREBASE_CLIENT_EMAIL = os.getenv('FIREBASE_CLIENT_EMAIL') + +firebase_config = { + "type": "service_account", + "project_id": FIREBASE_PROJECT_ID, + "private_key": FIREBASE_PRIVATE_KEY, + "client_email": FIREBASE_CLIENT_EMAIL, + "token_uri": "https://oauth2.googleapis.com/token" +} + +# Initialize Firebase Admin SDK if not already initialized +if not firebase_admin._apps: + cred = credentials.Certificate(firebase_config) + firebase_admin.initialize_app(cred) -cred = credentials.Certificate("/Users/annerinjeri/Downloads/powerhouse-62f4d-firebase-adminsdk-fbsvc-9a2e946c98.json") -firebase_admin.initialize_app(cred) +# Firestore reference db = firestore.client() -load_dotenv() +# Path to the JSON file +JSON_FILE_PATH = os.path.join(os.path.dirname(__file__), "../backend/devices.json") + +# Track last JSON hash to avoid unnecessary updates +last_json_hash = None + + +def hash_file_content(file_path): + """Calculate SHA256 hash of the file content.""" + with open(file_path, "r") as f: + content = f.read() + return sha256(content.encode()).hexdigest() + + +def update_firestore(): + """Sync JSON data with Firestore.""" + global last_json_hash + + new_hash = hash_file_content(JSON_FILE_PATH) + if new_hash == last_json_hash: + print("No changes detected in JSON. Skipping Firestore update.") + return # Skip update if the content hasn't changed -LAST_MODIFIED_TIME = 0 + last_json_hash = new_hash # Update hash -def set_doc_data(coll_ref, file): - rel_path=rel_path = os.path.join(os.path.dirname(__file__), f"../backend/{file}") - JSONfile = open(rel_path, "r") - data = json.load(JSONfile) - JSONfile.close() + with open(JSON_FILE_PATH, "r") as f: + data = json.load(f) + # Filter devices that are "connected" filtered_devices = [ - {key: device[key] for key in ["id", "name", "ip"]} - for device in data["smart_home_devices"] - - if device.get("connection_status") == "connected" + {key: device[key] for key in ["id", "name", "ip"]} + for device in data["smart_home_devices"] + if device.get("connection_status") == "connected" ] - for i in range(len(filtered_devices)): - coll_ref.document().set(filtered_devices[i]) - -def delete_collection(coll_ref): - - count_query = coll_ref.count() - query_result = count_query.get() - coll_size=query_result[0][0].value - - if coll_size == 0: - return - - docs = coll_ref.list_documents(page_size=coll_size) - deleted = 0 - - for doc in docs: - print(f"Deleting doc {doc.id} => {doc.get().to_dict()}") - doc.delete() - deleted = deleted + 1 - - if deleted >= coll_size: - return delete_collection(coll_ref) - -def is_json_updated(file): - global LAST_MODIFIED_TIME - current_modified_time = os.path.getmtime(file) - if current_modified_time > LAST_MODIFIED_TIME: - LAST_MODIFIED_TIME = current_modified_time - return True - return False - - -delete_collection(db.collection("Devices")) -set_doc_data(db.collection("Devices"),"devices.json") \ No newline at end of file + existing_devices = {doc.id: doc.to_dict() for doc in db.collection("Devices").stream()} + + added_devices = [] + modified_devices = [] + deleted_devices = [] + + new_device_ids = {str(device["id"]) for device in filtered_devices} + + # Add or update devices + for device in filtered_devices: + device_id = str(device["id"]) + if device_id not in existing_devices: + db.collection("Devices").document(device_id).set(device) + added_devices.append(device) + elif existing_devices[device_id] != device: + db.collection("Devices").document(device_id).set(device) + modified_devices.append(device) + + # Delete devices not in the JSON anymore + for device_id in existing_devices.keys(): + if device_id not in new_device_ids: + db.collection("Devices").document(device_id).delete() + deleted_devices.append(device_id) + + # Logging updates + if added_devices: + print(f"Added devices: {added_devices}") + if modified_devices: + print(f"Updated devices: {modified_devices}") + if deleted_devices: + print(f"Deleted devices: {deleted_devices}") + + if not (added_devices or modified_devices or deleted_devices): + print("No changes detected in Firestore.") + + +class JSONFileHandler(FileSystemEventHandler): + """Watches the JSON file for modifications.""" + + def on_modified(self, event): + """Triggered when JSON file is modified.""" + if event.src_path.endswith("devices.json"): + print("Detected JSON file update. Syncing with Firestore...") + update_firestore() + + +def start_file_watcher(): + """Start watching the JSON file for changes.""" + event_handler = JSONFileHandler() + observer = Observer() + observer.schedule(event_handler, path=os.path.dirname(JSON_FILE_PATH), recursive=False) + observer.start() + + try: + while True: + time.sleep(1) # Keep script running + except KeyboardInterrupt: + observer.stop() + observer.join() + + +if __name__ == "__main__": + print("Starting file watcher for devices.json...") + start_file_watcher() + diff --git a/database/users_db.json b/database/users_db.json index f6482f5..090407a 100644 --- a/database/users_db.json +++ b/database/users_db.json @@ -1,35 +1,38 @@ { "users": [ { + "user_id": 1, "user_name": "John Doe", "user_password": "1415", "allocated_devices": [ - "device1", - "device2", - "device3", - "device4", - "device5", - "device6" + "1", + "2", + "3", + "4", + "5", + "6" ], "user_role": "super_user" }, { + "user_id": 2, "user_name": "Jane Doe", "user_password": "2005", "allocated_devices": [ - "device3", - "device4" + "3", + "4" ], "user_role": "sub_user" }, { + "user_id": 3, "user_name": "John Smith", "user_password": "0007", "allocated_devices": [ - "device5", - "device6" + "5", + "6" ], "user_role": "sub_user" } ] -} \ No newline at end of file +} diff --git a/database/users_db.py b/database/users_db.py new file mode 100644 index 0000000..0b09e05 --- /dev/null +++ b/database/users_db.py @@ -0,0 +1,34 @@ +import firebase_admin +from firebase_admin import credentials, firestore +import json +import os +#rom dotenv import load_dotenv + +cred = credentials.Certificate("/Users/annerinjeri/Downloads/powerhouse-62f4d-firebase-adminsdk-fbsvc-9a2e946c98.json") +firebase_admin.initialize_app(cred) +db = firestore.client() + +#load_dotenv() +def_set_doc_data(collref,) +# Load JSON file +with open("users.json", "r") as file: + data = json.load(file) + +# Upload users to Firestore with device references +for user in data["users"]: + user_id = user["user_name"].replace(" ", "_").lower() # Unique Firestore ID + user_ref = db.collection("Users").document(user_id) + + # Convert device IDs into Firestore document references + device_refs = [db.collection("Devices").document(device_id) for device_id in user["allocated_devices"]] + + user_data = { + "user_name": user["user_name"], + "user_password": user["user_password"], # Password stored as plain text (⚠ Not recommended for production) + "allocated_devices": device_refs, # Store Firestore document references + "user_role": user["user_role"] + } + + user_ref.set(user_data) + print(f"User {user['user_name']} added with device references!") + diff --git a/frontend/src/app/automations/page.jsx b/frontend/src/app/automations/page.jsx index f988c66..8d4e928 100644 --- a/frontend/src/app/automations/page.jsx +++ b/frontend/src/app/automations/page.jsx @@ -1,11 +1,131 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import Breadcrumb from "../ui/dashboard/breadcrumbs"; +import { + Box, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + MenuItem, +} from "@mui/material"; + +import AddIcon from "@mui/icons-material/Add"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { TimePicker } from "@mui/x-date-pickers/TimePicker"; // Use TimePicker instead +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import dayjs from "dayjs"; const Automations = () => { + const [openDialog, setOpenDialog] = useState(false); + const [name, setName] = useState(""); + const [trigger, setTrigger] = useState(dayjs()); + const [condition, setCondition] = useState(""); + + const conditions = ["Temperature > 25°C", "Motion Detected", "Light On", "Door Open"]; + + // Open the dialog + const handleOpenDialog = () => { + setOpenDialog(true); + }; + + // Close the dialog + const handleCloseDialog = () => { + setOpenDialog(false); + setName(""); + setTrigger(dayjs()); + setCondition(""); + }; + + // Handle form submission + const handleSave = () => { + console.log("Saving Automation:", { name, trigger, condition }); + handleCloseDialog(); + }; + return (
+ + + + + + + {/* Dialog for Adding Automation */} + + Add Automation Schedule + + {/* Name Input */} + setName(e.target.value)} + sx={{fontFamily: "JetBrains Mono"}} + /> + + {/* Trigger Time Picker */} + + setTrigger(newValue)} + renderInput={(params) => } + /> + + + {/* Condition Dropdown */} + setCondition(e.target.value)} + > + {conditions.map((option) => ( + + {option} + + ))} + + + + {/* Dialog Actions */} + + + + +
); }; diff --git a/frontend/src/app/dashboard/page.jsx b/frontend/src/app/dashboard/page.jsx index b0e580b..1898532 100644 --- a/frontend/src/app/dashboard/page.jsx +++ b/frontend/src/app/dashboard/page.jsx @@ -47,7 +47,7 @@ const Dashboard = () => { const [data, setData] = useState([]); const [checked, setChecked] = useState([]); - const [timeRange, setTimeRange] = useState("daily"); + const [timeRange, setTimeRange] = useState("realtime"); const theme = useTheme(); const boxShadow = @@ -339,6 +339,17 @@ const Dashboard = () => { sx={{ marginTop: "auto", alignSelf: "center" }} color="primary" > + - diff --git a/package-lock.json b/package-lock.json index ee94fee..1e48a6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,10 @@ "@emotion/styled": "^11.14.0", "@fontsource/jetbrains-mono": "^5.1.2", "@mui/icons-material": "^6.4.1", + "@mui/lab": "^6.0.0-beta.28", "@mui/material": "^6.4.3", "@mui/x-charts": "^7.25.0", + "@mui/x-date-pickers": "^7.27.1", "dayjs": "^1.11.13", "framer-motion": "^12.4.1", "next": "^15.1.7", @@ -1122,6 +1124,44 @@ "license": "Apache-2.0", "peer": true }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@fontsource/jetbrains-mono": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.1.2.tgz", @@ -1654,10 +1694,43 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.69", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.69.tgz", + "integrity": "sha512-r2YyGUXpZxj8rLAlbjp1x2BnMERTZ/dMqd9cClKj2OJ7ALAuiv/9X5E9eHfRc9o/dGRuLSMq/WTjREktJVjxVA==", + "deprecated": "This package has been replaced by @base-ui-components/react", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@floating-ui/react-dom": "^2.1.1", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.1", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.3.tgz", - "integrity": "sha512-hlyOzo2ObarllAOeT1ZSAusADE5NZNencUeIvXrdQ1Na+FL1lcznhbxfV5He1KqGiuR8Az3xtCUcYKwMVGFdzg==", + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.5.tgz", + "integrity": "sha512-zoXvHU1YuoodgMlPS+epP084Pqv9V+Vg+5IGv9n/7IIFVQ2nkTngYHYxElCq8pdTTbDcgji+nNh0lxri2abWgA==", "license": "MIT", "funding": { "type": "opencollective", @@ -1690,14 +1763,59 @@ } } }, + "node_modules/@mui/lab": { + "version": "6.0.0-beta.28", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.28.tgz", + "integrity": "sha512-7d/nG5L3IXcdlgGvOcVwOZIQCHxjQOVVEGOtk0t0XSCQnS5zgQ5/KeKE/brWYdxA6ybSm3vp1sfBGq4AeqMCKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/base": "5.0.0-beta.69", + "@mui/system": "^6.4.3", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.3", + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": "^6.4.5", + "@mui/material-pigment-css": "^6.4.3", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.3.tgz", - "integrity": "sha512-ubtQjplbWneIEU8Y+4b2VA0CDBlyH5I3AmVFGmsLyDe/bf0ubxav5t11c8Afem6rkSFWPlZA2DilxmGka1xiKQ==", + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.5.tgz", + "integrity": "sha512-5eyEgSXocIeV1JkXs8mYyJXU0aFyXZIWI5kq2g/mCnIgJe594lkOBNAKnCIaGVfQTu2T6TTEHF8/hHIqpiIRGA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.4.3", + "@mui/core-downloads-tracker": "^6.4.5", "@mui/system": "^6.4.3", "@mui/types": "^7.2.21", "@mui/utils": "^6.4.3", @@ -2014,6 +2132,92 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@mui/x-date-pickers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.27.1.tgz", + "integrity": "sha512-2YPhTM9TM39dmIkEQdSB6P6NASePB9LuhXXKQqq0PX4FXGymYEPz/acQXkk617zwfxJJaDhJZ6g8SAv5pklTJQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0", + "@mui/x-internals": "7.26.0", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2 || ^3.0.0", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers/node_modules/@mui/x-internals": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.26.0.tgz", + "integrity": "sha512-VxTCYQcZ02d3190pdvys2TDg9pgbvewAVakEopiOgReKAUhLdRlgGJHcOA/eAuGLyK1YIo26A6Ow6ZKlSRLwMg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@mui/x-internals": { "version": "7.25.0", "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.25.0.tgz", diff --git a/package.json b/package.json index 3b5638c..a70e855 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,10 @@ "@emotion/styled": "^11.14.0", "@fontsource/jetbrains-mono": "^5.1.2", "@mui/icons-material": "^6.4.1", + "@mui/lab": "^6.0.0-beta.28", "@mui/material": "^6.4.3", "@mui/x-charts": "^7.25.0", + "@mui/x-date-pickers": "^7.27.1", "dayjs": "^1.11.13", "framer-motion": "^12.4.1", "next": "^15.1.7",