37
37
from textual .widgets import Header
38
38
from textual .widgets import Input
39
39
from textual .widgets import Placeholder
40
+ from textual .widgets import Select
40
41
from textual .widgets import Static
41
42
from textual .widgets import Switch
42
43
from textual_plotext import PlotextPlot
43
44
44
45
import thymed
46
+ from thymed import ThymedError
45
47
from thymed import TimeCard
46
48
47
49
@@ -200,7 +202,7 @@ class Statblock(Container):
200
202
"""A Block of statistics."""
201
203
202
204
timecard : reactive [TimeCard | None ] = reactive (None , recompose = True )
203
- period : reactive [str | None ] = reactive ("Period " , recompose = True )
205
+ period : reactive [str | None ] = reactive ("Week " , recompose = True )
204
206
delta : reactive [timedelta | None ] = reactive (timedelta (days = 7 ), recompose = True )
205
207
206
208
def compose (self ) -> ComposeResult :
@@ -283,22 +285,24 @@ def replot(self) -> None:
283
285
card = TimeCard (self .code )
284
286
end = datetime .today ()
285
287
start = end - self .delta
286
- df = card .general_report (start , end )
287
- df ["clock_in_day" ] = df .clock_in .dt .strftime ("%d/%m/%Y" )
288
- # TODO: Make the plot show an exact range, whether or not work entries are present. (Create a PR later.)
289
- # Create a new date range with daily increments over the full range.
290
- # The punches increments are variable (eg you may not work every day)
291
- # new_clock_in_day = pd.date_range(start, end)
292
- # Reindex the dataframe on the new range, filling blanks with an int of zero.
293
- # df = df.set_index("clock_in_day").reindex(new_clock_in_day, fill_value=0).reset_index()
294
- # Need to convert the clock_in to string for plotext
295
- dates = df .clock_in_day
296
- plt .clear_data ()
297
- plt .bar (dates , df .hours )
298
- plt .title (self .name )
299
- plt .xlabel ("Date" )
300
- plt .ylabel ("Hours" )
301
- self .plot .refresh ()
288
+ try :
289
+ df = card .general_report (start , end )
290
+ df ["clock_in_day" ] = df .clock_in .dt .strftime ("%d/%m/%Y" )
291
+ dates = df .clock_in_day
292
+ # TODO: Make the plot show an exact range, whether or not work entries are present. (Create a PR later.)
293
+ plt .clear_data ()
294
+ plt .bar (dates , df .hours )
295
+ plt .title (self .name )
296
+ plt .xlabel ("Date" )
297
+ plt .ylabel ("Hours" )
298
+
299
+ plt .xlim (left = datetime .strftime (start , "%d/%m/%Y" ))
300
+
301
+ self .plot .refresh ()
302
+ except ThymedError :
303
+ self .notify (
304
+ "Problem with that ChargeCode..." , severity = "error" , title = "Error"
305
+ )
302
306
303
307
@textual .on (Button .Pressed , "#period" )
304
308
def cycle_period (self ) -> None :
@@ -421,6 +425,52 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
421
425
self .app .pop_screen ()
422
426
423
427
428
+ class RemoveScreen (ModalScreen ):
429
+ """Screen with a dialog to remove a ChargeCode."""
430
+
431
+ def get_data (self ) -> list :
432
+ """Function to retrieve Thymed data."""
433
+ with open (thymed ._CHARGES ) as f :
434
+ try :
435
+ codes = json .load (f , object_hook = thymed .object_decoder )
436
+
437
+ # Sort the codes dictionary by key (code id)
438
+ sorted_codes = sorted (codes .items (), key = lambda kv : int (kv [0 ]))
439
+ codes = [x [1 ] for x in sorted_codes ]
440
+ except json .JSONDecodeError : # pragma: no cover
441
+ self .notify ("Got JSON Error" , severity = "error" )
442
+ # If the file is completely blank, we will get an error
443
+ codes = [("No Codes Found" , 0 )]
444
+
445
+ out = []
446
+ for code in codes :
447
+ out .append ((code .name , str (code .id )))
448
+
449
+ return out
450
+
451
+ def compose (self ) -> ComposeResult :
452
+ yield Grid (
453
+ Title ("Remove ChargeCode information" , id = "question" ),
454
+ Static ("ID Number: " , classes = "right" ),
455
+ Select (options = self .get_data (), id = "charge_id" ),
456
+ Static (
457
+ "THIS WILL IMMEDIATELY DELETE THE CODE AND ALL PUNCH DATA! IT CANNOT BE UNDONE!" ,
458
+ id = "warning" ,
459
+ ),
460
+ Button ("DELETE IT" , variant = "error" , id = "submit" ),
461
+ Button ("Cancel" , variant = "primary" , id = "cancel" ),
462
+ id = "dialog" ,
463
+ )
464
+
465
+ def on_button_pressed (self , event : Button .Pressed ) -> None :
466
+ charge_id = self .query_one ("#charge_id" ).value
467
+ data = [charge_id ]
468
+ if event .button .id == "submit" :
469
+ self .dismiss (data )
470
+ else :
471
+ self .app .pop_screen ()
472
+
473
+
424
474
class ThymedApp (App [None ]):
425
475
CSS_PATH = "thymed.tcss"
426
476
TITLE = "Thymed App"
@@ -564,7 +614,7 @@ def option_buttons(self, event: Button.Pressed) -> None:
564
614
self .action_launch_settings ()
565
615
566
616
@textual .on (Button .Pressed , "#add" )
567
- def code_screen (self , event : Button .Pressed ):
617
+ def code_screen (self , event : Button .Pressed ) -> None :
568
618
"""When we want to add a chargecode.
569
619
570
620
When the AddScreen is dismissed, it will call the
@@ -590,6 +640,32 @@ def add_code(data: list):
590
640
591
641
self .push_screen (AddScreen (), add_code )
592
642
643
+ @textual .on (Button .Pressed , "#remove" )
644
+ def remove_screen (self , event : Button .Pressed ) -> None :
645
+ """When we want to remove a chargecode.
646
+
647
+ When the RemoveScreen is dismissed, it will call the
648
+ callback function below.
649
+ """
650
+
651
+ def remove_code (data : list ):
652
+ """Method to actually remove the ChargeCode and data.
653
+
654
+ This method gets called after the RemoveScreen is dismissed.
655
+ It takes data and calls the base Thymed methods to remove
656
+ a ChargeCode object and it's corresponding punch data.
657
+
658
+ After we finish removing the code, we call get_data on
659
+ the ChargeManager screen to refresh the table.
660
+ """
661
+ id = data [0 ]
662
+ thymed .delete_charge (id )
663
+
664
+ applet = self .query_one ("#applet" )
665
+ applet .data = applet .get_data ()
666
+
667
+ self .push_screen (RemoveScreen (), remove_code )
668
+
593
669
def action_open_link (self , link : str ) -> None :
594
670
self .app .bell ()
595
671
import webbrowser
0 commit comments