@ -4,7 +4,7 @@ import { RouterLink } from "@angular/router";
import { FormsModule } from "@angular/forms" ;
import { FormsModule } from "@angular/forms" ;
import { MATERIAL } from "../shared/material" ;
import { MATERIAL } from "../shared/material" ;
import { ApiService } from "../core/api.service" ;
import { ApiService } from "../core/api.service" ;
import { Dashboard , Entry } from "../core/models" ;
import { Dashboard , Entry , Settings } from "../core/models" ;
@Component ( {
@Component ( {
standalone : true ,
standalone : true ,
imports : [ RouterLink , FormsModule , DatePipe , DecimalPipe , . . . MATERIAL ] ,
imports : [ RouterLink , FormsModule , DatePipe , DecimalPipe , . . . MATERIAL ] ,
@ -30,9 +30,9 @@ import { Dashboard, Entry } from "../core/models";
< button class = "timer-stop" mat - flat - button color = "warn" ( click ) = " stopTimer ( timer ) " > < mat - icon > stop < / m a t - i c o n > T i m e r s t o p p e n < / b u t t o n >
< button class = "timer-stop" mat - flat - button color = "warn" ( click ) = " stopTimer ( timer ) " > < mat - icon > stop < / m a t - i c o n > T i m e r s t o p p e n < / b u t t o n >
} @else {
} @else {
< div > < mat - icon > timer < / m a t - i c o n > < s p a n > Z e i t e r f a s s u n g < / s p a n > < s t r o n g > T i m e r s t a r t e n < / s t r o n g > < / d i v >
< div > < mat - icon > timer < / m a t - i c o n > < s p a n > Z e i t e r f a s s u n g < / s p a n > < s t r o n g > T i m e r s t a r t e n < / s t r o n g > < / d i v >
< mat - form - field appearance = "outline" > < mat - label > Ausbildung < / m a t - l a b e l > < m a t - s e l e c t [ ( n g M o d e l ) ] = " t i m e r T y p e " > @ f o r ( t o f t y p e s ; t r a c k t ) { < m a t - o p t i o n [ v a l u e ] = " t " > { { t } } < / m a t - o p t i o n > } < / m a t - s e l e c t > < / m a t - f o r m - f i e l d >
< mat - form - field appearance = "outline" > < mat - label > Ausbildung < / m a t - l a b e l > < m a t - s e l e c t [ ( n g M o d e l ) ] = " t i m e r T y p e " > @ f o r ( t o f t y p e s ( ) ; t r a c k t ) { < m a t - o p t i o n [ v a l u e ] = " t " > { { t y p e L a b e l ( t ) } } < / m a t - o p t i o n > } < / m a t - s e l e c t > < / m a t - f o r m - f i e l d >
< mat - form - field appearance = "outline" > < mat - label > Art < / m a t - l a b e l > < m a t - s e l e c t [ ( n g M o d e l ) ] = " t i m e r A r t " > @ f o r ( a o f t i m e r A r t s ( ) ; t r a c k a ) { < m a t - o p t i o n [ v a l u e ] = " a " > { { a } } < / m a t - o p t i o n > } < / m a t - s e l e c t > < / m a t - f o r m - f i e l d >
< mat - form - field appearance = "outline" > < mat - label > Art < / m a t - l a b e l > < m a t - s e l e c t [ ( n g M o d e l ) ] = " t i m e r A r t " > @ f o r ( a o f t i m e r A r t s ( ) ; t r a c k a ) { < m a t - o p t i o n [ v a l u e ] = " a " > { { a } } < / m a t - o p t i o n > } < / m a t - s e l e c t > < / m a t - f o r m - f i e l d >
< button class = "timer-start" mat - flat - button ( click ) = " startTimer ( ) " > < mat - icon > play_arrow < / m a t - i c o n > T i m e r s t a r t e n < / b u t t o n >
< button class = "timer-start" mat - flat - button [ disabled ] = " ! timerType | | ! timerArt " ( click ) = " startTimer ( ) " > < mat - icon > play_arrow < / m a t - i c o n > T i m e r s t a r t e n < / b u t t o n >
}
}
< / m a t - c a r d - c o n t e n t >
< / m a t - c a r d - c o n t e n t >
< / m a t - c a r d >
< / m a t - c a r d >
@ -49,8 +49,8 @@ import { Dashboard, Entry } from "../core/models";
> < mat - label > Ausbildung < / m a t - l a b e l
> < mat - label > Ausbildung < / m a t - l a b e l
> < mat - select [ ( ngModel ) ] = " typ " ( selectionChange ) = " load ( ) "
> < mat - select [ ( ngModel ) ] = " typ " ( selectionChange ) = " load ( ) "
> < mat - option value = "" > Alle < / m a t - o p t i o n >
> < mat - option value = "" > Alle < / m a t - o p t i o n >
@for ( t of types ; track t ) {
< mat - option [ value ] = " t " > { { t } } < / m a t - o p t i o n >
@for ( t of fil terT ypes( ) ; track t ) {
< mat - option [ value ] = " t " > { { typeLabel ( t ) } } < / m a t - o p t i o n >
}
}
< / m a t - s e l e c t > < / m a t - f o r m - f i e l d
< / m a t - s e l e c t > < / m a t - f o r m - f i e l d
> < mat - form - field appearance = "outline" > < mat - label > Von Jahr < / m a t - l a b e l > < m a t - s e l e c t [ ( n g M o d e l ) ] = " m i n Y e a r " > < m a t - o p t i o n v a l u e = " " > A l l e < / m a t - o p t i o n > @ f o r ( y o f y e a r s ( ) ; t r a c k y ) { < m a t - o p t i o n [ v a l u e ] = " y " > { { y } } < / m a t - o p t i o n > } < / m a t - s e l e c t > < / m a t - f o r m - f i e l d
> < mat - form - field appearance = "outline" > < mat - label > Von Jahr < / m a t - l a b e l > < m a t - s e l e c t [ ( n g M o d e l ) ] = " m i n Y e a r " > < m a t - o p t i o n v a l u e = " " > A l l e < / m a t - o p t i o n > @ f o r ( y o f y e a r s ( ) ; t r a c k y ) { < m a t - o p t i o n [ v a l u e ] = " y " > { { y } } < / m a t - o p t i o n > } < / m a t - s e l e c t > < / m a t - f o r m - f i e l d
@ -74,7 +74,7 @@ import { Dashboard, Entry } from "../core/models";
> < ng - container matColumnDef = "type"
> < ng - container matColumnDef = "type"
> < th mat - header - cell * matHeaderCellDef > Ausbildung < / th >
> < th mat - header - cell * matHeaderCellDef > Ausbildung < / th >
< td mat - cell * matCellDef = "let e" >
< td mat - cell * matCellDef = "let e" >
< strong > { { e . praktikums_typ } } < / strong >
< strong > { { typeLabel ( e . praktikums_typ ) } } < / strong >
< / td > < / n g - c o n t a i n e r
< / td > < / n g - c o n t a i n e r
> < ng - container matColumnDef = "art" > < th mat - header - cell * matHeaderCellDef > Art < / th > < td mat - cell * matCellDef = "let e" > { { e . entry_art } } < / td > < / n g - c o n t a i n e r
> < ng - container matColumnDef = "art" > < th mat - header - cell * matHeaderCellDef > Art < / th > < td mat - cell * matCellDef = "let e" > { { e . entry_art } } < / td > < / n g - c o n t a i n e r
> < ng - container matColumnDef = "time"
> < ng - container matColumnDef = "time"
@ -206,6 +206,16 @@ import { Dashboard, Entry } from "../core/models";
tr . mat - mdc - row . type - mediation :nth - of - type ( even ) > td {
tr . mat - mdc - row . type - mediation :nth - of - type ( even ) > td {
background - color : rgba ( 253 , 126 , 20 , 0.135 ) ;
background - color : rgba ( 253 , 126 , 20 , 0.135 ) ;
}
}
tr . mat - mdc - row . type - psychotherapie :nth - of - type ( odd ) > td { background - color :rgba ( 124 , 58 , 237 , . 055 ) ; }
tr . mat - mdc - row . type - psychotherapie :nth - of - type ( even ) > td { background - color :rgba ( 124 , 58 , 237 , . 115 ) ; }
tr . mat - mdc - row . type - lsb :nth - of - type ( odd ) > td { background - color :rgba ( 219 , 39 , 119 , . 055 ) ; }
tr . mat - mdc - row . type - lsb :nth - of - type ( even ) > td { background - color :rgba ( 219 , 39 , 119 , . 115 ) ; }
tr . mat - mdc - row . type - coaching :nth - of - type ( odd ) > td { background - color :rgba ( 8 , 145 , 178 , . 055 ) ; }
tr . mat - mdc - row . type - coaching :nth - of - type ( even ) > td { background - color :rgba ( 8 , 145 , 178 , . 115 ) ; }
tr . mat - mdc - row . type - supervision :nth - of - type ( odd ) > td { background - color :rgba ( 202 , 138 , 4 , . 055 ) ; }
tr . mat - mdc - row . type - supervision :nth - of - type ( even ) > td { background - color :rgba ( 202 , 138 , 4 , . 115 ) ; }
tr . mat - mdc - row . type - sonstige :nth - of - type ( odd ) > td { background - color :rgba ( 100 , 116 , 139 , . 055 ) ; }
tr . mat - mdc - row . type - sonstige :nth - of - type ( even ) > td { background - color :rgba ( 100 , 116 , 139 , . 115 ) ; }
tr . mat - mdc - row . type - propaedeutikum :hover > td {
tr . mat - mdc - row . type - propaedeutikum :hover > td {
background - color : rgba ( 13 , 110 , 253 , 0.17 ) ;
background - color : rgba ( 13 , 110 , 253 , 0.17 ) ;
}
}
@ -215,9 +225,19 @@ import { Dashboard, Entry } from "../core/models";
tr . mat - mdc - row . type - mediation :hover > td {
tr . mat - mdc - row . type - mediation :hover > td {
background - color : rgba ( 253 , 126 , 20 , 0.19 ) ;
background - color : rgba ( 253 , 126 , 20 , 0.19 ) ;
}
}
tr . mat - mdc - row . type - psychotherapie :hover > td { background - color :rgba ( 124 , 58 , 237 , . 17 ) ; }
tr . mat - mdc - row . type - lsb :hover > td { background - color :rgba ( 219 , 39 , 119 , . 17 ) ; }
tr . mat - mdc - row . type - coaching :hover > td { background - color :rgba ( 8 , 145 , 178 , . 17 ) ; }
tr . mat - mdc - row . type - supervision :hover > td { background - color :rgba ( 202 , 138 , 4 , . 17 ) ; }
tr . mat - mdc - row . type - sonstige :hover > td { background - color :rgba ( 100 , 116 , 139 , . 17 ) ; }
tr . mat - mdc - row . type - propaedeutikum > td :first - child { border - left - color : # 0 d6efd ; }
tr . mat - mdc - row . type - propaedeutikum > td :first - child { border - left - color : # 0 d6efd ; }
tr . mat - mdc - row . type - fachspezifikum > td :first - child { border - left - color : # 198754 ; }
tr . mat - mdc - row . type - fachspezifikum > td :first - child { border - left - color : # 198754 ; }
tr . mat - mdc - row . type - mediation > td :first - child { border - left - color : # fd7e14 ; }
tr . mat - mdc - row . type - mediation > td :first - child { border - left - color : # fd7e14 ; }
tr . mat - mdc - row . type - psychotherapie > td :first - child { border - left - color : # 7 c3aed ; }
tr . mat - mdc - row . type - lsb > td :first - child { border - left - color : # db2777 ; }
tr . mat - mdc - row . type - coaching > td :first - child { border - left - color : # 0891 b2 ; }
tr . mat - mdc - row . type - supervision > td :first - child { border - left - color : # ca8a04 ; }
tr . mat - mdc - row . type - sonstige > td :first - child { border - left - color : # 64748 b ; }
tr . mat - mdc - row . entry - today {
tr . mat - mdc - row . entry - today {
font - weight : 700 ;
font - weight : 700 ;
}
}
@ -236,9 +256,24 @@ import { Dashboard, Entry } from "../core/models";
: host - context ( body . dark ) tr . mat - mdc - row . type - fachspezifikum :nth - of - type ( even ) > td { background - color : rgba ( 53 , 184 , 116 , 0.18 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - fachspezifikum :nth - of - type ( even ) > td { background - color : rgba ( 53 , 184 , 116 , 0.18 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - mediation :nth - of - type ( odd ) > td { background - color : rgba ( 255 , 143 , 51 , 0.13 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - mediation :nth - of - type ( odd ) > td { background - color : rgba ( 255 , 143 , 51 , 0.13 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - mediation :nth - of - type ( even ) > td { background - color : rgba ( 255 , 143 , 51 , 0.2 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - mediation :nth - of - type ( even ) > td { background - color : rgba ( 255 , 143 , 51 , 0.2 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - psychotherapie :nth - of - type ( odd ) > td { background - color :rgba ( 167 , 139 , 250 , . 12 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - psychotherapie :nth - of - type ( even ) > td { background - color :rgba ( 167 , 139 , 250 , . 18 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - lsb :nth - of - type ( odd ) > td { background - color :rgba ( 244 , 114 , 182 , . 12 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - lsb :nth - of - type ( even ) > td { background - color :rgba ( 244 , 114 , 182 , . 18 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - coaching :nth - of - type ( odd ) > td { background - color :rgba ( 34 , 211 , 238 , . 12 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - coaching :nth - of - type ( even ) > td { background - color :rgba ( 34 , 211 , 238 , . 18 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - supervision :nth - of - type ( odd ) > td { background - color :rgba ( 250 , 204 , 21 , . 11 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - supervision :nth - of - type ( even ) > td { background - color :rgba ( 250 , 204 , 21 , . 17 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - sonstige :nth - of - type ( odd ) > td { background - color :rgba ( 148 , 163 , 184 , . 11 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - sonstige :nth - of - type ( even ) > td { background - color :rgba ( 148 , 163 , 184 , . 17 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - propaedeutikum :hover > td { background - color : rgba ( 62 , 139 , 255 , 0.25 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - propaedeutikum :hover > td { background - color : rgba ( 62 , 139 , 255 , 0.25 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - fachspezifikum :hover > td { background - color : rgba ( 53 , 184 , 116 , 0.25 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - fachspezifikum :hover > td { background - color : rgba ( 53 , 184 , 116 , 0.25 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - mediation :hover > td { background - color : rgba ( 255 , 143 , 51 , 0.27 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - mediation :hover > td { background - color : rgba ( 255 , 143 , 51 , 0.27 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - psychotherapie :hover > td { background - color :rgba ( 167 , 139 , 250 , . 25 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - lsb :hover > td { background - color :rgba ( 244 , 114 , 182 , . 25 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - coaching :hover > td { background - color :rgba ( 34 , 211 , 238 , . 25 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - supervision :hover > td { background - color :rgba ( 250 , 204 , 21 , . 24 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . type - sonstige :hover > td { background - color :rgba ( 148 , 163 , 184 , . 24 ) ; }
: host - context ( body . dark ) tr . mat - mdc - row . entry - today > td {
: host - context ( body . dark ) tr . mat - mdc - row . entry - today > td {
box - shadow : inset 0 3 px 0 # b794f4 , inset 0 - 3 px 0 # b794f4 ;
box - shadow : inset 0 3 px 0 # b794f4 , inset 0 - 3 px 0 # b794f4 ;
}
}
@ -261,17 +296,20 @@ export class EntriesComponent implements OnInit {
readonly entries = signal < Entry [ ] > ( [ ] ) ;
readonly entries = signal < Entry [ ] > ( [ ] ) ;
readonly loading = signal ( false ) ;
readonly loading = signal ( false ) ;
readonly columns = [ "date" , "time" , "type" , "art" , "details" , "distance" , "allowance" , "cost" , "training" , "actions" ] ;
readonly columns = [ "date" , "time" , "type" , "art" , "details" , "distance" , "allowance" , "cost" , "training" , "actions" ] ;
readonly types = [ "propädeutikum" , "fachspezifikum" , "mediation" ] ;
readonly arts : Record < string , string [ ] > = { propädeutikum : [ "Praktikum" , "Selbsterfahrung" , "Supervision" , "Fortbildung" , "Semesterkosten" ] , fachspezifikum : [ "Praktikum" , "Selbsterfahrung" , "Supervision" , "Fortbildung" , "Semesterkosten" , "Gruppenselbsterfahrung" , "Theorie/Methodikseminare" , "Peergruppensupervision" , "Einzel-/Kleingruppensupervision" , "Eigenständige Tätigkeit" ] , mediation : [ "Präsenzmodul" , "Selbsterfahrung" , "Supervision" , "Peergruppenarbeit" , "Fallarbeit" , "Praxisseminare" , "Literatur- und Selbststudium" , "Fortbildung" , "Semesterkosten" ] } ;
readonly types = signal < string [ ] > ( [ ] ) ;
readonly filterTypes = signal < string [ ] > ( [ ] ) ;
readonly arts = signal < Record < string , string [ ] > > ( { } ) ;
readonly typeLabels = signal < Record < string , string > > ( { } ) ;
readonly running = signal < Entry | null > ( null ) ;
readonly running = signal < Entry | null > ( null ) ;
readonly clock = signal ( Date . now ( ) ) ;
readonly clock = signal ( Date . now ( ) ) ;
timerType = "propädeutikum " ; timerArt = "Praktikum " ; lunchBreak = false ;
timerType = "" ; timerArt = "" ; lunchBreak = false ;
search = "" ;
search = "" ;
typ = "" ;
typ = "" ;
minYear : number | "" = "" ;
minYear : number | "" = "" ;
maxYear : number | "" = "" ;
maxYear : number | "" = "" ;
constructor ( private api : ApiService ) { }
constructor ( private api : ApiService ) { }
ngOnInit() {
ngOnInit() {
this . loadSettings ( ) ;
this . load ( ) ;
this . load ( ) ;
setInterval ( ( ) = > this . clock . set ( Date . now ( ) ) , 30 _000 ) ;
setInterval ( ( ) = > this . clock . set ( Date . now ( ) ) , 30 _000 ) ;
}
}
@ -286,7 +324,8 @@ export class EntriesComponent implements OnInit {
} ) ;
} ) ;
this . api . dashboard ( ) . subscribe ( ( d :Dashboard ) = > this . running . set ( d . running_entry ) ) ;
this . api . dashboard ( ) . subscribe ( ( d :Dashboard ) = > this . running . set ( d . running_entry ) ) ;
}
}
timerArts ( ) { const available = this . arts [ this . timerType ] || [ ] ; if ( ! available . includes ( this . timerArt ) ) this . timerArt = available [ 0 ] ; return available ; }
timerArts ( ) { const available = this . arts ( ) [ this . timerType ] || [ ] ; if ( ! available . includes ( this . timerArt ) ) this . timerArt = available [ 0 ] || "" ; return available ; }
typeLabel ( type : string ) { return this . typeLabels ( ) [ type ] || type . charAt ( 0 ) . toUpperCase ( ) + type . slice ( 1 ) ; }
startTimer ( ) { this . api . startTimer ( this . timerType , this . timerArt ) . subscribe ( e = > { this . running . set ( e ) ; this . load ( ) ; } ) ; }
startTimer ( ) { this . api . startTimer ( this . timerType , this . timerArt ) . subscribe ( e = > { this . running . set ( e ) ; this . load ( ) ; } ) ; }
stopTimer ( entry :Entry ) { this . api . stopTimer ( entry . id , this . lunchBreak ) . subscribe ( ( ) = > { this . running . set ( null ) ; this . lunchBreak = false ; this . load ( ) ; } ) ; }
stopTimer ( entry :Entry ) { this . api . stopTimer ( entry . id , this . lunchBreak ) . subscribe ( ( ) = > { this . running . set ( null ) ; this . lunchBreak = false ; this . load ( ) ; } ) ; }
elapsed ( entry :Entry ) { this . clock ( ) ; if ( ! entry . start_time ) return "0 h 0 min" ; const minutes = Math . max ( 0 , Math . floor ( ( Date . now ( ) - new Date ( entry . start_time ) . getTime ( ) ) / 60000 ) ) ; return ` ${ Math . floor ( minutes / 60 ) } h ${ minutes % 60 } min ` ; }
elapsed ( entry :Entry ) { this . clock ( ) ; if ( ! entry . start_time ) return "0 h 0 min" ; const minutes = Math . max ( 0 , Math . floor ( ( Date . now ( ) - new Date ( entry . start_time ) . getTime ( ) ) / 60000 ) ) ; return ` ${ Math . floor ( minutes / 60 ) } h ${ minutes % 60 } min ` ; }
@ -328,4 +367,14 @@ export class EntriesComponent implements OnInit {
URL . revokeObjectURL ( a . href ) ;
URL . revokeObjectURL ( a . href ) ;
} ) ;
} ) ;
}
}
private loadSettings() {
this . api . settings ( ) . subscribe ( ( settings : Settings ) = > {
this . types . set ( settings . enabled_praktikums_typen ) ;
this . filterTypes . set ( settings . praktikums_typen ) ;
this . arts . set ( settings . entry_arten_by_typ ) ;
this . typeLabels . set ( settings . praktikums_typ_labels ) ;
this . timerType = settings . enabled_praktikums_typen [ 0 ] || "" ;
this . timerArt = settings . entry_arten_by_typ [ this . timerType ] ? . [ 0 ] || "" ;
} ) ;
}
}
}