mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #460 from learnhouse/fix/assignments-issues
Assignments issues
This commit is contained in:
commit
3a95abfa6f
17 changed files with 489 additions and 163 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
import logfire
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
from config.config import LearnHouseConfig, get_learnhouse_config
|
from config.config import LearnHouseConfig, get_learnhouse_config
|
||||||
from src.core.events.events import shutdown_app, startup_app
|
from src.core.events.events import shutdown_app, startup_app
|
||||||
|
|
@ -38,6 +39,9 @@ app.add_middleware(
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logfire.configure(console=False, service_name=learnhouse_config.site_name,)
|
||||||
|
logfire.instrument_fastapi(app)
|
||||||
|
|
||||||
# Gzip Middleware (will add brotli later)
|
# Gzip Middleware (will add brotli later)
|
||||||
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,6 @@ from pydantic import BaseModel
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
class SentryConfig(BaseModel):
|
|
||||||
dsn: str
|
|
||||||
environment: str
|
|
||||||
release: str
|
|
||||||
|
|
||||||
|
|
||||||
class CookieConfig(BaseModel):
|
class CookieConfig(BaseModel):
|
||||||
domain: str
|
domain: str
|
||||||
|
|
||||||
|
|
@ -53,7 +47,6 @@ class HostingConfig(BaseModel):
|
||||||
allowed_origins: list
|
allowed_origins: list
|
||||||
allowed_regexp: str
|
allowed_regexp: str
|
||||||
self_hosted: bool
|
self_hosted: bool
|
||||||
sentry_config: Optional[SentryConfig]
|
|
||||||
cookie_config: CookieConfig
|
cookie_config: CookieConfig
|
||||||
content_delivery: ContentDeliveryConfig
|
content_delivery: ContentDeliveryConfig
|
||||||
|
|
||||||
|
|
@ -150,10 +143,7 @@ def get_learnhouse_config() -> LearnHouseConfig:
|
||||||
env_self_hosted = os.environ.get("LEARNHOUSE_SELF_HOSTED")
|
env_self_hosted = os.environ.get("LEARNHOUSE_SELF_HOSTED")
|
||||||
env_sql_connection_string = os.environ.get("LEARNHOUSE_SQL_CONNECTION_STRING")
|
env_sql_connection_string = os.environ.get("LEARNHOUSE_SQL_CONNECTION_STRING")
|
||||||
|
|
||||||
# Sentry Config
|
|
||||||
env_sentry_dsn = os.environ.get("LEARNHOUSE_SENTRY_DSN")
|
|
||||||
env_sentry_environment = os.environ.get("LEARNHOUSE_SENTRY_ENVIRONMENT")
|
|
||||||
env_sentry_release = os.environ.get("LEARNHOUSE_SENTRY_RELEASE")
|
|
||||||
|
|
||||||
# Fill in values with YAML file if they are not provided
|
# Fill in values with YAML file if they are not provided
|
||||||
site_name = env_site_name or yaml_config.get("site_name")
|
site_name = env_site_name or yaml_config.get("site_name")
|
||||||
|
|
@ -247,33 +237,6 @@ def get_learnhouse_config() -> LearnHouseConfig:
|
||||||
"mailing_config", {}
|
"mailing_config", {}
|
||||||
).get("system_email_adress")
|
).get("system_email_adress")
|
||||||
|
|
||||||
# Sentry config
|
|
||||||
# check if the sentry config is provided in the YAML file
|
|
||||||
sentry_config_verif = (
|
|
||||||
yaml_config.get("hosting_config", {}).get("sentry_config")
|
|
||||||
or env_sentry_dsn
|
|
||||||
or env_sentry_environment
|
|
||||||
or env_sentry_release
|
|
||||||
or None
|
|
||||||
)
|
|
||||||
|
|
||||||
sentry_dsn = env_sentry_dsn or yaml_config.get("hosting_config", {}).get(
|
|
||||||
"sentry_config", {}
|
|
||||||
).get("dsn")
|
|
||||||
sentry_environment = env_sentry_environment or yaml_config.get(
|
|
||||||
"hosting_config", {}
|
|
||||||
).get("sentry_config", {}).get("environment")
|
|
||||||
sentry_release = env_sentry_release or yaml_config.get("hosting_config", {}).get(
|
|
||||||
"sentry_config", {}
|
|
||||||
).get("release")
|
|
||||||
|
|
||||||
if sentry_config_verif:
|
|
||||||
sentry_config = SentryConfig(
|
|
||||||
dsn=sentry_dsn, environment=sentry_environment, release=sentry_release
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
sentry_config = None
|
|
||||||
|
|
||||||
# Payments config
|
# Payments config
|
||||||
env_stripe_secret_key = os.environ.get("LEARNHOUSE_STRIPE_SECRET_KEY")
|
env_stripe_secret_key = os.environ.get("LEARNHOUSE_STRIPE_SECRET_KEY")
|
||||||
env_stripe_publishable_key = os.environ.get("LEARNHOUSE_STRIPE_PUBLISHABLE_KEY")
|
env_stripe_publishable_key = os.environ.get("LEARNHOUSE_STRIPE_PUBLISHABLE_KEY")
|
||||||
|
|
@ -310,7 +273,6 @@ def get_learnhouse_config() -> LearnHouseConfig:
|
||||||
allowed_origins=list(allowed_origins),
|
allowed_origins=list(allowed_origins),
|
||||||
allowed_regexp=allowed_regexp,
|
allowed_regexp=allowed_regexp,
|
||||||
self_hosted=bool(self_hosted),
|
self_hosted=bool(self_hosted),
|
||||||
sentry_config=sentry_config,
|
|
||||||
cookie_config=cookie_config,
|
cookie_config=cookie_config,
|
||||||
content_delivery=content_delivery,
|
content_delivery=content_delivery,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ dependencies = [
|
||||||
"redis>=5.0.7",
|
"redis>=5.0.7",
|
||||||
"requests>=2.32.3",
|
"requests>=2.32.3",
|
||||||
"resend>=2.4.0",
|
"resend>=2.4.0",
|
||||||
"sentry-sdk[fastapi]>=2.13.0",
|
|
||||||
"sqlmodel>=0.0.19",
|
"sqlmodel>=0.0.19",
|
||||||
"tiktoken>=0.7.0",
|
"tiktoken>=0.7.0",
|
||||||
"uvicorn==0.30.1",
|
"uvicorn==0.30.1",
|
||||||
|
|
@ -38,6 +37,7 @@ dependencies = [
|
||||||
"sqlalchemy-utils>=0.41.2",
|
"sqlalchemy-utils>=0.41.2",
|
||||||
"stripe>=11.1.1",
|
"stripe>=11.1.1",
|
||||||
"python-jose>=3.3.0",
|
"python-jose>=3.3.0",
|
||||||
|
"logfire[sqlalchemy]>=3.8.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
import logfire
|
||||||
import os
|
import os
|
||||||
import importlib
|
import importlib
|
||||||
from config.config import get_learnhouse_config
|
from config.config import get_learnhouse_config
|
||||||
|
|
@ -39,6 +40,7 @@ engine = create_engine(
|
||||||
|
|
||||||
# Create all tables after importing all models
|
# Create all tables after importing all models
|
||||||
SQLModel.metadata.create_all(engine)
|
SQLModel.metadata.create_all(engine)
|
||||||
|
logfire.instrument_sqlalchemy(engine=engine)
|
||||||
|
|
||||||
async def connect_to_db(app: FastAPI):
|
async def connect_to_db(app: FastAPI):
|
||||||
app.db_engine = engine # type: ignore
|
app.db_engine = engine # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ from src.core.events.autoinstall import auto_install
|
||||||
from src.core.events.content import check_content_directory
|
from src.core.events.content import check_content_directory
|
||||||
from src.core.events.database import close_database, connect_to_db
|
from src.core.events.database import close_database, connect_to_db
|
||||||
from src.core.events.logs import create_logs_dir
|
from src.core.events.logs import create_logs_dir
|
||||||
from src.core.events.sentry import init_sentry
|
|
||||||
|
|
||||||
|
|
||||||
def startup_app(app: FastAPI) -> Callable:
|
def startup_app(app: FastAPI) -> Callable:
|
||||||
|
|
@ -14,9 +13,6 @@ def startup_app(app: FastAPI) -> Callable:
|
||||||
learnhouse_config: LearnHouseConfig = get_learnhouse_config()
|
learnhouse_config: LearnHouseConfig = get_learnhouse_config()
|
||||||
app.learnhouse_config = learnhouse_config # type: ignore
|
app.learnhouse_config = learnhouse_config # type: ignore
|
||||||
|
|
||||||
# Init Sentry
|
|
||||||
await init_sentry(app)
|
|
||||||
|
|
||||||
# Connect to database
|
# Connect to database
|
||||||
await connect_to_db(app)
|
await connect_to_db(app)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
from fastapi import FastAPI
|
|
||||||
|
|
||||||
import sentry_sdk
|
|
||||||
|
|
||||||
from config.config import LearnHouseConfig
|
|
||||||
|
|
||||||
async def init_sentry(app: FastAPI) -> None:
|
|
||||||
|
|
||||||
learnhouse_config : LearnHouseConfig = app.learnhouse_config # type: ignore
|
|
||||||
if learnhouse_config.hosting_config.sentry_config is not None:
|
|
||||||
sentry_sdk.init(
|
|
||||||
dsn=app.learnhouse_config.hosting_config.sentry_config.dsn, # type: ignore
|
|
||||||
environment=app.learnhouse_config.hosting_config.sentry_config.environment, # type: ignore
|
|
||||||
release=app.learnhouse_config.hosting_config.sentry_config.release, # type: ignore
|
|
||||||
traces_sample_rate=1.0,
|
|
||||||
)
|
|
||||||
|
|
@ -162,7 +162,7 @@ class AssignmentTask(AssignmentTaskBase, table=True):
|
||||||
|
|
||||||
class AssignmentTaskSubmissionBase(SQLModel):
|
class AssignmentTaskSubmissionBase(SQLModel):
|
||||||
"""Represents the common fields for an assignment task submission."""
|
"""Represents the common fields for an assignment task submission."""
|
||||||
|
assignment_task_submission_uuid: str
|
||||||
task_submission: Dict = Field(default={}, sa_column=Column(JSON))
|
task_submission: Dict = Field(default={}, sa_column=Column(JSON))
|
||||||
grade: int = 0 # Value is always between 0-100
|
grade: int = 0 # Value is always between 0-100
|
||||||
task_submission_grade_feedback: str
|
task_submission_grade_feedback: str
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ from src.security.rbac.rbac import (
|
||||||
authorization_verify_based_on_roles_and_authorship,
|
authorization_verify_based_on_roles_and_authorship,
|
||||||
authorization_verify_if_element_is_public,
|
authorization_verify_if_element_is_public,
|
||||||
authorization_verify_if_user_is_anon,
|
authorization_verify_if_user_is_anon,
|
||||||
|
authorization_verify_based_on_roles,
|
||||||
)
|
)
|
||||||
from src.services.courses.activities.uploads.sub_file import upload_submission_file
|
from src.services.courses.activities.uploads.sub_file import upload_submission_file
|
||||||
from src.services.courses.activities.uploads.tasks_ref_files import (
|
from src.services.courses.activities.uploads.tasks_ref_files import (
|
||||||
|
|
@ -565,10 +566,17 @@ async def put_assignment_task_submission_file(
|
||||||
org_statement = select(Organization).where(Organization.id == course.org_id)
|
org_statement = select(Organization).where(Organization.id == course.org_id)
|
||||||
org = db_session.exec(org_statement).first()
|
org = db_session.exec(org_statement).first()
|
||||||
|
|
||||||
# RBAC check
|
# RBAC check - only need read permission to submit files
|
||||||
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||||
|
|
||||||
# Upload reference file
|
# Check if user is enrolled in the course
|
||||||
|
if not await authorization_verify_based_on_roles(request, current_user.id, "read", course.course_uuid, db_session):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="You must be enrolled in this course to submit files"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Upload submission file
|
||||||
if sub_file and sub_file.filename and activity and org:
|
if sub_file and sub_file.filename and activity and org:
|
||||||
name_in_disk = f"{assignment_task_uuid}_sub_{current_user.email}_{uuid4()}.{sub_file.filename.split('.')[-1]}"
|
name_in_disk = f"{assignment_task_uuid}_sub_{current_user.email}_{uuid4()}.{sub_file.filename.split('.')[-1]}"
|
||||||
await upload_submission_file(
|
await upload_submission_file(
|
||||||
|
|
@ -699,7 +707,7 @@ async def handle_assignment_task_submission(
|
||||||
current_user: PublicUser | AnonymousUser,
|
current_user: PublicUser | AnonymousUser,
|
||||||
db_session: Session,
|
db_session: Session,
|
||||||
):
|
):
|
||||||
# TODO: Improve terrible implementation of this function
|
assignment_task_submission_uuid = assignment_task_submission_object.assignment_task_submission_uuid
|
||||||
# Check if assignment task exists
|
# Check if assignment task exists
|
||||||
statement = select(AssignmentTask).where(
|
statement = select(AssignmentTask).where(
|
||||||
AssignmentTask.assignment_task_uuid == assignment_task_uuid
|
AssignmentTask.assignment_task_uuid == assignment_task_uuid
|
||||||
|
|
@ -722,15 +730,59 @@ async def handle_assignment_task_submission(
|
||||||
detail="Assignment not found",
|
detail="Assignment not found",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if user already submitted the assignment
|
# Check if course exists
|
||||||
statement = select(AssignmentTaskSubmission).where(
|
statement = select(Course).where(Course.id == assignment.course_id)
|
||||||
AssignmentTaskSubmission.assignment_task_id == assignment_task.id,
|
course = db_session.exec(statement).first()
|
||||||
AssignmentTaskSubmission.user_id == current_user.id,
|
|
||||||
)
|
|
||||||
assignment_task_submission = db_session.exec(statement).first()
|
|
||||||
|
|
||||||
# Update Task submission if it exists
|
if not course:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="Course not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if user has instructor/admin permissions
|
||||||
|
is_instructor = await authorization_verify_based_on_roles(request, current_user.id, "update", course.course_uuid, db_session)
|
||||||
|
|
||||||
|
# For regular users, ensure they can only submit their own work
|
||||||
|
if not is_instructor:
|
||||||
|
# Check if user is enrolled in the course
|
||||||
|
if not await authorization_verify_based_on_roles(request, current_user.id, "read", course.course_uuid, db_session):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="You must be enrolled in this course to submit assignments"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Regular users cannot update grades - only check if actual values are being set
|
||||||
|
if (assignment_task_submission_object.grade is not None and assignment_task_submission_object.grade != 0) or \
|
||||||
|
(assignment_task_submission_object.task_submission_grade_feedback is not None and assignment_task_submission_object.task_submission_grade_feedback != ""):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="You do not have permission to update grades"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only need read permission for submissions
|
||||||
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||||
|
else:
|
||||||
|
# Instructors/admins need update permission to grade
|
||||||
|
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
||||||
|
|
||||||
|
# Try to find existing submission if UUID is provided
|
||||||
|
assignment_task_submission = None
|
||||||
|
if assignment_task_submission_uuid:
|
||||||
|
statement = select(AssignmentTaskSubmission).where(
|
||||||
|
AssignmentTaskSubmission.assignment_task_submission_uuid == assignment_task_submission_uuid
|
||||||
|
)
|
||||||
|
assignment_task_submission = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
# If submission exists, update it
|
||||||
if assignment_task_submission:
|
if assignment_task_submission:
|
||||||
|
# For regular users, ensure they can only update their own submissions
|
||||||
|
if not is_instructor and assignment_task_submission.user_id != current_user.id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail="You can only update your own submissions"
|
||||||
|
)
|
||||||
|
|
||||||
# Update only the fields that were passed in
|
# Update only the fields that were passed in
|
||||||
for var, value in vars(assignment_task_submission_object).items():
|
for var, value in vars(assignment_task_submission_object).items():
|
||||||
if value is not None:
|
if value is not None:
|
||||||
|
|
@ -742,9 +794,6 @@ async def handle_assignment_task_submission(
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
db_session.refresh(assignment_task_submission)
|
db_session.refresh(assignment_task_submission)
|
||||||
|
|
||||||
# return assignment task submission read
|
|
||||||
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Create new Task submission
|
# Create new Task submission
|
||||||
current_time = str(datetime.now())
|
current_time = str(datetime.now())
|
||||||
|
|
@ -753,10 +802,10 @@ async def handle_assignment_task_submission(
|
||||||
model_data = assignment_task_submission_object.model_dump()
|
model_data = assignment_task_submission_object.model_dump()
|
||||||
|
|
||||||
assignment_task_submission = AssignmentTaskSubmission(
|
assignment_task_submission = AssignmentTaskSubmission(
|
||||||
assignment_task_submission_uuid=f"assignmenttasksubmission_{uuid4()}",
|
assignment_task_submission_uuid=assignment_task_submission_uuid or f"assignmenttasksubmission_{uuid4()}",
|
||||||
task_submission=model_data["task_submission"],
|
task_submission=model_data["task_submission"],
|
||||||
grade=model_data["grade"],
|
grade=0, # Always start with 0 for new submissions
|
||||||
task_submission_grade_feedback=model_data["task_submission_grade_feedback"],
|
task_submission_grade_feedback="", # Start with empty feedback
|
||||||
assignment_task_id=int(assignment_task.id), # type: ignore
|
assignment_task_id=int(assignment_task.id), # type: ignore
|
||||||
assignment_type=assignment_task.assignment_type,
|
assignment_type=assignment_task.assignment_type,
|
||||||
activity_id=assignment.activity_id,
|
activity_id=assignment.activity_id,
|
||||||
|
|
@ -770,9 +819,10 @@ async def handle_assignment_task_submission(
|
||||||
# Insert Assignment Task Submission in DB
|
# Insert Assignment Task Submission in DB
|
||||||
db_session.add(assignment_task_submission)
|
db_session.add(assignment_task_submission)
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
db_session.refresh(assignment_task_submission)
|
||||||
|
|
||||||
# return assignment task submission read
|
# return assignment task submission read
|
||||||
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
||||||
|
|
||||||
|
|
||||||
async def read_user_assignment_task_submissions(
|
async def read_user_assignment_task_submissions(
|
||||||
|
|
@ -1096,7 +1146,7 @@ async def create_assignment_submission(
|
||||||
)
|
)
|
||||||
|
|
||||||
# RBAC check
|
# RBAC check
|
||||||
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||||
|
|
||||||
# Create Assignment User Submission
|
# Create Assignment User Submission
|
||||||
assignment_user_submission = AssignmentUserSubmission(
|
assignment_user_submission = AssignmentUserSubmission(
|
||||||
|
|
|
||||||
88
apps/api/uv.lock
generated
88
apps/api/uv.lock
generated
|
|
@ -403,6 +403,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 },
|
{ url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "executing"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "faker"
|
name = "faker"
|
||||||
version = "36.1.1"
|
version = "36.1.1"
|
||||||
|
|
@ -1003,6 +1012,7 @@ dependencies = [
|
||||||
{ name = "langchain-community", version = "0.2.19", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" },
|
{ name = "langchain-community", version = "0.2.19", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" },
|
||||||
{ name = "langchain-openai", version = "0.1.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" },
|
{ name = "langchain-openai", version = "0.1.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12.4'" },
|
||||||
{ name = "langchain-openai", version = "0.1.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" },
|
{ name = "langchain-openai", version = "0.1.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12.4'" },
|
||||||
|
{ name = "logfire", extra = ["sqlalchemy"] },
|
||||||
{ name = "openai" },
|
{ name = "openai" },
|
||||||
{ name = "passlib" },
|
{ name = "passlib" },
|
||||||
{ name = "psycopg2-binary" },
|
{ name = "psycopg2-binary" },
|
||||||
|
|
@ -1015,7 +1025,6 @@ dependencies = [
|
||||||
{ name = "redis" },
|
{ name = "redis" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "resend" },
|
{ name = "resend" },
|
||||||
{ name = "sentry-sdk", extra = ["fastapi"] },
|
|
||||||
{ name = "sqlalchemy-utils" },
|
{ name = "sqlalchemy-utils" },
|
||||||
{ name = "sqlmodel" },
|
{ name = "sqlmodel" },
|
||||||
{ name = "stripe" },
|
{ name = "stripe" },
|
||||||
|
|
@ -1038,6 +1047,7 @@ requires-dist = [
|
||||||
{ name = "langchain", specifier = ">=0.1.7" },
|
{ name = "langchain", specifier = ">=0.1.7" },
|
||||||
{ name = "langchain-community", specifier = ">=0.0.20" },
|
{ name = "langchain-community", specifier = ">=0.0.20" },
|
||||||
{ name = "langchain-openai", specifier = ">=0.0.6" },
|
{ name = "langchain-openai", specifier = ">=0.0.6" },
|
||||||
|
{ name = "logfire", extras = ["sqlalchemy"], specifier = ">=3.8.0" },
|
||||||
{ name = "openai", specifier = ">=1.50.2" },
|
{ name = "openai", specifier = ">=1.50.2" },
|
||||||
{ name = "passlib", specifier = ">=1.7.4" },
|
{ name = "passlib", specifier = ">=1.7.4" },
|
||||||
{ name = "psycopg2-binary", specifier = ">=2.9.9" },
|
{ name = "psycopg2-binary", specifier = ">=2.9.9" },
|
||||||
|
|
@ -1050,7 +1060,6 @@ requires-dist = [
|
||||||
{ name = "redis", specifier = ">=5.0.7" },
|
{ name = "redis", specifier = ">=5.0.7" },
|
||||||
{ name = "requests", specifier = ">=2.32.3" },
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
{ name = "resend", specifier = ">=2.4.0" },
|
{ name = "resend", specifier = ">=2.4.0" },
|
||||||
{ name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.13.0" },
|
|
||||||
{ name = "sqlalchemy-utils", specifier = ">=0.41.2" },
|
{ name = "sqlalchemy-utils", specifier = ">=0.41.2" },
|
||||||
{ name = "sqlmodel", specifier = ">=0.0.19" },
|
{ name = "sqlmodel", specifier = ">=0.0.19" },
|
||||||
{ name = "stripe", specifier = ">=11.1.1" },
|
{ name = "stripe", specifier = ">=11.1.1" },
|
||||||
|
|
@ -1059,6 +1068,29 @@ requires-dist = [
|
||||||
{ name = "uvicorn", specifier = "==0.30.1" },
|
{ name = "uvicorn", specifier = "==0.30.1" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "logfire"
|
||||||
|
version = "3.8.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "executing" },
|
||||||
|
{ name = "opentelemetry-exporter-otlp-proto-http" },
|
||||||
|
{ name = "opentelemetry-instrumentation" },
|
||||||
|
{ name = "opentelemetry-sdk" },
|
||||||
|
{ name = "protobuf" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/7c/ccd2aa47da9154788f0846864481b194695ca38db486500ae207b2c9f995/logfire-3.8.0.tar.gz", hash = "sha256:dc64745641d077e9411836c0d17af08f4fcef2737545dc43b67e51b20679e88b", size = 292729 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/d2/5644fab9b42c9f6de2dfe202666e125349d08d963d2f788aa44780d4da71/logfire-3.8.0-py3-none-any.whl", hash = "sha256:a2cda4d7bfb3a3a21bf99aa5c94bde6edff405ffa312b4dcbf8b99a8d52bf32e", size = 186976 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
sqlalchemy = [
|
||||||
|
{ name = "opentelemetry-instrumentation-sqlalchemy" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mako"
|
name = "mako"
|
||||||
version = "1.3.9"
|
version = "1.3.9"
|
||||||
|
|
@ -1304,6 +1336,24 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/35/d9f63fd84c2ed8dbd407bcbb933db4ed6e1b08e7fbdaca080b9ac309b927/opentelemetry_exporter_otlp_proto_grpc-1.30.0-py3-none-any.whl", hash = "sha256:2906bcae3d80acc54fd1ffcb9e44d324e8631058b502ebe4643ca71d1ff30830", size = 18550 },
|
{ url = "https://files.pythonhosted.org/packages/5e/35/d9f63fd84c2ed8dbd407bcbb933db4ed6e1b08e7fbdaca080b9ac309b927/opentelemetry_exporter_otlp_proto_grpc-1.30.0-py3-none-any.whl", hash = "sha256:2906bcae3d80acc54fd1ffcb9e44d324e8631058b502ebe4643ca71d1ff30830", size = 18550 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opentelemetry-exporter-otlp-proto-http"
|
||||||
|
version = "1.30.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "deprecated" },
|
||||||
|
{ name = "googleapis-common-protos" },
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "opentelemetry-exporter-otlp-proto-common" },
|
||||||
|
{ name = "opentelemetry-proto" },
|
||||||
|
{ name = "opentelemetry-sdk" },
|
||||||
|
{ name = "requests" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/04/f9/abb9191d536e6a2e2b7903f8053bf859a76bf784e3ca19a5749550ef19e4/opentelemetry_exporter_otlp_proto_http-1.30.0.tar.gz", hash = "sha256:c3ae75d4181b1e34a60662a6814d0b94dd33b628bee5588a878bed92cee6abdc", size = 15073 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/3c/cdf34bc459613f2275aff9b258f35acdc4c4938dad161d17437de5d4c034/opentelemetry_exporter_otlp_proto_http-1.30.0-py3-none-any.whl", hash = "sha256:9578e790e579931c5ffd50f1e6975cbdefb6a0a0a5dea127a6ae87df10e0a589", size = 17245 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opentelemetry-instrumentation"
|
name = "opentelemetry-instrumentation"
|
||||||
version = "0.51b0"
|
version = "0.51b0"
|
||||||
|
|
@ -1351,6 +1401,22 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/55/1c/ec2d816b78edf2404d7b3df6d09eefb690b70bfd191b7da06f76634f1bdc/opentelemetry_instrumentation_fastapi-0.51b0-py3-none-any.whl", hash = "sha256:10513bbc11a1188adb9c1d2c520695f7a8f2b5f4de14e8162098035901cd6493", size = 12117 },
|
{ url = "https://files.pythonhosted.org/packages/55/1c/ec2d816b78edf2404d7b3df6d09eefb690b70bfd191b7da06f76634f1bdc/opentelemetry_instrumentation_fastapi-0.51b0-py3-none-any.whl", hash = "sha256:10513bbc11a1188adb9c1d2c520695f7a8f2b5f4de14e8162098035901cd6493", size = 12117 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opentelemetry-instrumentation-sqlalchemy"
|
||||||
|
version = "0.51b0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "opentelemetry-instrumentation" },
|
||||||
|
{ name = "opentelemetry-semantic-conventions" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "wrapt" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ab/b2/970b1b46576b663bba64503486afe266c064c2bfd1862876420714ce29d9/opentelemetry_instrumentation_sqlalchemy-0.51b0.tar.gz", hash = "sha256:dbfe95b69006017f903dda194606be458d54789e6b3419d37161fb8861bb98a5", size = 14582 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/d4/b68c3b3388dd5107f3ed532747e112249c152ba44af71a1f96673d66e3ee/opentelemetry_instrumentation_sqlalchemy-0.51b0-py3-none-any.whl", hash = "sha256:5ff4816228b496aef1511149e2b17a25e0faacec4d5eb65bf18a9964af40f1af", size = 14132 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opentelemetry-proto"
|
name = "opentelemetry-proto"
|
||||||
version = "1.30.0"
|
version = "1.30.0"
|
||||||
|
|
@ -1816,24 +1882,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/ac/e7dc469e49048dc57f62e0c555d2ee3117fa30813d2a1a2962cce3a2a82a/s3transfer-0.11.2-py3-none-any.whl", hash = "sha256:be6ecb39fadd986ef1701097771f87e4d2f821f27f6071c872143884d2950fbc", size = 84151 },
|
{ url = "https://files.pythonhosted.org/packages/1b/ac/e7dc469e49048dc57f62e0c555d2ee3117fa30813d2a1a2962cce3a2a82a/s3transfer-0.11.2-py3-none-any.whl", hash = "sha256:be6ecb39fadd986ef1701097771f87e4d2f821f27f6071c872143884d2950fbc", size = 84151 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sentry-sdk"
|
|
||||||
version = "2.22.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "certifi" },
|
|
||||||
{ name = "urllib3" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/81/b6/662988ecd2345bf6c3a5c306a9a3590852742eff91d0a78a143398b816f3/sentry_sdk-2.22.0.tar.gz", hash = "sha256:b4bf43bb38f547c84b2eadcefbe389b36ef75f3f38253d7a74d6b928c07ae944", size = 303539 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/12/7f/0e4459173e9671ba5f75a48dda2442bcc48a12c79e54e5789381c8c6a9bc/sentry_sdk-2.22.0-py2.py3-none-any.whl", hash = "sha256:3d791d631a6c97aad4da7074081a57073126c69487560c6f8bffcf586461de66", size = 325815 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.optional-dependencies]
|
|
||||||
fastapi = [
|
|
||||||
{ name = "fastapi" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shellingham"
|
name = "shellingham"
|
||||||
version = "1.5.4"
|
version = "1.5.4"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import toast from 'react-hot-toast';
|
||||||
|
|
||||||
type FileSchema = {
|
type FileSchema = {
|
||||||
fileUUID: string;
|
fileUUID: string;
|
||||||
|
assignment_task_submission_uuid?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TaskFileObjectProps = {
|
type TaskFileObjectProps = {
|
||||||
|
|
@ -64,13 +65,13 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
||||||
// wait for 1 second to show loading animation
|
// wait for 1 second to show loading animation
|
||||||
await new Promise((r) => setTimeout(r, 1500))
|
await new Promise((r) => setTimeout(r, 1500))
|
||||||
if (res.success === false) {
|
if (res.success === false) {
|
||||||
|
|
||||||
setError(res.data.detail)
|
setError(res.data.detail)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
} else {
|
} else {
|
||||||
assignmentTaskStateHook({ type: 'reload' })
|
assignmentTaskStateHook({ type: 'reload' })
|
||||||
setUserSubmissions({
|
setUserSubmissions({
|
||||||
fileUUID: res.data.file_uuid,
|
fileUUID: res.data.file_uuid,
|
||||||
|
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||||
})
|
})
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
setError('')
|
setError('')
|
||||||
|
|
@ -86,8 +87,14 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
||||||
if (assignmentTaskUUID) {
|
if (assignmentTaskUUID) {
|
||||||
const res = await getAssignmentTaskSubmissionsMe(assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
const res = await getAssignmentTaskSubmissionsMe(assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
setUserSubmissions(res.data.task_submission);
|
setUserSubmissions({
|
||||||
setInitialUserSubmissions(res.data.task_submission);
|
...res.data.task_submission,
|
||||||
|
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||||
|
});
|
||||||
|
setInitialUserSubmissions({
|
||||||
|
...res.data.task_submission,
|
||||||
|
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,6 +108,7 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
||||||
|
|
||||||
// Save the quiz to the server
|
// Save the quiz to the server
|
||||||
const values = {
|
const values = {
|
||||||
|
assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid,
|
||||||
task_submission: userSubmissions,
|
task_submission: userSubmissions,
|
||||||
grade: 0,
|
grade: 0,
|
||||||
task_submission_grade_feedback: '',
|
task_submission_grade_feedback: '',
|
||||||
|
|
@ -156,9 +164,15 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
||||||
if (assignmentTaskUUID && user_id) {
|
if (assignmentTaskUUID && user_id) {
|
||||||
const res = await getAssignmentTaskSubmissionsUser(assignmentTaskUUID, user_id, assignment.assignment_object.assignment_uuid, access_token);
|
const res = await getAssignmentTaskSubmissionsUser(assignmentTaskUUID, user_id, assignment.assignment_object.assignment_uuid, access_token);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
setUserSubmissions(res.data.task_submission);
|
setUserSubmissions({
|
||||||
|
...res.data.task_submission,
|
||||||
|
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||||
|
});
|
||||||
setUserSubmissionObject(res.data);
|
setUserSubmissionObject(res.data);
|
||||||
setInitialUserSubmissions(res.data.task_submission);
|
setInitialUserSubmissions({
|
||||||
|
...res.data.task_submission,
|
||||||
|
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -173,6 +187,7 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
||||||
|
|
||||||
// Save the grade to the server
|
// Save the grade to the server
|
||||||
const values = {
|
const values = {
|
||||||
|
assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid,
|
||||||
task_submission: userSubmissions,
|
task_submission: userSubmissions,
|
||||||
grade: grade,
|
grade: grade,
|
||||||
task_submission_grade_feedback: 'Graded by teacher : @' + session.data.user.username,
|
task_submission_grade_feedback: 'Graded by teacher : @' + session.data.user.username,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ type QuizSubmitSchema = {
|
||||||
optionUUID: string;
|
optionUUID: string;
|
||||||
answer: boolean
|
answer: boolean
|
||||||
}[];
|
}[];
|
||||||
|
assignment_task_submission_uuid?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TaskQuizObjectProps = {
|
type TaskQuizObjectProps = {
|
||||||
|
|
@ -175,8 +176,14 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
|
||||||
if (assignmentTaskUUID) {
|
if (assignmentTaskUUID) {
|
||||||
const res = await getAssignmentTaskSubmissionsMe(assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
const res = await getAssignmentTaskSubmissionsMe(assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
setUserSubmissions(res.data.task_submission);
|
setUserSubmissions({
|
||||||
setInitialUserSubmissions(res.data.task_submission);
|
...res.data.task_submission,
|
||||||
|
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||||
|
});
|
||||||
|
setInitialUserSubmissions({
|
||||||
|
...res.data.task_submission,
|
||||||
|
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -242,9 +249,15 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
|
||||||
if (assignmentTaskUUID && user_id) {
|
if (assignmentTaskUUID && user_id) {
|
||||||
const res = await getAssignmentTaskSubmissionsUser(assignmentTaskUUID, user_id, assignment.assignment_object.assignment_uuid, access_token);
|
const res = await getAssignmentTaskSubmissionsUser(assignmentTaskUUID, user_id, assignment.assignment_object.assignment_uuid, access_token);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
setUserSubmissions(res.data.task_submission);
|
setUserSubmissions({
|
||||||
|
...res.data.task_submission,
|
||||||
|
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||||
|
});
|
||||||
setUserSubmissionObject(res.data);
|
setUserSubmissionObject(res.data);
|
||||||
setInitialUserSubmissions(res.data.task_submission);
|
setInitialUserSubmissions({
|
||||||
|
...res.data.task_submission,
|
||||||
|
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -271,6 +284,7 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
|
||||||
|
|
||||||
// Save the grade to the server
|
// Save the grade to the server
|
||||||
const values = {
|
const values = {
|
||||||
|
assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid,
|
||||||
task_submission: userSubmissions,
|
task_submission: userSubmissions,
|
||||||
grade: finalGrade,
|
grade: finalGrade,
|
||||||
task_submission_grade_feedback: 'Auto graded by system',
|
task_submission_grade_feedback: 'Auto graded by system',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
import BreadCrumbs from '@components/Dashboard/Misc/BreadCrumbs'
|
import BreadCrumbs from '@components/Dashboard/Misc/BreadCrumbs'
|
||||||
import { BookOpen, BookX, EllipsisVertical, Eye, Layers2, Monitor, UserRoundPen } from 'lucide-react'
|
import { ArrowRight, BookOpen, BookX, EllipsisVertical, Eye, Layers2, Monitor, Pencil, UserRoundPen } from 'lucide-react'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { AssignmentProvider, useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
|
import { AssignmentProvider, useAssignments } from '@components/Contexts/Assignments/AssignmentContext';
|
||||||
import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip';
|
import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip';
|
||||||
|
|
@ -16,6 +16,7 @@ import { updateActivity } from '@services/courses/activities';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import AssignmentEditorSubPage from './subpages/AssignmentEditorSubPage';
|
import AssignmentEditorSubPage from './subpages/AssignmentEditorSubPage';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
import EditAssignmentModal from '@components/Objects/Modals/Activities/Assignments/EditAssignmentModal';
|
||||||
const AssignmentSubmissionsSubPage = dynamic(() => import('./subpages/AssignmentSubmissionsSubPage'))
|
const AssignmentSubmissionsSubPage = dynamic(() => import('./subpages/AssignmentSubmissionsSubPage'))
|
||||||
|
|
||||||
function AssignmentEdit() {
|
function AssignmentEdit() {
|
||||||
|
|
@ -46,7 +47,9 @@ function AssignmentEdit() {
|
||||||
<div className="pl-10 mr-10 tracking-tighter">
|
<div className="pl-10 mr-10 tracking-tighter">
|
||||||
<BrdCmpx />
|
<BrdCmpx />
|
||||||
<div className="w-100 flex justify-between">
|
<div className="w-100 flex justify-between">
|
||||||
<div className="flex font-bold text-2xl">Assignment Tools </div>
|
<div className="flex font-bold text-2xl">
|
||||||
|
<AssignmentTitle />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col justify-center antialiased'>
|
<div className='flex flex-col justify-center antialiased'>
|
||||||
|
|
@ -106,6 +109,7 @@ function PublishingState() {
|
||||||
const assignment = useAssignments() as any;
|
const assignment = useAssignments() as any;
|
||||||
const session = useLHSession() as any;
|
const session = useLHSession() as any;
|
||||||
const access_token = session?.data?.tokens?.access_token;
|
const access_token = session?.data?.tokens?.access_token;
|
||||||
|
const [isEditModalOpen, setIsEditModalOpen] = React.useState(false);
|
||||||
|
|
||||||
async function updateAssignmentPublishState(assignmentUUID: string) {
|
async function updateAssignmentPublishState(assignmentUUID: string) {
|
||||||
const res = await updateAssignment({ published: !assignment?.assignment_object?.published }, assignmentUUID, access_token)
|
const res = await updateAssignment({ published: !assignment?.assignment_object?.published }, assignmentUUID, access_token)
|
||||||
|
|
@ -125,50 +129,83 @@ function PublishingState() {
|
||||||
}, [assignment])
|
}, [assignment])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex mx-auto mt-5 items-center space-x-4'>
|
<>
|
||||||
<div className={`flex text-xs rounded-full px-3.5 py-2 mx-auto font-bold outline outline-1 ${!assignment?.assignment_object?.published ? 'outline-gray-300 bg-gray-200/60' : 'outline-green-300 bg-green-200/60'}`}>
|
<div className='flex mx-auto mt-5 items-center space-x-4'>
|
||||||
{assignment?.assignment_object?.published ? 'Published' : 'Unpublished'}
|
<div className={`flex text-xs rounded-full px-3.5 py-2 mx-auto font-bold outline outline-1 ${!assignment?.assignment_object?.published ? 'outline-gray-300 bg-gray-200/60' : 'outline-green-300 bg-green-200/60'}`}>
|
||||||
</div>
|
{assignment?.assignment_object?.published ? 'Published' : 'Unpublished'}
|
||||||
<div><EllipsisVertical className='text-gray-500' size={13} /></div>
|
|
||||||
|
|
||||||
<ToolTip
|
|
||||||
side='left'
|
|
||||||
slateBlack
|
|
||||||
sideOffset={10}
|
|
||||||
content="Preview the Assignment as a student" >
|
|
||||||
<Link
|
|
||||||
target='_blank'
|
|
||||||
href={`/course/${assignment?.course_object?.course_uuid.replace('course_', '')}/activity/${assignment?.activity_object?.activity_uuid.replace('activity_', '')}`}
|
|
||||||
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-to-bl text-cyan-800 font-medium from-sky-400/50 to-cyan-200/80 border border-cyan-600/10 shadow-cyan-900/10 shadow-lg'>
|
|
||||||
<Eye size={18} />
|
|
||||||
<p className=' text-sm font-bold'>Preview</p>
|
|
||||||
</Link>
|
|
||||||
</ToolTip>
|
|
||||||
{assignment?.assignment_object?.published && <ToolTip
|
|
||||||
side='left'
|
|
||||||
slateBlack
|
|
||||||
sideOffset={10}
|
|
||||||
content="Make your Assignment unavailable for students" >
|
|
||||||
<div
|
|
||||||
onClick={() => updateAssignmentPublishState(assignment?.assignment_object?.assignment_uuid)}
|
|
||||||
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-to-bl text-gray-800 font-medium from-gray-400/50 to-gray-200/80 border border-gray-600/10 shadow-gray-900/10 shadow-lg'>
|
|
||||||
<BookX size={18} />
|
|
||||||
<p className='text-sm font-bold'>Unpublish</p>
|
|
||||||
</div>
|
</div>
|
||||||
</ToolTip>}
|
<div><EllipsisVertical className='text-gray-500' size={13} /></div>
|
||||||
{!assignment?.assignment_object?.published &&
|
|
||||||
<ToolTip
|
<ToolTip
|
||||||
side='left'
|
side='left'
|
||||||
slateBlack
|
slateBlack
|
||||||
sideOffset={10}
|
sideOffset={10}
|
||||||
content="Make your Assignment public and available for students" >
|
content="Edit Assignment Details">
|
||||||
|
<div
|
||||||
|
onClick={() => setIsEditModalOpen(true)}
|
||||||
|
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-to-bl text-blue-800 font-medium from-blue-400/50 to-blue-200/80 border border-blue-600/10 shadow-blue-900/10 shadow-lg'>
|
||||||
|
<Pencil size={18} />
|
||||||
|
<p className='text-sm font-bold'>Edit</p>
|
||||||
|
</div>
|
||||||
|
</ToolTip>
|
||||||
|
|
||||||
|
<ToolTip
|
||||||
|
side='left'
|
||||||
|
slateBlack
|
||||||
|
sideOffset={10}
|
||||||
|
content="Preview the Assignment as a student" >
|
||||||
|
<Link
|
||||||
|
target='_blank'
|
||||||
|
href={`/course/${assignment?.course_object?.course_uuid.replace('course_', '')}/activity/${assignment?.activity_object?.activity_uuid.replace('activity_', '')}`}
|
||||||
|
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-to-bl text-cyan-800 font-medium from-sky-400/50 to-cyan-200/80 border border-cyan-600/10 shadow-cyan-900/10 shadow-lg'>
|
||||||
|
<Eye size={18} />
|
||||||
|
<p className=' text-sm font-bold'>Preview</p>
|
||||||
|
</Link>
|
||||||
|
</ToolTip>
|
||||||
|
{assignment?.assignment_object?.published && <ToolTip
|
||||||
|
side='left'
|
||||||
|
slateBlack
|
||||||
|
sideOffset={10}
|
||||||
|
content="Make your Assignment unavailable for students" >
|
||||||
<div
|
<div
|
||||||
onClick={() => updateAssignmentPublishState(assignment?.assignment_object?.assignment_uuid)}
|
onClick={() => updateAssignmentPublishState(assignment?.assignment_object?.assignment_uuid)}
|
||||||
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-to-bl text-green-800 font-medium from-green-400/50 to-lime-200/80 border border-green-600/10 shadow-green-900/10 shadow-lg'>
|
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-to-bl text-gray-800 font-medium from-gray-400/50 to-gray-200/80 border border-gray-600/10 shadow-gray-900/10 shadow-lg'>
|
||||||
<BookOpen size={18} />
|
<BookX size={18} />
|
||||||
<p className=' text-sm font-bold'>Publish</p>
|
<p className='text-sm font-bold'>Unpublish</p>
|
||||||
</div>
|
</div>
|
||||||
</ToolTip>}
|
</ToolTip>}
|
||||||
</div>
|
{!assignment?.assignment_object?.published &&
|
||||||
|
<ToolTip
|
||||||
|
side='left'
|
||||||
|
slateBlack
|
||||||
|
sideOffset={10}
|
||||||
|
content="Make your Assignment public and available for students" >
|
||||||
|
<div
|
||||||
|
onClick={() => updateAssignmentPublishState(assignment?.assignment_object?.assignment_uuid)}
|
||||||
|
className='flex px-3 py-2 cursor-pointer rounded-md space-x-2 items-center bg-linear-to-bl text-green-800 font-medium from-green-400/50 to-lime-200/80 border border-green-600/10 shadow-green-900/10 shadow-lg'>
|
||||||
|
<BookOpen size={18} />
|
||||||
|
<p className=' text-sm font-bold'>Publish</p>
|
||||||
|
</div>
|
||||||
|
</ToolTip>}
|
||||||
|
</div>
|
||||||
|
{isEditModalOpen && (
|
||||||
|
<EditAssignmentModal
|
||||||
|
isOpen={isEditModalOpen}
|
||||||
|
onClose={() => setIsEditModalOpen(false)}
|
||||||
|
assignment={assignment?.assignment_object}
|
||||||
|
accessToken={access_token}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AssignmentTitle() {
|
||||||
|
const assignment = useAssignments() as any;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
Assignment Tools
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,21 +41,27 @@ function AssignmentSubmissionsSubPage({ assignment_uuid }: { assignment_uuid: st
|
||||||
<X size={18} />
|
<X size={18} />
|
||||||
<h3>Late</h3>
|
<h3>Late</h3>
|
||||||
</div>
|
</div>
|
||||||
{renderSubmissions('LATE')}
|
<div className='flex flex-col gap-4'>
|
||||||
|
{renderSubmissions('LATE')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<div className='flex w-fit mx-auto px-3.5 py-1 bg-amber-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
|
<div className='flex w-fit mx-auto px-3.5 py-1 bg-amber-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
|
||||||
<SendHorizonal size={18} />
|
<SendHorizonal size={18} />
|
||||||
<h3>Submitted</h3>
|
<h3>Submitted</h3>
|
||||||
</div>
|
</div>
|
||||||
{renderSubmissions('SUBMITTED')}
|
<div className='flex flex-col gap-4'>
|
||||||
|
{renderSubmissions('SUBMITTED')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<div className='flex w-fit mx-auto px-3.5 py-1 bg-emerald-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
|
<div className='flex w-fit mx-auto px-3.5 py-1 bg-emerald-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
|
||||||
<UserCheck size={18} />
|
<UserCheck size={18} />
|
||||||
<h3>Graded</h3>
|
<h3>Graded</h3>
|
||||||
</div>
|
</div>
|
||||||
{renderSubmissions('GRADED')}
|
<div className='flex flex-col gap-4'>
|
||||||
|
{renderSubmissions('GRADED')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ function EvaluateAssignment({ user_id }: any) {
|
||||||
const assignments = useAssignments() as any;
|
const assignments = useAssignments() as any;
|
||||||
const session = useLHSession() as any;
|
const session = useLHSession() as any;
|
||||||
const org = useOrg() as any;
|
const org = useOrg() as any;
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
async function gradeAssignment() {
|
async function gradeAssignment() {
|
||||||
const res = await putFinalGrade(user_id, assignments?.assignment_object.assignment_uuid, session.data?.tokens?.access_token);
|
const res = await putFinalGrade(user_id, assignments?.assignment_object.assignment_uuid, session.data?.tokens?.access_token);
|
||||||
|
|
@ -92,17 +91,17 @@ function EvaluateAssignment({ user_id }: any) {
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<div className='flex space-x-4 font-semibold items-center justify-between'>
|
<div className='flex space-x-4 font-semibold items-center justify-between'>
|
||||||
<button onClick={rejectAssignment} className='flex space-x-2 px-4 py-2 text-sm bg-rose-600/80 text-white rounded-lg nice-shadow items-center'>
|
<button onClick={rejectAssignment} className='flex space-x-2 px-4 py-2 text-sm bg-rose-600/80 text-white rounded-lg nice-shadow items-center cursor-pointer'>
|
||||||
<X size={18} />
|
<X size={18} />
|
||||||
<span>Reject Assignment</span>
|
<span>Reject Assignment</span>
|
||||||
</button>
|
</button>
|
||||||
<div className='flex space-x-3 items-center'>
|
<div className='flex space-x-3 items-center'>
|
||||||
<button onClick={gradeAssignment} className='flex space-x-2 px-4 py-2 text-sm bg-violet-600/80 text-white rounded-lg nice-shadow items-center'>
|
<button onClick={gradeAssignment} className='flex space-x-2 px-4 py-2 text-sm bg-violet-600/80 text-white rounded-lg nice-shadow items-center cursor-pointer'>
|
||||||
<BookOpenCheck size={18} />
|
<BookOpenCheck size={18} />
|
||||||
<span>Set final grade</span>
|
<span>Set final grade</span>
|
||||||
</button>
|
</button>
|
||||||
<MoveRight className='text-gray-400' size={18} />
|
<MoveRight className='text-gray-400' size={18} />
|
||||||
<button onClick={markActivityAsDone} className='flex space-x-2 px-4 py-2 text-sm bg-teal-600/80 text-white rounded-lg nice-shadow items-center'>
|
<button onClick={markActivityAsDone} className='flex space-x-2 px-4 py-2 text-sm bg-teal-600/80 text-white rounded-lg nice-shadow items-center cursor-pointer'>
|
||||||
<Check size={18} />
|
<Check size={18} />
|
||||||
<span>Mark Activity as Done for User</span>
|
<span>Mark Activity as Done for User</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,22 @@ function AssignmentStudentActivity() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='w-full rounded-full bg-slate-500/5 nice-shadow h-[2px]'></div>
|
|
||||||
|
|
||||||
|
{assignments?.assignment_object?.description && (
|
||||||
|
<div className='flex flex-col space-y-2 p-4 md:p-6 bg-slate-100/30 rounded-md nice-shadow'>
|
||||||
|
<div className='flex flex-col space-y-3'>
|
||||||
|
<div className='flex items-center gap-2 text-slate-700'>
|
||||||
|
<Info size={16} className="text-slate-500" />
|
||||||
|
<h3 className='text-sm font-semibold'>Assignment Description</h3>
|
||||||
|
</div>
|
||||||
|
<div className='pl-6'>
|
||||||
|
<p className='text-sm leading-relaxed text-slate-600'>{assignments.assignment_object.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
{assignments && assignments?.assignment_tasks?.sort((a: any, b: any) => a.id - b.id).map((task: any, index: number) => {
|
{assignments && assignments?.assignment_tasks?.sort((a: any, b: any) => a.id - b.id).map((task: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { updateAssignment } from '@services/courses/assignments';
|
||||||
|
import { mutate } from 'swr';
|
||||||
|
import { getAPIUrl } from '@services/config/config';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import FormLayout, {
|
||||||
|
FormField,
|
||||||
|
FormLabelAndMessage,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
Flex,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage
|
||||||
|
} from '@components/Objects/StyledElements/Form/Form';
|
||||||
|
import * as Form from '@radix-ui/react-form';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import Modal from '@components/Objects/StyledElements/Modal/Modal';
|
||||||
|
|
||||||
|
interface Assignment {
|
||||||
|
assignment_uuid: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
due_date?: string;
|
||||||
|
grading_type?: 'ALPHABET' | 'NUMERIC' | 'PERCENTAGE';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditAssignmentFormProps {
|
||||||
|
onClose: () => void;
|
||||||
|
assignment: Assignment;
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditAssignmentModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
assignment: Assignment;
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditAssignmentForm: React.FC<EditAssignmentFormProps> = ({
|
||||||
|
onClose,
|
||||||
|
assignment,
|
||||||
|
accessToken
|
||||||
|
}) => {
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
title: assignment.title || '',
|
||||||
|
description: assignment.description || '',
|
||||||
|
due_date: assignment.due_date || '',
|
||||||
|
grading_type: assignment.grading_type || 'ALPHABET'
|
||||||
|
},
|
||||||
|
enableReinitialize: true,
|
||||||
|
onSubmit: async (values, { setSubmitting }) => {
|
||||||
|
const toast_loading = toast.loading('Updating assignment...');
|
||||||
|
try {
|
||||||
|
const res = await updateAssignment(values, assignment.assignment_uuid, accessToken);
|
||||||
|
if (res.success) {
|
||||||
|
mutate(`${getAPIUrl()}assignments/${assignment.assignment_uuid}`);
|
||||||
|
toast.success('Assignment updated successfully');
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to update assignment');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('An error occurred while updating the assignment');
|
||||||
|
} finally {
|
||||||
|
toast.dismiss(toast_loading);
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormLayout onSubmit={formik.handleSubmit}>
|
||||||
|
<FormField name="title">
|
||||||
|
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||||
|
<FormLabel>Assignment Title</FormLabel>
|
||||||
|
<FormMessage match="valueMissing">
|
||||||
|
Please provide a name for your assignment
|
||||||
|
</FormMessage>
|
||||||
|
</Flex>
|
||||||
|
<Form.Control asChild>
|
||||||
|
<Input
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.title}
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField name="description">
|
||||||
|
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||||
|
<FormLabel>Assignment Description</FormLabel>
|
||||||
|
<FormMessage match="valueMissing">
|
||||||
|
Please provide a description for your assignment
|
||||||
|
</FormMessage>
|
||||||
|
</Flex>
|
||||||
|
<Form.Control asChild>
|
||||||
|
<Textarea
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.description}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField name="due_date">
|
||||||
|
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||||
|
<FormLabel>Due Date</FormLabel>
|
||||||
|
<FormMessage match="valueMissing">
|
||||||
|
Please provide a due date for your assignment
|
||||||
|
</FormMessage>
|
||||||
|
</Flex>
|
||||||
|
<Form.Control asChild>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.due_date}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField name="grading_type">
|
||||||
|
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||||
|
<FormLabel>Grading Type</FormLabel>
|
||||||
|
<FormMessage match="valueMissing">
|
||||||
|
Please provide a grading type for your assignment
|
||||||
|
</FormMessage>
|
||||||
|
</Flex>
|
||||||
|
<select
|
||||||
|
id="grading_type"
|
||||||
|
name="grading_type"
|
||||||
|
className='w-full bg-gray-100/40 rounded-lg px-3 py-2 outline outline-1 outline-gray-100'
|
||||||
|
onChange={(e) => formik.setFieldValue('grading_type', e.target.value, true)}
|
||||||
|
value={formik.values.grading_type}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="ALPHABET">Alphabet</option>
|
||||||
|
<option value="NUMERIC">Numeric</option>
|
||||||
|
<option value="PERCENTAGE">Percentage</option>
|
||||||
|
</select>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-3 mt-6">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<Form.Submit asChild>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
className="px-4 py-2 bg-black text-white font-bold rounded-md hover:bg-black/90"
|
||||||
|
>
|
||||||
|
{formik.isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||||
|
</button>
|
||||||
|
</Form.Submit>
|
||||||
|
</div>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditAssignmentModal: React.FC<EditAssignmentModalProps> = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
assignment,
|
||||||
|
accessToken
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isDialogOpen={isOpen}
|
||||||
|
onOpenChange={onClose}
|
||||||
|
minHeight="md"
|
||||||
|
minWidth="lg"
|
||||||
|
dialogContent={
|
||||||
|
<EditAssignmentForm
|
||||||
|
onClose={onClose}
|
||||||
|
assignment={assignment}
|
||||||
|
accessToken={accessToken}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
dialogTitle="Edit Assignment"
|
||||||
|
dialogDescription="Update assignment details"
|
||||||
|
dialogTrigger={null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditAssignmentModal;
|
||||||
|
|
@ -38,7 +38,7 @@ const ConfirmationModal = (params: ModalParams) => {
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<DialogOverlay />
|
<DialogOverlay />
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div className="h-26 flex space-x-4 tracking-tight">
|
<div className="flex space-x-4 tracking-tight">
|
||||||
<div
|
<div
|
||||||
className={`icon p-6 rounded-xl flex items-center align-content-center ${
|
className={`icon p-6 rounded-xl flex items-center align-content-center ${
|
||||||
params.status === 'warning' ? warningColors : infoColors
|
params.status === 'warning' ? warningColors : infoColors
|
||||||
|
|
@ -51,13 +51,13 @@ const ConfirmationModal = (params: ModalParams) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text pt-1 space-x-0 w-auto grow">
|
<div className="text pt-1 space-x-0 w-auto grow">
|
||||||
<div className="text-xl font-bold text-black ">
|
<div className="text-xl font-bold text-black">
|
||||||
{params.dialogTitle}
|
{params.dialogTitle}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-md text-gray-500 w-60 leading-tight">
|
<div className="text-md text-gray-500 leading-tight mt-1">
|
||||||
{params.confirmationMessage}
|
{params.confirmationMessage}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row-reverse pt-2">
|
<div className="flex flex-row-reverse mt-4">
|
||||||
<div
|
<div
|
||||||
id={params.buttonid}
|
id={params.buttonid}
|
||||||
className={`rounded-md text-sm px-3 py-2 font-bold flex justify-center items-center hover:cursor-pointer ${
|
className={`rounded-md text-sm px-3 py-2 font-bold flex justify-center items-center hover:cursor-pointer ${
|
||||||
|
|
@ -126,11 +126,11 @@ const DialogContent = styled(Dialog.Content, {
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
minWidth: '500px',
|
minWidth: '500px',
|
||||||
overflow: 'hidden',
|
maxWidth: '600px',
|
||||||
|
overflow: 'visible',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
maxHeight: '85vh',
|
maxHeight: '85vh',
|
||||||
maxWidth: '600px',
|
padding: '24px',
|
||||||
padding: 11,
|
|
||||||
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||||
'&:focus': { outline: 'none' },
|
'&:focus': { outline: 'none' },
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue