mirror of
https://github.com/TurTaskProject/TurTaskWeb.git
synced 2025-12-18 21:44:07 +01:00
Merge branch 'main' into feature/dashboard
This commit is contained in:
commit
e75453e7c3
0
backend/boards/__init__.py
Normal file
0
backend/boards/__init__.py
Normal file
11
backend/boards/admin.py
Normal file
11
backend/boards/admin.py
Normal file
@ -0,0 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from .models import Board, ListBoard
|
||||
|
||||
@admin.register(Board)
|
||||
class BoardAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'user']
|
||||
|
||||
@admin.register(ListBoard)
|
||||
class ListBoardAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'position', 'board']
|
||||
list_filter = ['board', 'position']
|
||||
9
backend/boards/apps.py
Normal file
9
backend/boards/apps.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BoardsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'boards'
|
||||
|
||||
def ready(self):
|
||||
import boards.signals
|
||||
35
backend/boards/migrations/0001_initial.py
Normal file
35
backend/boards/migrations/0001_initial.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Generated by Django 4.2.6 on 2023-11-19 19:19
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Board',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ListBoard',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('position', models.IntegerField()),
|
||||
('board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boards.board')),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
backend/boards/migrations/__init__.py
Normal file
0
backend/boards/migrations/__init__.py
Normal file
34
backend/boards/models.py
Normal file
34
backend/boards/models.py
Normal file
@ -0,0 +1,34 @@
|
||||
from django.db import models
|
||||
|
||||
from users.models import CustomUser
|
||||
|
||||
class Board(models.Model):
|
||||
"""
|
||||
Kanban board model.
|
||||
|
||||
:param user: The user who owns the board.
|
||||
:param name: The name of the board.
|
||||
:param created_at: The date and time when the board was created.
|
||||
"""
|
||||
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=255)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name}"
|
||||
|
||||
|
||||
class ListBoard(models.Model):
|
||||
"""
|
||||
List inside a Kanban board.
|
||||
|
||||
:param board: The board that the list belongs to.
|
||||
:param name: The name of the list.
|
||||
:param position: The position of the list in Kanban.
|
||||
"""
|
||||
board = models.ForeignKey(Board, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=255)
|
||||
position = models.IntegerField()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name}"
|
||||
14
backend/boards/signals.py
Normal file
14
backend/boards/signals.py
Normal file
@ -0,0 +1,14 @@
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from boards.models import Board, ListBoard
|
||||
from users.models import CustomUser
|
||||
|
||||
@receiver(post_save, sender=CustomUser)
|
||||
def create_default_board(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
board = Board.objects.create(user=instance, name="My Default Board")
|
||||
|
||||
ListBoard.objects.create(board=board, name="Todo", position=1)
|
||||
ListBoard.objects.create(board=board, name="In Progress", position=2)
|
||||
ListBoard.objects.create(board=board, name="Done", position=3)
|
||||
3
backend/boards/tests.py
Normal file
3
backend/boards/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
5
backend/boards/urls.py
Normal file
5
backend/boards/urls.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.urls import path
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
]
|
||||
3
backend/boards/views.py
Normal file
3
backend/boards/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@ -53,6 +53,7 @@ INSTALLED_APPS = [
|
||||
'users',
|
||||
'authentications',
|
||||
'dashboard',
|
||||
'boards',
|
||||
|
||||
'corsheaders',
|
||||
'drf_spectacular',
|
||||
|
||||
@ -28,4 +28,5 @@ urlpatterns = [
|
||||
path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
|
||||
path('api/', include('dashboard.urls')),
|
||||
path('api/', include('boards.urls')),
|
||||
]
|
||||
@ -1,3 +1,29 @@
|
||||
from django.contrib import admin
|
||||
from .models import Tag, Todo, RecurrenceTask, RecurrencePattern, Habit, Subtask
|
||||
|
||||
# Register your models here.
|
||||
@admin.register(Tag)
|
||||
class TagAdmin(admin.ModelAdmin):
|
||||
list_display = ['name']
|
||||
|
||||
@admin.register(Todo)
|
||||
class TodoAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'list_board', 'is_active', 'priority']
|
||||
list_filter = ['list_board', 'is_active', 'priority']
|
||||
|
||||
@admin.register(RecurrenceTask)
|
||||
class RecurrenceTaskAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'list_board', 'rrule', 'is_active']
|
||||
list_filter = ['list_board', 'rrule', 'is_active']
|
||||
|
||||
@admin.register(RecurrencePattern)
|
||||
class RecurrencePatternAdmin(admin.ModelAdmin):
|
||||
list_display = ['recurrence_task', 'recurring_type', 'day_of_week', 'week_of_month', 'day_of_month', 'month_of_year']
|
||||
|
||||
@admin.register(Habit)
|
||||
class HabitAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'streak', 'current_count']
|
||||
|
||||
@admin.register(Subtask)
|
||||
class SubtaskAdmin(admin.ModelAdmin):
|
||||
list_display = ['parent_task', 'description', 'completed']
|
||||
list_filter = ['parent_task', 'completed']
|
||||
|
||||
@ -6,9 +6,9 @@ from rest_framework import viewsets
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from tasks.utils import get_service, generate_recurrence_rule
|
||||
from tasks.models import Todo, RecurrenceTask
|
||||
from tasks.serializers import TodoUpdateSerializer, RecurrenceTaskUpdateSerializer
|
||||
from tasks.utils import get_service
|
||||
from tasks.models import Todo
|
||||
from tasks.serializers import TodoUpdateSerializer
|
||||
|
||||
class GoogleCalendarEventViewset(viewsets.ViewSet):
|
||||
"""Viewset for list or save Google Calendar Events."""
|
||||
@ -50,7 +50,11 @@ class GoogleCalendarEventViewset(viewsets.ViewSet):
|
||||
return events
|
||||
|
||||
def _validate_serializer(self, serializer):
|
||||
"""Validate serializer and return response."""
|
||||
"""
|
||||
Validate serializer and return response.
|
||||
|
||||
:param serializer: The serializer to validate.
|
||||
"""
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response("Validate Successfully", status=200)
|
||||
@ -61,7 +65,6 @@ class GoogleCalendarEventViewset(viewsets.ViewSet):
|
||||
events = self._get_google_events(request)
|
||||
|
||||
responses = []
|
||||
recurrence_task_ids = []
|
||||
for event in events:
|
||||
start_datetime = event.get('start', {}).get('dateTime')
|
||||
end_datetime = event.get('end', {}).get('dateTime')
|
||||
@ -71,25 +74,6 @@ class GoogleCalendarEventViewset(viewsets.ViewSet):
|
||||
event.pop('start')
|
||||
event.pop('end')
|
||||
|
||||
if (event.get('recurringEventId') in recurrence_task_ids):
|
||||
continue
|
||||
|
||||
if (event.get('recurringEventId') is not None):
|
||||
originalStartTime = event.get('originalStartTime', {}).get('dateTime')
|
||||
rrule_text = generate_recurrence_rule(event['start_datetime'], event['end_datetime'], originalStartTime)
|
||||
event['recurrence'] = rrule_text
|
||||
event.pop('originalStartTime')
|
||||
recurrence_task_ids.append(event['recurringEventId'])
|
||||
|
||||
try:
|
||||
task = RecurrenceTask.objects.get(google_calendar_id=event['id'])
|
||||
serializer = RecurrenceTaskUpdateSerializer(instance=task, data=event)
|
||||
except RecurrenceTask.DoesNotExist:
|
||||
serializer = RecurrenceTaskUpdateSerializer(data=event, user=request.user)
|
||||
|
||||
responses.append(self._validate_serializer(serializer))
|
||||
continue
|
||||
|
||||
try:
|
||||
task = Todo.objects.get(google_calendar_id=event['id'])
|
||||
serializer = TodoUpdateSerializer(instance=task, data=event)
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
# Generated by Django 4.2.6 on 2023-11-19 20:15
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boards', '0001_initial'),
|
||||
('tasks', '0014_recurrencetask_completed_todo_completed'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RecurrencePattern',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('recurring_type', models.IntegerField(choices=[(0, 'Daily'), (1, 'Weekly'), (2, 'Monthly'), (3, 'Yearly')])),
|
||||
('max_occurrences', models.IntegerField(default=0)),
|
||||
('day_of_week', models.IntegerField(choices=[(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday')])),
|
||||
('week_of_month', models.IntegerField(choices=[(1, 'First'), (2, 'Second'), (3, 'Third'), (4, 'Fourth'), (5, 'Last')])),
|
||||
('day_of_month', models.IntegerField(default=0)),
|
||||
('month_of_year', models.IntegerField(choices=[(1, 'January'), (2, 'February'), (3, 'March'), (4, 'April'), (5, 'May'), (6, 'June'), (7, 'July'), (8, 'August'), (9, 'September'), (10, 'October'), (11, 'November'), (12, 'December')])),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='transaction',
|
||||
name='user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='UserNotification',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='habit',
|
||||
name='end_event',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='habit',
|
||||
name='google_calendar_id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='habit',
|
||||
name='start_event',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='recurrencetask',
|
||||
name='google_calendar_id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='recurrencetask',
|
||||
name='recurrence_rule',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='habit',
|
||||
name='current_count',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recurrencetask',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recurrencetask',
|
||||
name='is_full_day_event',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recurrencetask',
|
||||
name='list_board',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recurrencetask',
|
||||
name='parent_task',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='tasks.recurrencetask'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recurrencetask',
|
||||
name='rrule',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='todo',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='todo',
|
||||
name='is_full_day_event',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='todo',
|
||||
name='list_board',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Transaction',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recurrencepattern',
|
||||
name='recurrence_task',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.recurrencetask'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.2.6 on 2023-11-19 20:24
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boards', '0001_initial'),
|
||||
('tasks', '0015_recurrencepattern_remove_transaction_user_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='recurrencetask',
|
||||
name='list_board',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='todo',
|
||||
name='list_board',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.2.6 on 2023-11-19 20:27
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('boards', '0001_initial'),
|
||||
('tasks', '0016_alter_recurrencetask_list_board_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='recurrencetask',
|
||||
name='list_board',
|
||||
field=models.ForeignKey(default=1, null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='todo',
|
||||
name='list_board',
|
||||
field=models.ForeignKey(default=1, null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'),
|
||||
),
|
||||
]
|
||||
@ -1,6 +1,8 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
from boards.models import ListBoard, Board
|
||||
|
||||
class Tag(models.Model):
|
||||
"""
|
||||
Represents a tag that can be associated with tasks.
|
||||
@ -12,7 +14,7 @@ class Tag(models.Model):
|
||||
|
||||
class Task(models.Model):
|
||||
"""
|
||||
Represents a Abstract of task, such as Habit, Daily, Todo, or Reward.
|
||||
Represents a Abstract of task, such as Habit, Recurrence, Todo.
|
||||
|
||||
:param user: The user who owns the task.
|
||||
:param title: Title of the task.
|
||||
@ -23,10 +25,6 @@ class Task(models.Model):
|
||||
:param challenge: Associated challenge (optional).
|
||||
:param fromSystem: A boolean field indicating if the task is from System.
|
||||
:param creation_date: Creation date of the task.
|
||||
:param last_update: Last updated date of the task.
|
||||
:param: google_calendar_id: Google Calendar Event ID of the task.
|
||||
:param start_event: Start event of the task.
|
||||
:param end_event: End event(Due Date) of the task.
|
||||
"""
|
||||
class Difficulty(models.IntegerChoices):
|
||||
EASY = 1, 'Easy'
|
||||
@ -45,22 +43,36 @@ class Task(models.Model):
|
||||
fromSystem = models.BooleanField(default=False)
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
last_update = models.DateTimeField(auto_now=True)
|
||||
google_calendar_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
start_event = models.DateTimeField(null=True)
|
||||
end_event = models.DateTimeField(null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Todo(Task):
|
||||
|
||||
"""
|
||||
Represent a Todo task.
|
||||
|
||||
:param list_board: The list board that the task belongs to.
|
||||
:param is_active: A boolean field indicating whether the task is active. (Archive or not)
|
||||
:param is_full_day_event: A boolean field indicating whether the task is a full day event.
|
||||
:param start_event: Start date and time of the task.
|
||||
:param end_event: End date and time of the task.
|
||||
:param google_calendar_id: The Google Calendar ID of the task.
|
||||
:param completed: A boolean field indicating whether the task is completed.
|
||||
:param priority: The priority of the task (range: 1 to 4).
|
||||
"""
|
||||
class EisenhowerMatrix(models.IntegerChoices):
|
||||
IMPORTANT_URGENT = 1, 'Important & Urgent'
|
||||
IMPORTANT_NOT_URGENT = 2, 'Important & Not Urgent'
|
||||
NOT_IMPORTANT_URGENT = 3, 'Not Important & Urgent'
|
||||
NOT_IMPORTANT_NOT_URGENT = 4, 'Not Important & Not Urgent'
|
||||
|
||||
list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True, default=1)
|
||||
is_active = models.BooleanField(default=True)
|
||||
is_full_day_event = models.BooleanField(default=False)
|
||||
start_event = models.DateTimeField(null=True)
|
||||
end_event = models.DateTimeField(null=True)
|
||||
google_calendar_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
completed = models.BooleanField(default=False)
|
||||
priority = models.PositiveSmallIntegerField(choices=EisenhowerMatrix.choices, default=EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT)
|
||||
|
||||
@ -68,15 +80,95 @@ class Todo(Task):
|
||||
return self.title
|
||||
|
||||
class RecurrenceTask(Task):
|
||||
"""
|
||||
Represent a Recurrence task. (Occure every day, week, month, year)
|
||||
|
||||
:param list_board: The list board that the task belongs to.
|
||||
:param rrule: The recurrence rule of the task.
|
||||
:param is_active: A boolean field indicating whether the task is active. (Archive or not)
|
||||
:param is_full_day_event: A boolean field indicating whether the task is a full day event.
|
||||
:param start_event: Start date and time of the task.
|
||||
:param end_event: End date and time of the task.
|
||||
:param completed: A boolean field indicating whether the task is completed.
|
||||
:param parent_task: The parent task of the subtask.
|
||||
"""
|
||||
list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True, default=1)
|
||||
rrule = models.CharField(max_length=255, null=True, blank=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
is_full_day_event = models.BooleanField(default=False)
|
||||
start_event = models.DateTimeField(null=True)
|
||||
end_event = models.DateTimeField(null=True)
|
||||
completed = models.BooleanField(default=False)
|
||||
recurrence_rule = models.CharField()
|
||||
parent_task = models.ForeignKey("self", on_delete=models.CASCADE, null=True)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.title} ({self.recurrence_rule})"
|
||||
|
||||
|
||||
class RecurrencePattern(models.Model):
|
||||
"""
|
||||
:param recurrence_task: The recurrence task that the pattern belongs to.
|
||||
:param recurring_type: The type of recurrence.
|
||||
:param max_occurrences: The maximum number of occurrences.
|
||||
:param day_of_week: The day of the week that event will occure.
|
||||
:param week_of_month: The week of the month that event will occure.
|
||||
:param day_of_month: The day of the month that event will occure.
|
||||
:param month_of_year: The month of the year that event will occure.
|
||||
"""
|
||||
class RecurringType(models.IntegerChoices):
|
||||
DAILY = 0, 'Daily'
|
||||
WEEKLY = 1, 'Weekly'
|
||||
MONTHLY = 2, 'Monthly'
|
||||
YEARLY = 3, 'Yearly'
|
||||
|
||||
class DayOfWeek(models.IntegerChoices):
|
||||
MONDAY = 0, 'Monday'
|
||||
TUESDAY = 1, 'Tuesday'
|
||||
WEDNESDAY = 2, 'Wednesday'
|
||||
THURSDAY = 3, 'Thursday'
|
||||
FRIDAY = 4, 'Friday'
|
||||
SATURDAY = 5, 'Saturday'
|
||||
SUNDAY = 6, 'Sunday'
|
||||
|
||||
class WeekOfMonth(models.IntegerChoices):
|
||||
FIRST = 1, 'First'
|
||||
SECOND = 2, 'Second'
|
||||
THIRD = 3, 'Third'
|
||||
FOURTH = 4, 'Fourth'
|
||||
LAST = 5, 'Last'
|
||||
|
||||
class MonthOfYear(models.IntegerChoices):
|
||||
JANUARY = 1, 'January'
|
||||
FEBRUARY = 2, 'February'
|
||||
MARCH = 3, 'March'
|
||||
APRIL = 4, 'April'
|
||||
MAY = 5, 'May'
|
||||
JUNE = 6, 'June'
|
||||
JULY = 7, 'July'
|
||||
AUGUST = 8, 'August'
|
||||
SEPTEMBER = 9, 'September'
|
||||
OCTOBER = 10, 'October'
|
||||
NOVEMBER = 11, 'November'
|
||||
DECEMBER = 12, 'December'
|
||||
|
||||
recurrence_task = models.ForeignKey(RecurrenceTask, on_delete=models.CASCADE)
|
||||
recurring_type = models.IntegerField(choices=RecurringType.choices)
|
||||
max_occurrences = models.IntegerField(default=0)
|
||||
day_of_week = models.IntegerField(choices=DayOfWeek.choices)
|
||||
week_of_month = models.IntegerField(choices=WeekOfMonth.choices)
|
||||
day_of_month = models.IntegerField(default=0)
|
||||
month_of_year = models.IntegerField(choices=MonthOfYear.choices)
|
||||
|
||||
|
||||
class Habit(Task):
|
||||
"""
|
||||
Represent a Habit task with streaks.
|
||||
|
||||
:param streak: The streak of the habit.
|
||||
:param current_count: The current count of the habit.
|
||||
"""
|
||||
streak = models.IntegerField(default=0)
|
||||
current_count = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.title} ({self.streak})"
|
||||
@ -91,67 +183,4 @@ class Subtask(models.Model):
|
||||
"""
|
||||
parent_task = models.ForeignKey(Todo, on_delete=models.CASCADE)
|
||||
description = models.TextField()
|
||||
completed = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class UserNotification(models.Model):
|
||||
"""
|
||||
Represents a user notification.
|
||||
|
||||
:param type: The type of the notification (e.g., 'NEW_CHAT_MESSAGE').
|
||||
:param data: JSON data associated with the notification.
|
||||
:param seen: A boolean field indicating whether the notification has been seen.
|
||||
"""
|
||||
NOTIFICATION_TYPES = (
|
||||
('LEVEL_UP', 'Level Up'),
|
||||
('DEATH', 'Death'),
|
||||
)
|
||||
|
||||
type = models.CharField(max_length=255, choices=[type for type in NOTIFICATION_TYPES])
|
||||
data = models.JSONField(default=dict)
|
||||
seen = models.BooleanField(default=False)
|
||||
|
||||
@staticmethod
|
||||
def clean_notification(notifications):
|
||||
"""
|
||||
Cleanup function for removing corrupt notification data:
|
||||
- Removes notifications with null or missing id or type.
|
||||
"""
|
||||
if not notifications:
|
||||
return notifications
|
||||
|
||||
filtered_notifications = []
|
||||
|
||||
for notification in notifications:
|
||||
if notification.id is None or notification.type is None:
|
||||
continue
|
||||
|
||||
return filtered_notifications
|
||||
|
||||
|
||||
class Transaction(models.Model):
|
||||
"""
|
||||
Represents a transaction involving currencies in the system.
|
||||
|
||||
:param currency: The type of currency used in the transaction
|
||||
:param transactionType: The type of the transaction
|
||||
:param description: Additional text.
|
||||
:param amount: The transaction amount.
|
||||
:param user: The user involved in the transaction.
|
||||
"""
|
||||
CURRENCIES = (('gold', 'Gold'),)
|
||||
TRANSACTION_TYPES = (
|
||||
('buy_gold', 'Buy Gold'),
|
||||
('spend', 'Spend'),
|
||||
('debug', 'Debug'),
|
||||
('force_update_gold', 'Force Update Gold'),
|
||||
)
|
||||
|
||||
currency = models.CharField(max_length=12, choices=CURRENCIES)
|
||||
transaction_type = models.CharField(max_length=24, choices=TRANSACTION_TYPES)
|
||||
description = models.TextField(blank=True)
|
||||
amount = models.FloatField(default=0)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return f"Transaction ({self.id})"
|
||||
completed = models.BooleanField(default=False)
|
||||
@ -1,5 +1,4 @@
|
||||
from rest_framework import serializers
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from .models import Todo, RecurrenceTask
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
from django.db.models.signals import pre_save
|
||||
from django.db.models.signals import pre_save, post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
|
||||
from boards.models import ListBoard
|
||||
from tasks.models import Todo
|
||||
|
||||
|
||||
@ -22,4 +23,17 @@ def update_priority(sender, instance, **kwargs):
|
||||
elif time_until_due <= urgency_threshold and instance.importance < importance_threshold:
|
||||
instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT
|
||||
else:
|
||||
instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT
|
||||
instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT
|
||||
|
||||
|
||||
@receiver(post_save, sender=Todo)
|
||||
def assign_todo_to_listboard(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
user_board = instance.user.board_set.first()
|
||||
|
||||
if user_board:
|
||||
first_list_board = user_board.listboard_set.order_by('position').first()
|
||||
|
||||
if first_list_board:
|
||||
instance.list_board = first_list_board
|
||||
instance.save()
|
||||
@ -0,0 +1,38 @@
|
||||
# Generated by Django 4.2.6 on 2023-11-19 20:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0005_alter_userstats_endurance_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='userstats',
|
||||
name='endurance',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='userstats',
|
||||
name='intelligence',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='userstats',
|
||||
name='luck',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='userstats',
|
||||
name='perception',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='userstats',
|
||||
name='strength',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customuser',
|
||||
name='last_name',
|
||||
field=models.CharField(blank=True, max_length=150),
|
||||
),
|
||||
]
|
||||
@ -5,16 +5,18 @@ from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from .managers import CustomAccountManager
|
||||
|
||||
|
||||
class CustomUser(AbstractBaseUser, PermissionsMixin):
|
||||
# User fields
|
||||
"""
|
||||
User model where email is the unique identifier for authentication.
|
||||
"""
|
||||
email = models.EmailField(_('email address'), unique=True)
|
||||
username = models.CharField(max_length=150, unique=True)
|
||||
first_name = models.CharField(max_length=150, blank=True)
|
||||
last_name = models.CharField(max_length=150, blank=True)
|
||||
start_date = models.DateTimeField(default=timezone.now)
|
||||
about = models.TextField(_('about'), max_length=500, blank=True)
|
||||
profile_pic = models.ImageField(upload_to='profile_pics', null=True, blank=True, default='profile_pics/default.png')
|
||||
@ -35,7 +37,6 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
|
||||
# String representation of the user
|
||||
return self.username
|
||||
|
||||
|
||||
def random_luck():
|
||||
return random.randint(1, 50)
|
||||
|
||||
@ -51,17 +52,6 @@ class UserStats(models.Model):
|
||||
health = models.IntegerField(default=100)
|
||||
gold = models.FloatField(default=0.0)
|
||||
experience = models.FloatField(default=0)
|
||||
strength = models.IntegerField(default=1,
|
||||
validators=[MinValueValidator(1),
|
||||
MaxValueValidator(100)])
|
||||
intelligence = models.IntegerField(default=1, validators=[MinValueValidator(1),
|
||||
MaxValueValidator(100)])
|
||||
endurance = models.IntegerField(default=1, validators=[MinValueValidator(1),
|
||||
MaxValueValidator(100)])
|
||||
perception = models.IntegerField(default=1, validators=[MinValueValidator(1),
|
||||
MaxValueValidator(100)])
|
||||
luck = models.IntegerField(default=random_luck, validators=[MinValueValidator(1),
|
||||
MaxValueValidator(50)],)
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@ -41,6 +40,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-bootstrap": "^2.9.1",
|
||||
"react-datetime-picker": "^5.5.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.11.0",
|
||||
"react-router-dom": "^6.18.0",
|
||||
|
||||
@ -5,9 +5,6 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@asseinfo/react-kanban':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@dnd-kit/core':
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -98,6 +95,9 @@ dependencies:
|
||||
react-bootstrap:
|
||||
specifier: ^2.9.1
|
||||
version: 2.9.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-datetime-picker:
|
||||
specifier: ^5.5.3
|
||||
version: 5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
@ -174,19 +174,6 @@ packages:
|
||||
'@jridgewell/trace-mapping': 0.3.20
|
||||
dev: true
|
||||
|
||||
/@asseinfo/react-kanban@2.2.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-/gCigrNXRHeP9VCo8RipTOrA0vAPRIOThJhR4ibVxe6BLkaWFUEuJ1RMT4fODpRRsE3XsdrfVGKkfpRBKgvxXg==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-beautiful-dnd: 13.1.1(react-dom@18.2.0)(react@18.2.0)
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- react-native
|
||||
dev: false
|
||||
|
||||
/@babel/code-frame@7.22.13:
|
||||
resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -1489,6 +1476,16 @@ packages:
|
||||
hoist-non-react-statics: 3.3.2
|
||||
dev: false
|
||||
|
||||
/@types/lodash.memoize@4.1.9:
|
||||
resolution: {integrity: sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==}
|
||||
dependencies:
|
||||
'@types/lodash': 4.14.201
|
||||
dev: false
|
||||
|
||||
/@types/lodash@4.14.201:
|
||||
resolution: {integrity: sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==}
|
||||
dev: false
|
||||
|
||||
/@types/parse-json@4.0.2:
|
||||
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
||||
dev: false
|
||||
@ -1500,7 +1497,6 @@ packages:
|
||||
resolution: {integrity: sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==}
|
||||
dependencies:
|
||||
'@types/react': 18.2.37
|
||||
dev: true
|
||||
|
||||
/@types/react-redux@7.1.30:
|
||||
resolution: {integrity: sha512-i2kqM6YaUwFKduamV6QM/uHbb0eCP8f8ZQ/0yWf+BsAVVsZPRYJ9eeGWZ3uxLfWwwA0SrPRMTPTqsPFkY3HZdA==}
|
||||
@ -1551,6 +1547,10 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@wojtekmaj/date-utils@1.5.1:
|
||||
resolution: {integrity: sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==}
|
||||
dev: false
|
||||
|
||||
/acorn-jsx@5.3.2(acorn@8.11.2):
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@ -2053,6 +2053,10 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/detect-element-overflow@1.4.2:
|
||||
resolution: {integrity: sha512-4m6cVOtvm/GJLjo7WFkPfwXoEIIbM7GQwIh4WEa4g7IsNi1YzwUsGL5ApNLrrHL29bHeNeQ+/iZhw+YHqgE2Fw==}
|
||||
dev: false
|
||||
|
||||
/didyoumean@1.2.2:
|
||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||
|
||||
@ -2545,6 +2549,13 @@ packages:
|
||||
get-intrinsic: 1.2.2
|
||||
dev: true
|
||||
|
||||
/get-user-locale@2.3.1:
|
||||
resolution: {integrity: sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==}
|
||||
dependencies:
|
||||
'@types/lodash.memoize': 4.1.9
|
||||
lodash.memoize: 4.1.2
|
||||
dev: false
|
||||
|
||||
/glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@ -2972,6 +2983,10 @@ packages:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
dev: true
|
||||
|
||||
/lodash.memoize@4.1.2:
|
||||
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
|
||||
dev: false
|
||||
|
||||
/lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
@ -2992,6 +3007,10 @@ packages:
|
||||
yallist: 3.1.1
|
||||
dev: true
|
||||
|
||||
/make-event-props@1.6.2:
|
||||
resolution: {integrity: sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==}
|
||||
dev: false
|
||||
|
||||
/memoize-one@5.2.1:
|
||||
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
|
||||
dev: false
|
||||
@ -3369,6 +3388,95 @@ packages:
|
||||
dependencies:
|
||||
date-fns: 2.30.0
|
||||
react: 18.2.0
|
||||
/react-calendar@4.6.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-MvCPdvxEvq7wICBhFxlYwxS2+IsVvSjTcmlr0Kl3yDRVhoX7btNg0ySJx5hy9rb1eaM4nDpzQcW5c87nfQ8n8w==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.37
|
||||
'@wojtekmaj/date-utils': 1.5.1
|
||||
clsx: 2.0.0
|
||||
get-user-locale: 2.3.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
tiny-warning: 1.0.3
|
||||
dev: false
|
||||
|
||||
/react-clock@4.5.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-xcSpsehBpX0NHwjEzZ9BP4Ouv54nlYqDMHoone82xW7TpPdkWNrBQd4+SiMQfbpqj1yvh2kSwn6FXffw37gAkw==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.37
|
||||
'@wojtekmaj/date-utils': 1.5.1
|
||||
clsx: 2.0.0
|
||||
get-user-locale: 2.3.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-date-picker@10.5.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-EwLNYPy+/2p7VwsAWnitPhtkC2tesABkZNlAAIEPZeHucyMlO5KB6z55POdtamu6T6vs0RY2G5EVgxDaxlj0MQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.37
|
||||
'@wojtekmaj/date-utils': 1.5.1
|
||||
clsx: 2.0.0
|
||||
get-user-locale: 2.3.1
|
||||
make-event-props: 1.6.2
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-calendar: 4.6.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-fit: 1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
update-input-width: 1.4.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/react-dom'
|
||||
dev: false
|
||||
|
||||
/react-datetime-picker@5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-bWGEPwGrZjaXTB8P4pbTSDygctLaqTWp0nNibaz8po+l4eTh9gv3yiJ+n4NIcpIJDqZaQJO57Bnij2rAFVQyLw==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.37
|
||||
'@wojtekmaj/date-utils': 1.5.1
|
||||
clsx: 2.0.0
|
||||
get-user-locale: 2.3.1
|
||||
make-event-props: 1.6.2
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-calendar: 4.6.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-clock: 4.5.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-date-picker: 10.5.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-fit: 1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-time-picker: 6.5.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react-dom'
|
||||
dev: false
|
||||
|
||||
/react-dom@18.2.0(react@18.2.0):
|
||||
@ -3381,6 +3489,30 @@ packages:
|
||||
scheduler: 0.23.0
|
||||
dev: false
|
||||
|
||||
/react-fit@1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-y/TYovCCBzfIwRJsbLj0rH4Es40wPQhU5GPPq9GlbdF09b0OdzTdMSkBza0QixSlgFzTm6dkM7oTFzaVvaBx+w==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
'@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.37
|
||||
'@types/react-dom': 18.2.15
|
||||
detect-element-overflow: 1.4.2
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
tiny-warning: 1.0.3
|
||||
dev: false
|
||||
|
||||
/react-icons@4.11.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==}
|
||||
/react-icons@4.12.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==}
|
||||
peerDependencies:
|
||||
@ -3491,6 +3623,29 @@ packages:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-lifecycles-compat: 3.0.4
|
||||
/react-time-picker@6.5.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-xRamxjndpq3HfnEL+6T3VyirLMEn4D974OJgs9sTP8iJX/RB02rmwy09C9oBThTGuN3ycbozn06iYLn148vcdw==}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.2.37
|
||||
'@wojtekmaj/date-utils': 1.5.1
|
||||
clsx: 2.0.0
|
||||
get-user-locale: 2.3.1
|
||||
make-event-props: 1.6.2
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-clock: 4.5.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-fit: 1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
|
||||
update-input-width: 1.4.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/react-dom'
|
||||
dev: false
|
||||
|
||||
/react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0):
|
||||
@ -3858,6 +4013,10 @@ packages:
|
||||
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
|
||||
dev: false
|
||||
|
||||
/tiny-warning@1.0.3:
|
||||
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
|
||||
dev: false
|
||||
|
||||
/to-fast-properties@2.0.0:
|
||||
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
|
||||
engines: {node: '>=4'}
|
||||
@ -4269,6 +4428,10 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
dev: true
|
||||
|
||||
/update-input-width@1.4.2:
|
||||
resolution: {integrity: sha512-/p0XLhrQQQ4bMWD7bL9duYObwYCO1qGr8R19xcMmoMSmXuQ7/1//veUnCObQ7/iW6E2pGS6rFkS4TfH4ur7e/g==}
|
||||
dev: false
|
||||
|
||||
/uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
dependencies:
|
||||
|
||||
@ -1,14 +1,152 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { FaTasks, FaRegListAlt } from "react-icons/fa";
|
||||
import { FaPlus } from "react-icons/fa6";
|
||||
import { TbChecklist } from "react-icons/tb";
|
||||
|
||||
function TaskDetailModal() {
|
||||
const [difficulty, setDifficulty] = useState(50);
|
||||
const [isChallengeChecked, setChallengeChecked] = useState(true);
|
||||
const [isImportantChecked, setImportantChecked] = useState(true);
|
||||
|
||||
const handleChallengeChange = () => {
|
||||
setChallengeChecked(!isChallengeChecked);
|
||||
};
|
||||
|
||||
const handleImportantChange = () => {
|
||||
setImportantChecked(!isImportantChecked);
|
||||
};
|
||||
const handleDifficultyChange = event => {
|
||||
setDifficulty(parseInt(event.target.value, 10));
|
||||
};
|
||||
|
||||
return (
|
||||
<dialog id="task_detail_modal" className="modal">
|
||||
<div className="modal-box">
|
||||
<div className="modal-box w-4/5 max-w-3xl">
|
||||
{/* Title */}
|
||||
<div className="flex flex-col py-2">
|
||||
<div className="flex flex-col">
|
||||
<h3 className="font-bold text-lg">
|
||||
<span className="flex gap-2">{<FaTasks className="my-2" />}Title</span>
|
||||
</h3>
|
||||
<p className="text-xs">Todo List</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="flex flex-col py-2 pb-4">
|
||||
<div className="flex flex-row space-x-5">
|
||||
<div className="dropdown">
|
||||
<label tabIndex={0} className="btn-md border-2 rounded-xl m-1 py-1">
|
||||
+ Add Tags
|
||||
</label>
|
||||
<ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li>
|
||||
<a>
|
||||
<input type="checkbox" checked="checked" className="checkbox checkbox-sm" />
|
||||
Item 2
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a>
|
||||
<input type="checkbox" checked="checked" className="checkbox checkbox-sm" />
|
||||
Item 2
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a>
|
||||
<input type="checkbox" checked="checked" className="checkbox checkbox-sm" />
|
||||
Item 2
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-nowrap overflow-x-auto"></div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<h2 className="font-bold">
|
||||
<span className="flex gap-2">
|
||||
<FaRegListAlt className="my-1" />
|
||||
Description
|
||||
</span>
|
||||
</h2>
|
||||
<textarea className="textarea w-full" disabled></textarea>
|
||||
</div>
|
||||
|
||||
{/* Difficulty, Challenge and Importance */}
|
||||
<div className="flex flex-row space-x-3 my-4">
|
||||
<div className="flex-1 card shadow border-2 p-2">
|
||||
<input
|
||||
type="range"
|
||||
id="difficultySelector"
|
||||
min={0}
|
||||
max="100"
|
||||
value={difficulty}
|
||||
className="range"
|
||||
step="25"
|
||||
onChange={handleDifficultyChange}
|
||||
/>
|
||||
<div className="w-full flex justify-between text-xs px-2 space-x-2">
|
||||
<span>Easy</span>
|
||||
<span>Normal</span>
|
||||
<span>Hard</span>
|
||||
<span>Very Hard</span>
|
||||
<span>Devil</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Challenge Checkbox */}
|
||||
<div className="card shadow border-2 p-2">
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer space-x-2">
|
||||
<span className="label-text">Challenge</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isChallengeChecked}
|
||||
className="checkbox"
|
||||
onChange={handleChallengeChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Important Checkbox */}
|
||||
<div className="card shadow border-2 p-2">
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer space-x-2">
|
||||
<span className="label-text">Important</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isImportantChecked}
|
||||
className="checkbox"
|
||||
onChange={handleImportantChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Subtask */}
|
||||
<div className="flex flex-col pt-2">
|
||||
<h2 className="font-bold">
|
||||
<span className="flex gap-1">
|
||||
<TbChecklist className="my-1" />
|
||||
Subtasks
|
||||
</span>
|
||||
</h2>
|
||||
<div className="flex space-x-3 pt-2">
|
||||
<input type="text" placeholder="subtask topic" className="input input-bordered flex-1 w-full" />
|
||||
<button className="btn">
|
||||
<FaPlus />
|
||||
Add Subtask
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="dialog">
|
||||
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">X</button>
|
||||
</form>
|
||||
<h3 className="font-bold text-lg">Hello!</h3>
|
||||
<p className="py-4">Press ESC key or click on ✕ button to close</p>
|
||||
</div>
|
||||
</dialog>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user