From c0a58b45f4c5e1e051702a16c187db32a06d09ba Mon Sep 17 00:00:00 2001 From: c-d-p Date: Mon, 21 Apr 2025 23:47:38 +0200 Subject: [PATCH] [V0.4] Added TODOs --- backend/__pycache__/main.cpython-312.pyc | Bin 1940 -> 1989 bytes .../alembic/__pycache__/env.cpython-312.pyc | Bin 3127 -> 3176 bytes backend/alembic/env.py | 1 + .../versions/9a82960db482_add_todo_table.py | 32 +++ ...a82960db482_add_todo_table.cpython-312.pyc | Bin 0 -> 982 bytes backend/main.py | 1 + backend/modules/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 706 -> 792 bytes .../nlp/__pycache__/api.cpython-312.pyc | Bin 7936 -> 11382 bytes .../nlp/__pycache__/service.cpython-312.pyc | Bin 6347 -> 7583 bytes backend/modules/nlp/api.py | 70 ++++++ backend/modules/nlp/service.py | 56 ++++- backend/modules/todo/__init__.py | 2 + .../todo/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 149 bytes .../todo/__pycache__/api.cpython-312.pyc | Bin 0 -> 3268 bytes .../todo/__pycache__/models.cpython-312.pyc | Bin 0 -> 1006 bytes .../todo/__pycache__/schemas.cpython-312.pyc | Bin 0 -> 1568 bytes .../todo/__pycache__/service.cpython-312.pyc | Bin 0 -> 3104 bytes backend/modules/todo/api.py | 62 +++++ backend/modules/todo/models.py | 17 ++ backend/modules/todo/schemas.py | 26 +++ backend/modules/todo/service.py | 36 +++ interfaces/nativeapp/src/api/client.ts | 2 - interfaces/nativeapp/src/api/todo.ts | 58 +++++ .../nativeapp/src/screens/DashboardScreen.tsx | 218 +++++++++++++++++- interfaces/nativeapp/src/types/todo.ts | 23 ++ 26 files changed, 589 insertions(+), 17 deletions(-) create mode 100644 backend/alembic/versions/9a82960db482_add_todo_table.py create mode 100644 backend/alembic/versions/__pycache__/9a82960db482_add_todo_table.cpython-312.pyc create mode 100644 backend/modules/todo/__init__.py create mode 100644 backend/modules/todo/__pycache__/__init__.cpython-312.pyc create mode 100644 backend/modules/todo/__pycache__/api.cpython-312.pyc create mode 100644 backend/modules/todo/__pycache__/models.cpython-312.pyc create mode 100644 backend/modules/todo/__pycache__/schemas.cpython-312.pyc create mode 100644 backend/modules/todo/__pycache__/service.cpython-312.pyc create mode 100644 backend/modules/todo/api.py create mode 100644 backend/modules/todo/models.py create mode 100644 backend/modules/todo/schemas.py create mode 100644 backend/modules/todo/service.py create mode 100644 interfaces/nativeapp/src/api/todo.ts create mode 100644 interfaces/nativeapp/src/types/todo.ts diff --git a/backend/__pycache__/main.cpython-312.pyc b/backend/__pycache__/main.cpython-312.pyc index 4903f4e66088002c4ebdaf809f05faf99e3796fa..8743bd6b1e0d4531c2d79ec366cb76559a1c1210 100644 GIT binary patch delta 522 zcmbQjf0Uo^G%qg~0}$x+vt^jEP2`hcTrg3+)-8o0g)2uiS1d}5k%5UJRUk_bqzDzH zaIa>Bh-4X}%7A!4Jw_An$=dUzvZk`9Go~`8vPc4j)0iM+3U3QbloZftsnt-uQPMyj zd#dyrKA5plGO03?Coqak_G9E_mdUc69LFf`&A*5-MIc2moe_lB2(4y?XlGbC0OKkSO~zX+A^9o!o3ohs85xBqS24#hicP-G?4%BI zXfeo#3Pln?@|HkOW?E`-L1JEfT4HiZeo z19DPxii`MxI&N{MrCbD-Qp+)GE>V^b8fM^1_k*Cft^qUa%qw9HE&%+ZYK{N^ delta 465 zcmX@gKZT#~G%qg~0}yE0vt@i`oyaG_IAx-Gttk^jsz8<&NCFk4aII#9h-B%a$^dy2 zH;PaE!|lkO%9_fa&X~%W$|4CAO=E(PDLgGKQ4&CdC00XqMo9vB?5UD#cwvS{Nu^3< znNRj$ln2t57#1+3a-}n*@GW9Y;ZG4rX9VFjf~#2}>KGY-daN<^qzJ)GPZ4fmfZHus z$*w6f`3&Q#&2>!tjEq8)`@5CaWK-Bv7;A<~OY8 znD_*MOg~MYA{HQBBs}>9hm(OQhzSxe5(5$9Kthx07I$)fUS4W)Nn%n?YLP5Zte6Q% zC@2(3Ot#{bW>p4qJSRtSUT0L8Y{YfX{g!ZUeoARhYO!7k&*%oEi*knEi)%IJ})sh^%h5NYDr>BVo4&zZjc*`lqcJA%ScK9nZGz}AO_kMSpm6> YKwNxaauIh1^IZm&$+x&8SuBA<0MYwd@Bjb+ delta 381 zcmaDMv0Z}qG%qg~0}x!OXUzzk$ScXnv{Buiu|Aa}OAjQB3ZleQ#j^}h#X!79Atr`Y zX6$mS86oN!8BztZj4<^^Nu}_laJ8^RNdswaAT0wjizCZwvOTkuN)ZQ;C%qb~jgg^} z5y+Q8=4!r`zR7D@{a7V{dJHzPonzwT2eSM$d5Tzobdk{H zI~-01A|NJ6yhs#8hye*rrd!;}`FVM%$t8(NIjKc5K(S&bAfcd8BtF@XQ<_x?$nlt* z$9bJmZn7KKJtm-84%`adU}HeG6)8>5=9ZBZ2Qq(g*yQG?l;)(`6 None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/backend/alembic/versions/__pycache__/9a82960db482_add_todo_table.cpython-312.pyc b/backend/alembic/versions/__pycache__/9a82960db482_add_todo_table.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50b8b359b6f565aeb09f69cafd65a53cda3555ab GIT binary patch literal 982 zcmah|zi-n(6h8lmW4m$E0tF!~i?$8y0JgN&6)W6_taEX-IkQjiLzC3+HO zttliR1&WXP%QUBTIjxIUj@1#56jrCaFy@spul<8pQ(l8pFGMR3HJGFCLSHQAcRI@u z>PZA6J#kzgsp>fZqMoxnC z6$&lgteCY@WwB%~(6Cjlw#?h*irJ`{O;!CuC4(YQma$waYTdBc4+@5H5WUf*S3AfMSe_Fh`K0*u^ojT8#lQGnw+ptv4q7 zbe~`cB0cunC~(Sa?*Z1>9(L&TzR`{XWO%S^co9U#gXPs_!*#r;w7uc@C~!NTv4t?R zDmEsi+YW>_bJR{9EqD7Q+dCVDr)le1HX}=60FEV)QE;Bd|5ZD)Lht>j-c;=wuok#L zfhf$MbRRxX^DB&)VZgRf_a%&y9uC)v(O*dKzY7_j@MI{uP*DlY{Q?D^;pW(qm_ z#F)adz;WC+UgE?bdBDwo1{XhqOUE*pnH$Kv^CNKe=vrlQ2udfdu9Tw(BpmMZO z8<%eESKpN0T{xI69Tv(XP&+D{19kV_aDMn~UwHL=A0C{$d6>I30_KS%aLV^Qh18dy D50Cb_ literal 0 HcmV?d00001 diff --git a/backend/main.py b/backend/main.py index 651ccd1..29719b3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -11,6 +11,7 @@ import logging # import all models to ensure they are registered before create_all from modules.calendar.models import CalendarEvent from modules.auth.models import User +from modules.todo.models import Todo # Import the new Todo model logging.getLogger('passlib').setLevel(logging.ERROR) # fix bc package logging is broken diff --git a/backend/modules/__init__.py b/backend/modules/__init__.py index 154386d..73bfd78 100644 --- a/backend/modules/__init__.py +++ b/backend/modules/__init__.py @@ -4,6 +4,7 @@ from .auth.api import router as auth_router from .user.api import router as user_router from .calendar.api import router as calendar_router from .nlp.api import router as nlp_router +from .todo.api import router as todo_router router = APIRouter(prefix="/api") router.include_router(admin_router) @@ -11,3 +12,4 @@ router.include_router(auth_router) router.include_router(user_router) router.include_router(calendar_router) router.include_router(nlp_router) +router.include_router(todo_router) diff --git a/backend/modules/__pycache__/__init__.cpython-312.pyc b/backend/modules/__pycache__/__init__.cpython-312.pyc index 8e2100d8ef7c04525f1c1cc63cd28616a58af814..f3f44a1d39bbd1a5ef6b4aff3678509abc1f81d6 100644 GIT binary patch delta 233 zcmX@aI)jbxG%qg~0}vSZvt_6=P2`hcG@GdItCYf&!kojK%N)hW$iT#q%9z5khIKU~ zNFf756mu$bmi)vnMMn9FYe2*~5b;6LTpl8iOs4X{bTKl3bRo+@*r|Mz)fgQa1tw=O z_VaU;AbOEObVBx32Bjh~pe6vRL@#;( delta 120 zcmbQic8HblG%qg~0}wp3WyrX}IFV0+kz=B|@5BaCM(K&u6oJGJ5OE7c`~eb^OsGAXpi_?L`2WCb_#=8u%cNui=GH8R5@m&VZ$y=FxIj%A& J7Ks4W002jj9$o+d diff --git a/backend/modules/nlp/__pycache__/api.cpython-312.pyc b/backend/modules/nlp/__pycache__/api.cpython-312.pyc index 1d6c0c4b4322914576614ddf1c1bfc80b3a6da97..054df8cd5b6c08ad2e640ddb4305f94bbe8ad5c2 100644 GIT binary patch delta 3584 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 delta 1433 zcmZ8hUrbw77(eIU_CkU3xAeA!wint#F9oJ3Fa~ZZY;G^s4ULYP*mXS@D5HOJdJ!jzdeauxyl#n|FamQgpz8R;S`6owL)1y#H(QNX<}~a~HLTMLbFEto2hvzKBg{P? zO6xJ567W6qJ?WlU4&4VeN)qkSp?77&=WI?}8EY!bD-5Xv=XpF`Pm&Y6}#3 zp-C-mhy5+gccg#cvR~JVQRy98R4l(!`lfhn9id*)r}WT}xT3sjJqZPz;tS<-@k&js zTCoxG0a+p9EsLZ0LrsLzK(W5=T{Ius|3q`Up@vqow(B86_@~9YjW1J|jhIj49QZKb ziglAP{~ftN#jQF`+*X@L)D?AZpu!(T=Da>kNWPu50RQY!rsB^Hi-&4l22PxI`6KEY zWsX(#hPp`2Q-|sx;g?rkNBV2!{v9i>O>)OlxgSE{?mJiAV&55|Zn4v%hTX7z<&aHE zQv@ozZn$1Lu#0q&EXh-3k?O&s&rNBq1`T-!LO}F*wuQa@6Q{RSvyeA45zL=P7)5vv6}Chwp2*~(8uyBy zy#BuPn6)RlkuVJwH{LLyaWBeYr%*7#SezUATs8xT8y6wpSyAx$EFK`muFoU!ezEUs zt$iKh0Q~X}i)i$3NtbBxD}S>@e-*nO8}ctr&0;TjfAU38+}9$l(J}J%P-FniH~M%V zm~Yhy518$Na`ZrJgW$oKZIZ}8S|h$o0eL4-2f@zCh;n&U-W{Y6?2cL|iLiIQUwaYC za(HSk{>V0uVD^hQ+(GQR?5Aui6`A3PYyv6)|E=9OkLvjsKfDe`M*__s%BGp zGs~Cr_*d67tukg4tboOnvk5bPz;h1#UGYaKScM&;dVkTa$LURlXBSLZU>cH@SIUyh z=1q=EC{^c{St4Vml01&|RfO`m_|+;`{NG`oRF9>zY+>Fw!`DE9ACbHZa9^gB{ze9O z$>1L8+#~8EqCFx*dqhOTZjlT3iZ8?c135~^=}zbHJ}GwoBZ?Ck~pb?t1(@thHBSqv0j5LV|8a|L|YQq-s`a=0! z>RC6(X$o^E@E32+Js1gz=A-)T~rg@B=?%xY|pWz=#^_h9;AQ1p#mRk?- z9yeN6^$K*1s2YjzPn1(#Q~p)ntplqvk9xt}{4%)&%(vhg5Y_B{3eF(?hg=a>;(StbGV7YH>NER#x$S)OayuZkzL9N1bQJ~)<32Yi=3&)!0ybCeQ; zxUjuTL?Obwe}H^KC+(J~*hfXCRSPRxE?Wpw2Z|#Z#QcA3rfp2Ac}~lQp(X@T{(UqD zVfz*XN5|=0ZCgGB$Tno5E2nirL1aG24x2n5y0FiGt+9AYC;YACB&Q?IxaWt(yWfqR zmbP#(xI1E^v1S{-Wm+{u%$QUzk(%L>%RsX!6jTfV$ef~ot z`=nT%woJ649Bn}%cr>vGbh>TZ?pu0lK$~gU0dOT*ytrOMv7x4bl7}F7ytY4}KTUa4 zQF>HgYm^V>9)`N*5l{HHSB!LQ2IM;BkW`u}E@uzSD z)d>JGdzKQaIuI|FBTHrC8(n4L6kbBgCfhJ-28yKcr!uApq>82prt+o;r3$2RqzJdL zM2V$H!DU2%vZAS6sXUTE3xM)sEv!-EDdIr2OeqpTwGvRaB#DbxC(7#^%ZS(w>Y`lXptTv$HwpWTxho zOm>tB3DU6fv%Sec& eG8zfmeMn(sv}gI4%4jUe(!l#6hjB8$>;wQ2JaaDq diff --git a/backend/modules/nlp/api.py b/backend/modules/nlp/api.py index de262b0..8f0587c 100644 --- a/backend/modules/nlp/api.py +++ b/backend/modules/nlp/api.py @@ -15,6 +15,10 @@ from modules.nlp.models import ChatMessage # Import ChatMessage model from modules.calendar.service import create_calendar_event, get_calendar_events, update_calendar_event, delete_calendar_event from modules.calendar.models import CalendarEvent from modules.calendar.schemas import CalendarEventCreate, CalendarEventUpdate +# Import TODO services, schemas, and model +from modules.todo import service as todo_service +from modules.todo.models import Todo +from modules.todo.schemas import TodoCreate, TodoUpdate router = APIRouter(prefix="/nlp", tags=["nlp"]) @@ -31,6 +35,18 @@ def format_calendar_events(events: List[CalendarEvent]) -> List[str]: formatted.append(f"- {title} ({start_str}{' - ' + end_str if end_str else ''})") return formatted +# Helper to format TODO items (expects list of Todo models) +def format_todos(todos: List[Todo]) -> List[str]: + if not todos: + return ["Your TODO list is empty."] + formatted = ["Here is your TODO list:"] + for todo in todos: + status = "[X]" if todo.complete else "[ ]" + date_str = f" (Due: {todo.date.strftime('%Y-%m-%d')})" if todo.date else "" + remind_str = " (Reminder)" if todo.remind else "" + formatted.append(f"- {status} {todo.task}{date_str}{remind_str} (ID: {todo.id})") + return formatted + # Update the response model for the endpoint @router.post("/process-command", response_model=ProcessCommandResponse) def process_command(request_data: ProcessCommandRequest, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): @@ -129,6 +145,60 @@ def process_command(request_data: ProcessCommandRequest, current_user: User = De # --------------------------------- return ProcessCommandResponse(responses=responses) + # --- Add TODO Cases --- + case "get_todos": + todos: List[Todo] = todo_service.get_todos(db, user=current_user, **params) + formatted_responses = format_todos(todos) + responses.extend(formatted_responses) + # --- Save Additional AI Responses --- + for resp in formatted_responses: + save_chat_message(db, user_id=current_user.id, sender=MessageSender.AI, text=resp) + # ---------------------------------- + return ProcessCommandResponse(responses=responses) + + case "add_todo": + todo_data = TodoCreate(**params) + created_todo = todo_service.create_todo(db, todo=todo_data, user=current_user) + add_response = f"Added TODO: '{created_todo.task}' (ID: {created_todo.id})." + responses.append(add_response) + # --- Save Additional AI Response --- + save_chat_message(db, user_id=current_user.id, sender=MessageSender.AI, text=add_response) + # --------------------------------- + return ProcessCommandResponse(responses=responses) + + case "update_todo": + todo_id = params.pop('todo_id', None) + if todo_id is None: + error_msg = "TODO ID is required for update." + save_chat_message(db, user_id=current_user.id, sender=MessageSender.AI, text=error_msg) + raise HTTPException(status_code=400, detail=error_msg) + todo_data = TodoUpdate(**params) + updated_todo = todo_service.update_todo(db, todo_id=todo_id, todo_update=todo_data, user=current_user) + update_response = f"Updated TODO ID {updated_todo.id}: '{updated_todo.task}'." + if 'complete' in params: + status = "complete" if params['complete'] else "incomplete" + update_response += f" Marked as {status}." + responses.append(update_response) + # --- Save Additional AI Response --- + save_chat_message(db, user_id=current_user.id, sender=MessageSender.AI, text=update_response) + # --------------------------------- + return ProcessCommandResponse(responses=responses) + + case "delete_todo": + todo_id = params.get('todo_id') + if todo_id is None: + error_msg = "TODO ID is required for delete." + save_chat_message(db, user_id=current_user.id, sender=MessageSender.AI, text=error_msg) + raise HTTPException(status_code=400, detail=error_msg) + deleted_todo = todo_service.delete_todo(db, todo_id=todo_id, user=current_user) + delete_response = f"Deleted TODO ID {deleted_todo.id}: '{deleted_todo.task}'." + responses.append(delete_response) + # --- Save Additional AI Response --- + save_chat_message(db, user_id=current_user.id, sender=MessageSender.AI, text=delete_response) + # --------------------------------- + return ProcessCommandResponse(responses=responses) + # --- End TODO Cases --- + case _: print(f"Warning: Unhandled intent '{intent}' reached api.py match statement.") # The initial response_text was already saved diff --git a/backend/modules/nlp/service.py b/backend/modules/nlp/service.py index d2722be..f1e2046 100644 --- a/backend/modules/nlp/service.py +++ b/backend/modules/nlp/service.py @@ -9,10 +9,9 @@ from typing import List # Import List # Import the new model and Enum from .models import ChatMessage, MessageSender -# from core.config import settings +from core.config import settings -# client = genai.Client(api_key=settings.GOOGLE_API_KEY) -client = genai.Client(api_key="AIzaSyBrte_mETZJce8qE6cRTSz_fHOjdjlShBk") +client = genai.Client(api_key=settings.GOOGLE_API_KEY) ### Base prompt for MAIA, used for inital user requests SYSTEM_PROMPT = """ @@ -24,10 +23,14 @@ Available functions/intents: 3. add_calendar_event(title: str, description: str, start: datetime, end: Optional[datetime], location: str): Add a new event. 4. update_calendar_event(event_id: int, title: Optional[str], description: Optional[str], start: Optional[datetime], end: Optional[datetime], location: Optional[str]): Update an existing event. Requires event_id. 5. delete_calendar_event(event_id: int): Delete an event. Requires event_id. -6. clarification_needed(request: str): Use this if the user's request is ambiguous or lacks necessary information (like event_id for update/delete). The original user request should be passed in the 'request' parameter. +6. get_todos(): Retrieve the user's TODO list. +7. add_todo(task: str, date: Optional[datetime], remind: Optional[bool]): Add a new task to the user's TODO list. +8. update_todo(todo_id: int, task: Optional[str], date: Optional[datetime], remind: Optional[bool], complete: Optional[bool]): Update an existing TODO item. Requires todo_id. +9. delete_todo(todo_id: int): Delete a TODO item. Requires todo_id. +10. clarification_needed(request: str): Use this if the user's request is ambiguous or lacks necessary information (like event_id or todo_id for update/delete). The original user request should be passed in the 'request' parameter. **IMPORTANT:** Respond ONLY with JSON containing BOTH "intent" and "params", AND a "response_text" field. -- "response_text" should be a friendly, user-facing message confirming the action taken, providing the answer, or asking for clarification. +- "response_text" should be a friendly, user-facing message confirming the action taken, providing the answer, asking for clarification OR can be empty if the query does not require a response to the user. Examples: @@ -38,7 +41,7 @@ MAIA: "params": { "title": "Meeting", "description": "Project X", - "start": "2025-04-19 15:00:00.000000+00:00", + "start": "2025-04-22 15:00:00.000000+00:00", "end": null, "location": null }, @@ -65,6 +68,47 @@ MAIA: "response_text": "Okay, I can help with that. Could you please provide the ID or more specific details about the 'team sync' event you want me to delete?" } +User: Add 'Buy groceries' to my todo list +MAIA: +{ + "intent": "add_todo", + "params": { + "task": "Buy groceries", + "date": null, + "remind": false + }, + "response_text": "I've added 'Buy groceries' to your TODO list." +} + +User: Show me my todos +MAIA: +{ + "intent": "get_todos", + "params": {}, + "response_text": "Okay, fetching your TODO list now." +} + +User: Mark task 15 as complete +MAIA: +{ + "intent": "update_todo", + "params": { + "todo_id": 15, + "complete": true + }, + "response_text": "Got it, I've marked task 15 as complete." +} + +User: Delete task 2 +MAIA: +{ + "intent": "delete_todo", + "params": { + "todo_id": 2 + }, + "response_text": "Okay, I've deleted task 2 from your list." +} + The datetime right now is """+str(datetime.now(timezone.utc))+""". """ diff --git a/backend/modules/todo/__init__.py b/backend/modules/todo/__init__.py new file mode 100644 index 0000000..d6fc651 --- /dev/null +++ b/backend/modules/todo/__init__.py @@ -0,0 +1,2 @@ +# backend/modules/todo/__init__.py +# This file makes the 'todo' directory a Python package. diff --git a/backend/modules/todo/__pycache__/__init__.cpython-312.pyc b/backend/modules/todo/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a4f3d322e5ea7bec99ddcdd5ffe66fc002a7f8b GIT binary patch literal 149 zcmX@j%ge<81Yi5uGGc)AV-N=&d}aZPOlPQM&}8&m$xy@u8PKSn4F!Om!hAWpHiBWTC87^pOUX1AD@|*SrQ+wS5Wzj!v-Q{SHuc5 Tgb|2~L5z>gjEsy$%s>_Zb)X{z literal 0 HcmV?d00001 diff --git a/backend/modules/todo/__pycache__/api.cpython-312.pyc b/backend/modules/todo/__pycache__/api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0cc2bdcd6de6cb3bf6727ca927dd332c3c3b2734 GIT binary patch literal 3268 zcmb_eO>7fK6rR~#uYcBdY$p)%i}_JoQxualr3FeW6e0*sQv^YkEGocvqaC9E2Wst3f)6i%G_-s~DX$OWllCC|K>dHcTi zzIpGh{|JQw1fJRN)l0wn2>BZqjYpio;>li_kXwWiMsp-jQ#3E7q`aJx^GZsgxG&{A zc{Qa9S|FbYbi~fskvZ2lnM#in+xYVQXN9}^p0ifI5S6?#)2$lP=l_7mlbC>71Lw$Z#LX*&a6Y%=HF_r82gqq8dO`qs1T9;Ld$x8YVjM+V<~?Y-*(g_Rx4 zlB-A9&X@Y5ux`DF?Sfwe-^Y4d@O%FQzqdVl6Zxg666wmMxvz-jjMsFEG;% zot~aPJANgrmn@@LuvOE_SYiY>&YxDCDH5TCnqLOBxf?&%Wy|Y zh^m~^O~GF>Q!<81SM9DAvuRK&6^(+$<6tg^b`hpZo(4&F z%GeV)Bnis9VS2=1uod&NQL;TbBX3w)w~3(=QQ;?;c^`yVG9y)F;D!P>u8r$TMRE91 zoS+B54Bv;tei#xiM}ABikroM0^R!75?*L^JZwa`H$N6rM>`vg7Id3%yz^02AVWJ8U z5GJ9njo`p)a9|;}c<|oz?>p9mpH{~=wMcc6?*W}_f#g?+oHp2X+v`g0lVcjxUrE6W z$#Gq-$kmXAu8W6BOkPnMz4RMek%1o1)RLP%_-){VqXhc7i45~K*y9M3P`I!n(h+ma_Ocm=CcKm;f3fHMHR&4y4ZIlh5DX%JI(BY^2Uo+G zKXdmc*TWOlQ=8s!_3J;weT#B!^yGSYyygQwW7R3ar>zMN%OwU$-#(Q_!S*Y8lrWJh zax;~-rICm*33dcojWp66RHxKj8idkwF3jx|bhW(DBmtWMzGn8rAabash~|DfR=bc{ z*Jf(Uj0m`WuKe#t!tsy5^MIh6>CN7;{azcr!>NX@7S(1b?}9O`+_wDy?^BzoDH9@& zsl*=JJDY*lSu!}5JV&lXN^$H0cG&zPm&swvzdBUpdG5tc)w)_T3Ul0t3nKE7Ybb}Z zMXnD)5@cpF(EWqB*W)&_p-kDjG}PMCc?i!tx1Eb#Zt^gmsNDH#DW#%m*$S409l;M* zdgRb>dhFcziRtl^b^!OsM7tH8(3YVxY#AFP_63gFZOf&y>u%wV_j)W}{ zL5Se>nePL~sSo*SIKmHuxf?zTb(vE7kQ`ql$G6DdEz~!{?}4@0+qLMy&5qa)pD%n|>pD~m4?p#L0#a42hl%9-uJ1 zGZ3oFFh9`3bq~y7+#jiXQSu=S>LAK!UBbN>27(xqi`{qom-^BC;dTM|Xy;}qQkP*C zj>GKw4H6oBuP)(US6hPs{Q@I?m1e21h{A*Du8ruv)#$#u2bZqXLUg=F(M1uv5ri4I rF>tf8cI@Qh3 literal 0 HcmV?d00001 diff --git a/backend/modules/todo/__pycache__/models.cpython-312.pyc b/backend/modules/todo/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32eb8e91d2a054c942708cd635a64f30ea2255d5 GIT binary patch literal 1006 zcmZuwJ#W)M7(P4xiXD>{3PM6^%fN?=NF+o%A*7bl5217+UA$OM?ntcO+3DF9q*IVO zz(lsfmI)QL1OJ1uk)Vesjzh7&Ti9%8(u%pbNAkR_uT6*wrv7Fq<32Ttqi~q zLC%MI22NZKb^ruW!hlGwL}XVc1-C#HS0Spa63x{lF)w4C7_O1w0xlBMH8ZSW%e7>% z44}FVpoYx7LcX_K>MD-@OAtq6+LH*UBxL$(n4lJ-OnaWtAZ#&xA&M~aLuM@c391JK zF>@)RC}@SxP}h-Ii6ZPLK@`UApu@Sc;KyjKnHR~Uc1BJ?!^r#RU%(iqXW5 zV!5vm>Ypcu#ktJj$(jOmA^2nkW|f?}8*r eKSAv)s2zbjM_}?hDF0DR>E@eOKacI1>mql2%nbEH9N1WXv0S|>L zq_^Ds8^pW+geR$BSn(ttyorejJ^9{j+9tuFee>qG?_=iu-pj|CnG%6D|H0h2rV;WT z5Btj)DT7y_yd|7)8jz4Wl%lQ$TBtj^()EBjOe6ORXRir21bwgPzK)?#6VQS%4p38} z7NEs3+k!$%fR@K>t&VP2equPAX2siHB35E90ytQY+NmE$UeIji*>X8bO$LwQ{FVsf zP);0ejR;*ZPHzIrHOCO9C_Fnd?wITIuz{Vf?3iPTBAkrTsiaOOo;sRc$gI_v$9SiV zrC##9nKACA;$CJ-5&99&tX3Sh1Ca{5oLR0Ld7*IKtmL|3%r^tnE3W%u(+fsl^dyy8 z)pflnic=3?m$~Ksj;&B zaJjMWwVsQJH}a%OBZW&hlGcU@z2tJcBTevB0+r+nh&QBPn%REXzi@HuZudg(#+TWp zPw-bdzsoN6XXm!=b?@%7x&F!8)`Kqp%xZFWKg0wT6i{4)3W!50I@H#(5|-|c6!4&? zt;;f4NHmi(D2}60K~JD|YG0s5f{po_^qG}g;Kzxy2OS{c%VF9zXkZ6=Kp2W+lWU4q z>J$L8o*f0_sWr08dx1L838FN@*vPj$hN&)r$C>%#*4oHcQx+g<&LNVf=&~!*4^~FV zvBaK~)dL^AmSS5TLk-EEuFf1{D9|fe1_Aul>WaFnJC}A@{r^OsSp*^<^j3G8yKJ$) za1O}a=%qW+XLc^HEXJIuEM5@T;5vx$${x;^(o|*DIO{OZ>^=On;Bt(P0o7qheWv*^ zAMwjbNZ02RSq|C;-+q_{*@LOm_sZyh`A!@?@t>|XZ8lZQtolU8q3flo^w&32kxbPq zGjms2hww;eg2ji1s?~R@`NPiw(6r6zz`7r$IW=f76;{>|Lva%R6YNT$!76Xx?;km~ zz4Dh)W8pUek*7dIyO>$+4)>zeZ%ORh%uG9NKYFUBT`KC;aH{@SG^-?&)3ImW%qRPn zT!I)_z2p{%Upl4q2dRG}r@xZwkTH6?+Zhtrh80SSy}2QQZD=yO)SE|qX;?AnLiffW I0-L(!KR^67?*IS* literal 0 HcmV?d00001 diff --git a/backend/modules/todo/__pycache__/service.cpython-312.pyc b/backend/modules/todo/__pycache__/service.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b774be9949010f8cd256098079b2d8f3213a4e8c GIT binary patch literal 3104 zcmd5;O>7fK6yEi&cb#mqfRl!{h$;lC#I#B(@s|Rkikb$Js0k`bD;Y}Uc*c0`bsT2b zK#HS=DpC`vDj*>xRS(U915qN8s)uqcQY8-UA&aSuR#a4~df-&I>4{U{oAo+B1bXer zo|!lA?VF$X&G+V~+S+OXO6aPR{4*#BzoX!ns&Z%LK6GXTA`sCOGGbH|1ri{V8OTUc zDI-T^kOd-}!Hg1Bct2>WnNT#8sft#CtWc4tR8H0Vj#pa}qDn#`Rg-d5BX!&oMo2ZS zAsVeogh5M|tIEsAdEkC*dU(@O!H*2^d0NOhG6%mWA@bO_7NCKnlHX zD28}b{8Aj3@(kUS#^|_(!#SzMe3^Ff7 zREEoT*$!P#Tkv+l)GXt zgDVyO)^VsNghzV))%0}Q(GSmde$)L`_t(A9UE1?~^S90S^}`d#AIi$-U01rM4o&uB zxBE)>WG{AluJlY@d?0UssD&r`R=^c((n=enZqPJfQ*m6eU$D=kEx>8d;7M2F?VAXK zU5R{Z?YCtEKTauJ`iAo2FH@ln<{$!^_!VETNw)|>WuDDq9jIuxtp5_FRV1XgJ*)L` z1PBrPB2{kie2%g)4i2^z%v~j6m^Nih2Z4q8NH&qMsLghuAjM46#|J`#y^0m8D%fD# z(Xi?qwLQ$RT_B@7YXGVV;h|Q0wSBt%M%PU5_1=QkSk#&wt$9(ot<7u2mZMI~(I4b{ z>Rr`oIbP6C6t#Xw>t8-OP|ya8+G$5Sy?iEC(1wdz!qE~1Ex9Zw*(*4sif{w8ue;!g zdLHt_|C^9wc>e<`n}7mnWfK5#sFVSl+5{?oxPf@2i5+$YE;h!RmlVc$Lm}c~V4>{w zGcoVc$tOB;N8?D=ZcJozBLoOn^B8sr8NgKtwPS`Esb)LD-72COCIF5kjwv^oFqmaC z+zdl1bi+v7L;K&_Kh)PhFx1sQ*w;B0f zE?UqNMcs6Cv!IUv>!|v~r;qdLIR7G#4sMr2VazWI)#rRl4l@ldSLi7( z=aszb&m;<8)XPYGn&G=sStpkmvM;j4qa1XU!o#?f9Jtwi0`yrUh0^NqPunvS`_3o$cCErbvz zBP<^2_@)>Uwhz=uMHYn$6HLQw)7-hG&IkJ25ZLg}Vz|i(H!amKhnouFgA=_E8+H{N znw^H`LPO+lK@7e=T|FgD4L%BOxu(w6&C*{&drA@v|MuMU!||{={Sa8g+V=&AKwu37 z@MW+G1XkIchvfxt576U94fL@$VXlb8j~#ENNrBJ#Ndxh0yhNI(_fqmf&^MwDM&Owf z^R_*8?t$E}nt?Iv@@@A;0t_kR3GOr!c6IB#88h*XHrASD8HRgv%d~(O!>ud75?W(9 zJK5^J>KMY-RTD8FmQiC3M^q#Cyvtt?{NdnA#)!=mi?>a#ALLh%$DQF;7}6bI97o5~ z&{1#7W9%JJVCuKNg9?Hwihl@AkA#iLy3DC?PD(*hu>^{JSI|C~? z_LnY-hIp;HBw#yN@3ps1dF`E3D}219$lzsrNq}Z?@UnJUEDgXcg literal 0 HcmV?d00001 diff --git a/backend/modules/todo/api.py b/backend/modules/todo/api.py new file mode 100644 index 0000000..ae57ceb --- /dev/null +++ b/backend/modules/todo/api.py @@ -0,0 +1,62 @@ +# backend/modules/todo/api.py +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import List + +from . import service, schemas +from core.database import get_db +from modules.auth.dependencies import get_current_user # Corrected import +from modules.auth.models import User # Assuming User model is in auth.models + +router = APIRouter( + prefix="/todos", + tags=["todos"], + dependencies=[Depends(get_current_user)], # Corrected dependency + responses={404: {"description": "Not found"}}, +) + +@router.post("/", response_model=schemas.Todo, status_code=status.HTTP_201_CREATED) +def create_todo_endpoint( + todo: schemas.TodoCreate, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) # Corrected dependency +): + return service.create_todo(db=db, todo=todo, user=current_user) + +@router.get("/", response_model=List[schemas.Todo]) +def read_todos_endpoint( + skip: int = 0, + limit: int = 100, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) # Corrected dependency +): + todos = service.get_todos(db=db, user=current_user, skip=skip, limit=limit) + return todos + +@router.get("/{todo_id}", response_model=schemas.Todo) +def read_todo_endpoint( + todo_id: int, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) # Corrected dependency +): + db_todo = service.get_todo(db=db, todo_id=todo_id, user=current_user) + if db_todo is None: + raise HTTPException(status_code=404, detail="Todo not found") + return db_todo + +@router.put("/{todo_id}", response_model=schemas.Todo) +def update_todo_endpoint( + todo_id: int, + todo_update: schemas.TodoUpdate, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) # Corrected dependency +): + return service.update_todo(db=db, todo_id=todo_id, todo_update=todo_update, user=current_user) + +@router.delete("/{todo_id}", response_model=schemas.Todo) +def delete_todo_endpoint( + todo_id: int, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) # Corrected dependency +): + return service.delete_todo(db=db, todo_id=todo_id, user=current_user) diff --git a/backend/modules/todo/models.py b/backend/modules/todo/models.py new file mode 100644 index 0000000..2b09fdd --- /dev/null +++ b/backend/modules/todo/models.py @@ -0,0 +1,17 @@ +# backend/modules/todo/models.py +from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey +from sqlalchemy.orm import relationship +from core.database import Base +import datetime + +class Todo(Base): + __tablename__ = "todos" + + id = Column(Integer, primary_key=True, index=True) + task = Column(String, index=True, nullable=False) + date = Column(DateTime, nullable=True) + remind = Column(Boolean, default=False) + complete = Column(Boolean, default=False) + owner_id = Column(Integer, ForeignKey("users.id")) + + owner = relationship("User") # Add relationship if needed, assuming User model exists in auth.models diff --git a/backend/modules/todo/schemas.py b/backend/modules/todo/schemas.py new file mode 100644 index 0000000..a857393 --- /dev/null +++ b/backend/modules/todo/schemas.py @@ -0,0 +1,26 @@ +# backend/modules/todo/schemas.py +from pydantic import BaseModel +from typing import Optional +import datetime + +class TodoBase(BaseModel): + task: str + date: Optional[datetime.datetime] = None + remind: bool = False + complete: bool = False + +class TodoCreate(TodoBase): + pass + +class TodoUpdate(BaseModel): + task: Optional[str] = None + date: Optional[datetime.datetime] = None + remind: Optional[bool] = None + complete: Optional[bool] = None + +class Todo(TodoBase): + id: int + owner_id: int + + class Config: + from_attributes = True diff --git a/backend/modules/todo/service.py b/backend/modules/todo/service.py new file mode 100644 index 0000000..bff56f6 --- /dev/null +++ b/backend/modules/todo/service.py @@ -0,0 +1,36 @@ +# backend/modules/todo/service.py +from sqlalchemy.orm import Session +from . import models, schemas +from modules.auth.models import User # Assuming User model is in auth.models +from fastapi import HTTPException, status + +def create_todo(db: Session, todo: schemas.TodoCreate, user: User): + db_todo = models.Todo(**todo.dict(), owner_id=user.id) + db.add(db_todo) + db.commit() + db.refresh(db_todo) + return db_todo + +def get_todos(db: Session, user: User, skip: int = 0, limit: int = 100): + return db.query(models.Todo).filter(models.Todo.owner_id == user.id).offset(skip).limit(limit).all() + +def get_todo(db: Session, todo_id: int, user: User): + db_todo = db.query(models.Todo).filter(models.Todo.id == todo_id, models.Todo.owner_id == user.id).first() + if db_todo is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Todo not found") + return db_todo + +def update_todo(db: Session, todo_id: int, todo_update: schemas.TodoUpdate, user: User): + db_todo = get_todo(db=db, todo_id=todo_id, user=user) # Reuse get_todo to check ownership and existence + update_data = todo_update.dict(exclude_unset=True) + for key, value in update_data.items(): + setattr(db_todo, key, value) + db.commit() + db.refresh(db_todo) + return db_todo + +def delete_todo(db: Session, todo_id: int, user: User): + db_todo = get_todo(db=db, todo_id=todo_id, user=user) # Reuse get_todo to check ownership and existence + db.delete(db_todo) + db.commit() + return db_todo diff --git a/interfaces/nativeapp/src/api/client.ts b/interfaces/nativeapp/src/api/client.ts index 5999b98..616f07c 100644 --- a/interfaces/nativeapp/src/api/client.ts +++ b/interfaces/nativeapp/src/api/client.ts @@ -108,11 +108,9 @@ apiClient.interceptors.response.use( } console.log('[API Client] Attempting token refresh...'); - // Send refresh token in the body, remove withCredentials const refreshResponse = await apiClient.post('/auth/refresh', { refresh_token: storedRefreshToken }, // Send token in body { - // No withCredentials needed headers: { 'Content-Type': 'application/json' }, } ); diff --git a/interfaces/nativeapp/src/api/todo.ts b/interfaces/nativeapp/src/api/todo.ts new file mode 100644 index 0000000..0ba6520 --- /dev/null +++ b/interfaces/nativeapp/src/api/todo.ts @@ -0,0 +1,58 @@ +// interfaces/nativeapp/src/api/todo.ts +import apiClient from './client'; +import { Todo, TodoCreate, TodoUpdate } from '../types/todo'; + +export const getTodos = async (skip: number = 0, limit: number = 100): Promise => { + try { + const response = await apiClient.get('/todos/', { params: { skip, limit } }); + console.log("[TODO] Got todos:", response.data); + return response.data; + } catch (error) { + console.error("Error fetching todos", error); + throw error; + } +}; + +export const getTodoById = async (todo_id: number): Promise => { + try { + const response = await apiClient.get(`/todos/${todo_id}`); + console.log("[TODO] Got todo:", response.data); + return response.data; + } catch (error) { + console.error(`Error fetching todo ${todo_id}`, error); + throw error; + } +}; + +export const createTodo = async (todo: TodoCreate): Promise => { + try { + const response = await apiClient.post('/todos/', todo); + console.log("[TODO] Created todo:", response.data); + return response.data; + } catch (error) { + console.error("Error creating todo", error); + throw error; + } +}; + +export const updateTodo = async (todo_id: number, todo: TodoUpdate): Promise => { + try { + const response = await apiClient.put(`/todos/${todo_id}`, todo); + console.log("[TODO] Updated todo:", response.data); + return response.data; + } catch (error) { + console.error(`Error updating todo ${todo_id}`, error); + throw error; + } +}; + +export const deleteTodo = async (todo_id: number): Promise => { // Backend returns the deleted item + try { + const response = await apiClient.delete(`/todos/${todo_id}`); + console.log("[TODO] Deleted todo:", response.data); + return response.data; // Return the data which is the deleted todo object + } catch (error) { + console.error(`Error deleting todo ${todo_id}`, error); + throw error; + } +}; diff --git a/interfaces/nativeapp/src/screens/DashboardScreen.tsx b/interfaces/nativeapp/src/screens/DashboardScreen.tsx index e271202..806beaf 100644 --- a/interfaces/nativeapp/src/screens/DashboardScreen.tsx +++ b/interfaces/nativeapp/src/screens/DashboardScreen.tsx @@ -1,20 +1,220 @@ // src/screens/DashboardScreen.tsx -import React, { useState, useEffect } from 'react'; // Added useEffect -import { View, StyleSheet, ScrollView } from 'react-native'; // Added ScrollView -import { Text, TextInput, Button, useTheme, Card, List, Divider } from 'react-native-paper'; // Added Card, List, Divider +import React, { useState, useEffect, useCallback } from 'react'; // Added useCallback +import { View, StyleSheet, ScrollView, TouchableOpacity } from 'react-native'; // Added TouchableOpacity +import { Text, TextInput, Button, useTheme, Card, List, Divider, Checkbox, IconButton, ActivityIndicator } from 'react-native-paper'; // Added Checkbox, IconButton, ActivityIndicator import { useNavigation } from '@react-navigation/native'; import { StackNavigationProp } from '@react-navigation/stack'; import { format, addDays, startOfDay, isSameDay, parseISO, endOfDay } from 'date-fns'; // Added date-fns imports import { getCalendarEvents } from '../api/calendar'; import { CalendarEvent } from '../types/calendar'; +import { Todo, TodoCreate, TodoUpdate } from '../types/todo'; // Import TODO types +import { getTodos, createTodo, updateTodo, deleteTodo } from '../api/todo'; // Import TODO API functions -// Placeholder for the TODO component -const TodoComponent = () => ( - - TODO Component Placeholder - -); +// --- TODO Component Implementation --- +const TodoComponent = () => { + const theme = useTheme(); + const [todos, setTodos] = useState([]); + const [newTask, setNewTask] = useState(''); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchTodos = useCallback(async () => { + setLoading(true); + setError(null); + try { + const fetchedTodos = await getTodos(); + // Add explicit types for sort parameters + setTodos(fetchedTodos.sort((a: Todo, b: Todo) => a.id - b.id)); + } catch (err) { + console.error("Failed to fetch todos:", err); + setError("Failed to load TODOs."); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchTodos(); + }, [fetchTodos]); + + const handleAddTask = async () => { + const trimmedTask = newTask.trim(); + if (!trimmedTask) return; + + const newTodoData: TodoCreate = { task: trimmedTask }; + try { + const createdTodo = await createTodo(newTodoData); + setTodos(prevTodos => [...prevTodos, createdTodo]); + setNewTask(''); // Clear input + } catch (err) { + console.error("Failed to add todo:", err); + setError("Failed to add TODO."); // Show error feedback + } + }; + + const handleToggleComplete = async (todo: Todo) => { + const updatedTodoData: TodoUpdate = { complete: !todo.complete }; + try { + const updatedTodo = await updateTodo(todo.id, updatedTodoData); + setTodos(prevTodos => + prevTodos.map(t => (t.id === todo.id ? updatedTodo : t)) + ); + } catch (err) { + console.error("Failed to update todo:", err); + setError("Failed to update TODO status."); + } + }; + + const handleDeleteTask = async (id: number) => { + try { + await deleteTodo(id); + setTodos(prevTodos => prevTodos.filter(t => t.id !== id)); + } catch (err) { + console.error("Failed to delete todo:", err); + setError("Failed to delete TODO."); + } + }; + + const styles = StyleSheet.create({ + card: { + marginVertical: 8, + backgroundColor: theme.colors.surface, // Use surface color for card background + }, + cardTitle: { + fontSize: 18, + fontWeight: 'bold', + paddingLeft: 16, + paddingTop: 12, + paddingBottom: 8, + color: theme.colors.primary, + }, + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingBottom: 12, + }, + textInput: { + flex: 1, + marginRight: 8, + backgroundColor: theme.colors.background, // Match background + }, + listItem: { + paddingVertical: 0, // Reduced padding + paddingLeft: 8, // Adjust left padding + backgroundColor: theme.colors.surface, // Ensure item background matches card + minHeight: 48, + }, + listCheckboxContainer: { + justifyContent: 'center', + height: '100%', + marginRight: 8, + }, + listItemContent: { + marginLeft: -8, // Counteract default List.Item padding if needed + }, + taskText: { + fontSize: 15, + color: theme.colors.onSurface, + }, + completedTaskText: { + textDecorationLine: 'line-through', + color: theme.colors.onSurfaceDisabled, + }, + deleteButton: { + marginRight: -8, // Align delete button better + }, + loadingContainer: { + padding: 20, + alignItems: 'center', + justifyContent: 'center', + }, + errorText: { + paddingHorizontal: 16, + paddingBottom: 12, + color: theme.colors.error, + }, + divider: { + marginHorizontal: 16, + } + }); + + return ( + + + TODO List + + {/* Add New Task Input */} + + + + + + {/* Loading Indicator */} + {loading && ( + + + + )} + + {/* Error Message */} + {error && !loading && {error}} + + {/* TODO List */} + {!loading && !error && todos.length === 0 && ( + No tasks yet. Add one above! + )} + {!loading && !error && todos.map((todo, index) => ( + + + {todo.task} + + } + titleNumberOfLines={2} // Allow wrapping + left={props => ( + + handleToggleComplete(todo)} + color={theme.colors.primary} + /> + + )} + right={props => ( + handleDeleteTask(todo.id)} + iconColor={theme.colors.error} + style={styles.deleteButton} + size={20} + /> + )} + contentStyle={styles.listItemContent} + /> + {index < todos.length - 1 && } + + ))} + + + ); +}; +// --- End TODO Component --- // --- Calendar Preview Component Implementation --- const CalendarPreview = () => { diff --git a/interfaces/nativeapp/src/types/todo.ts b/interfaces/nativeapp/src/types/todo.ts new file mode 100644 index 0000000..745b679 --- /dev/null +++ b/interfaces/nativeapp/src/types/todo.ts @@ -0,0 +1,23 @@ +// interfaces/nativeapp/src/types/todo.ts +export interface Todo { + id: number; + task: string; + date?: string | null; // Assuming date comes as ISO string or null + remind: boolean; + complete: boolean; + owner_id: number; +} + +export interface TodoCreate { + task: string; + date?: string | null; + remind?: boolean; + complete?: boolean; // Usually not set on creation, defaults to false +} + +export interface TodoUpdate { + task?: string; + date?: string | null; + remind?: boolean; + complete?: boolean; +}