Compare commits

..

597 Commits
1.2 ... 1.19

Author SHA1 Message Date
Kevin Papst
6c1d79fe5b release 1.19 (#3215) 2022-03-21 23:42:56 +01:00
Weblate (bot)
e5f1df8c70 Translated using Weblate (#3208)
Co-authored-by: Gil <almontegil@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Taplou <ploutarchosgram@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: 이정희 <daemul72@gmail.com>
2022-03-21 21:32:46 +01:00
Kevin Papst
d6b59f6799 defensive javascript (#3210) 2022-03-20 21:08:59 +01:00
Kevin Papst
4b069edd33 added event to extend detail pages (#3209) 2022-03-19 23:54:40 +01:00
Kevin Papst
a8ac6b2eda Release 1.18.3 (#3204)
* fix truncated comments for customer, project and activity
* fix export button label for non-translated renderer
* fixed avatar size if image or SVG is used
* filter timesheets by billable state in API
2022-03-18 23:45:41 +01:00
Weblate (bot)
5c925f0ca5 Translated using Weblate (#3194)
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Taplou <ploutarchosgram@gmail.com>
2022-03-18 23:25:22 +01:00
Kevin Papst
30c7782d6e automatic billable calculation (#3200) 2022-03-18 22:31:50 +01:00
Weblate (bot)
f7480902b4 Translated using Weblate (#3181)
Co-authored-by: ButterflyOfFire <ButterflyOfFire@protonmail.com>
Co-authored-by: Eric <alchemillatruth@purelymail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Tiger <tigercrl@icloud.com>
2022-03-11 23:50:37 +01:00
Kevin Papst
00b5a65859 1.18.2 (#3190)
* improve console version output
* fix empty string issue in csv export (DDE protection) - fixes #3189
* fix twig sort deprecation in php 8
* sort user by displayName in users report
* fix missing custom translations in edit modal
* fix font-family in invoice.css
* invoice template "freelancer pdf" - added customer number, moved some fields around
* invoice template "default" - relocate customer number and order number in
2022-03-11 23:25:23 +01:00
Kevin Papst
317ac59f47 fix select2 and dropdown width for quick-entry form (#3188) 2022-03-07 14:22:16 +01:00
Kevin Papst
b08c156552 1.18.1 - fix description pattern in title (#3179)
* bump version
* add comment to customer/project/activity entity and collections api 
* improve title pattern usage
2022-02-28 16:32:41 +01:00
Weblate (bot)
2ad7629c2a Translations update from Hosted Weblate (#3177)
* fix broken files containing keys from messages namespace
* pre-fill missing translations with english
* new command to pre-fill empty translations
* fix locale in filename
* activate locales fa and nb_NO

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Eric <alchemillatruth@purelymail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2022-02-28 16:02:40 +01:00
Kevin Papst
42963ead5c switch to excel cell formats for report exports (#3178) 2022-02-27 13:36:52 +01:00
Kevin Papst
c0168ecc5b 1.18 (#3158)
* bump version
* fix translation ids
* added css classes to modify form with custom css
* improve export pdf file names
* respect financial year in new report
* added new InvoiceCalculator: price
* upgrade packages and node-sass to v7
* title pattern for customer, project and activity via API
* support negative money without currency
* fix sub-locale in print export template
* fix overbooking validation for monthly budget
* fix copying entities with different set of custom-fields compared to the current configuration
2022-02-25 20:41:56 +01:00
Weblate (bot)
34649d809e Translated using Weblate (Croatian) (#3174)
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
2022-02-25 20:40:16 +01:00
Weblate (bot)
ee0f6b0ccf Translations update from Hosted Weblate (#3171)
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
2022-02-25 15:30:37 +01:00
Kevin Papst
c8b47d1033 added explicit classes for widgets (#3170) 2022-02-22 21:15:19 +01:00
Weblate (bot)
3d3ae97af4 Translations update from Hosted Weblate (#3160)
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
2022-02-22 13:29:48 +01:00
fredyb
d7066a2bbc support subtotal in filtered spreadsheet (#3166) 2022-02-22 13:29:03 +01:00
Weblate (bot)
7f8545f1dc Translations update from Hosted Weblate (#3105)
Co-authored-by: Miovio <mgrundemar@gmail.com>
Co-authored-by: ButterflyOfFire <ButterflyOfFire@protonmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2022-02-16 12:57:47 +01:00
Kevin Papst
85a63c4b78 new user-year reporting and data-type chooser (#3155) 2022-02-16 12:18:04 +01:00
Kevin Papst
1852d71974 export user-list reports in excel (#3154) 2022-02-14 17:55:10 +01:00
Kevin Papst
7da7468965 configure display of customer, project and activity in dropdown lists (#3151) 2022-02-14 17:43:05 +01:00
Kevin Papst
83a894823f release 1.17.2 (#3135)
* allow to input password interactively in console
* added block to simplify overwriting export template parts
* prevent installation in PHP 8.1
* composer update
* phpunit version to 9
* support negative amounts in excel export
* added system configuration actions for calendar, weekly timesheet, users
* added method to render text with full markdown support
2022-02-14 17:33:52 +01:00
Kevin Papst
4cb0ac37a6 unify access to custom fields (#3106) 2022-01-28 00:04:58 +01:00
Weblate (bot)
7a6fab8624 Translations update from Hosted Weblate (#3093)
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Eric <alchemillatruth@purelymail.com>
Co-authored-by: Bogi Napoleon Wennerstrøm <bogi.wennerstrom@gmail.com>
Co-authored-by: Oliver Darvishi <nimawm81@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
2022-01-25 14:07:30 +01:00
Kevin Papst
6416b67c63 fix admin access for customer in invoice module (#3095) 2022-01-25 14:06:09 +01:00
Kevin Papst
1e269a6f82 improve team member handling (#3097) 2022-01-25 13:44:49 +01:00
Kevin Papst
8694a3bfe4 added invoice delete event (#3096) 2022-01-24 21:33:13 +01:00
Kevin Papst
9e320674c1 phpstan improvements (#3092) 2022-01-23 15:49:38 +01:00
Kevin Papst
9047e9e785 optimize prod error messages (#3091) 2022-01-23 15:29:41 +01:00
Kevin Papst
b8d8f0c16d release 1.17 (#3090) 2022-01-23 01:33:00 +01:00
Kevin Papst
8b8e9b001e bump dependencies (#3089) 2022-01-22 17:11:30 +01:00
Weblate (bot)
9c40bd98aa Translations update from Hosted Weblate (#3048)
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: 黄煜祺 <QQBB18646265284@163.com>
Co-authored-by: Hibiki Kimura <h.kimura787@gmail.com>
Co-authored-by: 좀비때려잡아 <mickey9301@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2022-01-22 13:38:45 +01:00
Kevin Papst
1a0608f49e new export template (#3082) 2022-01-22 13:20:22 +01:00
Kevin Papst
3543e0a1b5 code improvements (#3088) 2022-01-22 02:46:02 +01:00
Kevin Papst
97c23f75dd improve permission handling for quick entry controller (#3081) 2022-01-20 22:24:19 +01:00
Kevin Papst
49bb9980b8 added invoice model to invoice created event (#3079)
* added model to InvoiceCreatedEvent
* allow to switch formatter locale
2022-01-20 18:41:55 +01:00
Kevin Papst
fd4cbb43c1 bugfixes (#3078)
* fix phpdoc
* fix invoice export field order
* prevent empty migration warning
* do not trigger export validation on new timesheets
* update license year
* remove trailing comma in function call for php compatibility
* add new composer plugin config
* bump phpunit schema version
2022-01-20 17:10:56 +01:00
Kevin Papst
40242b7dcc support custom fields for invoices (#3077) 2022-01-20 16:59:26 +01:00
Kevin Papst
9731023014 1.16.10 (#3047) 2022-01-01 18:12:57 +01:00
Weblate (bot)
0abbba7a56 Translations update from Hosted Weblate (#3032)
Co-authored-by: easyjoh <johannes@easyplusplus.net>
Co-authored-by: Dmitry <dmitrydmitry761@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: 拉姆徐 <xulong_715138082@qq.com>
Co-authored-by: Michal Gajewski <gajewmic@gmail.com>
Co-authored-by: DJScias <djscias@gmail.com>
2021-12-30 22:53:01 +01:00
Kevin Papst
cc809183ae added comment field to invoice (#3045) 2021-12-30 21:49:30 +01:00
Kevin Papst
f7b3f4ed76 improve export permission checks (#3027) 2021-12-17 01:10:49 +01:00
Kevin Papst
04fc954769 allow to configure the amount of recent activity rows in an empty week (#3026) 2021-12-16 16:58:10 +01:00
Kevin Papst
3621b8c27c fix invoice budget calculation (#3024) 2021-12-15 17:56:18 +01:00
Kevin Papst
358959522d release 1.16.9 (#3018)
* fix filter action display
* allow to set system configuration during runtime
* bump version
* use deepl pro free to translate missing keys
* replacing broken Github action
2021-12-14 02:29:06 +01:00
Weblate (bot)
19cd5962aa Translations update from Hosted Weblate (#3013)
Co-authored-by: Trần Công Minh <minhhp198x@gmail.com>
2021-12-14 00:10:26 +01:00
Kevin Papst
961336f35c fix project are not filtered after submit (#3016) 2021-12-11 15:51:23 +01:00
Kevin Papst
573fa9f7f3 fix create invoice token issue (#3007) 2021-12-09 01:36:58 +01:00
Kevin Papst
22ce6b047a new invoice template variables for budgets (#3005) 2021-12-08 22:15:07 +01:00
Kevin Papst
82525f382d fix invoice preview (#3002) 2021-12-08 14:21:15 +01:00
Kevin Papst
ec9dd08970 cleanup duplicate translation ids (#3001) 2021-12-08 01:49:44 +01:00
Kevin Papst
9ec0280334 improve translation test and fix error (#2998)
Co-authored-by: Kristoffer Grundström <swedishsailfishosuser@tutanota.com>
2021-12-07 16:56:08 +01:00
Kevin Papst
0861dbd75d new command to work with translation files (#2993) 2021-12-05 14:54:02 +01:00
Kevin Papst
f5949b6b9a use new trustedAuthors flag in CI action (#2992) 2021-12-04 22:46:39 +01:00
Weblate (bot)
99905497e1 Translations update from Hosted Weblate (#2951)
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: BeckeBauer <berko@gmx.de>
Co-authored-by: Bogi Napoleon Wennerstrøm <bogi.wennerstrom@gmail.com>
Co-authored-by: Miovio <mgrundemar@gmail.com>
Co-authored-by: Roman Ondráček <ondracek.roman@centrum.cz>
Co-authored-by: Dronrs <Dutythinking@protonmail.com>
Co-authored-by: Michal Matúšov <kubof.hromoslav@gmail.com>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-12-04 02:37:04 +01:00
Kevin Papst
4ae18b73d1 bump version (#2991) 2021-12-04 02:27:32 +01:00
Kevin Papst
1da26e041d fix invoice create and search (#2990) 2021-12-04 01:18:49 +01:00
Kevin Papst
4e42911f3d more csrf protection for invoice and search (#2984) 2021-12-02 18:01:51 +01:00
Kevin Papst
b0045a910c prevent that lock files will be committed in PRs (#2983) 2021-12-02 13:06:23 +01:00
Kevin Papst
9491f07ade fix deleting invoice documents (#2980) 2021-11-29 15:57:39 +01:00
Kevin Papst
d994339de5 fix two opening form tags (#2972) 2021-11-29 15:49:54 +01:00
Kevin Papst
0464054a2b bump version 2021-11-24 12:55:38 +01:00
Kevin Papst
b062164277 allow to delete invoice documents (#2968) 2021-11-24 10:30:49 +01:00
Kevin Papst
ff9acab0fc improve permissison handling in invoice screen (#2965) 2021-11-21 16:41:03 +01:00
Kevin Papst
76e09447c8 make sure that markdown uses safe mode (#2961) 2021-11-19 23:07:27 +01:00
Kevin Papst
9470699798 escape data in calendar popover (#2960) 2021-11-19 22:57:03 +01:00
Kevin Papst
89bfa82c61 escape customer, project and activity name in javascript (#2959) 2021-11-19 19:45:37 +01:00
Kevin Papst
859131308a improve export filename (#2958) 2021-11-19 18:53:43 +01:00
tdozbun-reno
56d02673ed Update CSRF Token IDs for Issue kevinpapst/kimai2#2947 (#2948)
* bump version
* removed not needed token, as it is already contained in the form
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-11-18 18:32:00 +01:00
Kevin Papst
b28e9c120c version 1.16.2 (#2942)
* bump version
* include calendar week in week chooser
* table names in SQL
* show save flash message
* prevent migration warning
* drop default value to prevent error when server version is not set
* csrf token for duplicate actions
* updated translations
2021-11-18 12:33:13 +01:00
Kevin Papst
c858edf5c9 release 1.16.1 (#2938)
* optional csrf token name
* fix regression when selecting all projects as super user
* prevent invalid sql
2021-11-16 21:48:50 +01:00
Kevin Papst
99ebd1d503 deactivate codecov patch percentage check (#2937) 2021-11-16 12:09:41 +01:00
Kevin Papst
f63a2c8ef6 Release 1.16 (#2929)
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
2021-11-16 10:51:01 +01:00
Kevin Papst
95796ab256 improve csrf handling (#2936) 2021-11-16 10:17:26 +01:00
Kevin Papst
a1992494d3 fetch user preferences via API (#2905) 2021-11-15 21:14:45 +01:00
Kevin Papst
5896ae26c6 improve error handling during invoice generation (#2932)
* prevent that items will be marked as exported if invoice can not be generated
* check for existing invoice number to prevent that invalid invoices will be generated
* allow to add a unique id to invoice preview files on batch export
* hide delete invoice action with deactivated permission
* try to automatically fix duplicate invoice id
2021-11-15 19:21:22 +01:00
Weblate (bot)
8978c181ee Translations update from Weblate (#2915)
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Anna Gonzalez Mellado <annagm615@gmail.com>
Co-authored-by: Gil <almontegil@gmail.com>
2021-11-15 00:57:59 +01:00
Kevin Papst
ae975d07db submit invoice search after changing the template (#2931) 2021-11-15 00:20:43 +01:00
Kevin Papst
6b49535b52 prevent csrf to flush logs (#2930) 2021-11-14 20:39:22 +01:00
Kevin Papst
8b0962e192 configure am/pm time-format as user preference (#2789) 2021-11-14 18:18:26 +01:00
pkaltenboeck
dce0578a2b new ProjectConstraint to add dynamic project validation (#2747) 2021-11-14 16:56:52 +01:00
Kevin Papst
a483184372 PDF memory optimizations (#2736) 2021-11-14 02:51:52 +01:00
Kevin Papst
6e6c60b29a added new invoice status: canceled (#2922) 2021-11-10 12:37:36 +01:00
Kevin Papst
21dbfdb4f9 do not reset password for LDAP and SAML users or rehash on API calls (#2916) 2021-11-10 11:34:50 +01:00
Kevin Papst
73ab26c7b7 added resname for tool compatibility (#2912) 2021-11-05 14:40:31 +01:00
Weblate (bot)
3e61b60c4e Translations update from Weblate (#2850)
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Anders Johansson <johansson@aljmedia.se>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Roman Shabanov <romash2605@gmail.com>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Artem <Localizer_in_Russian@protonmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: KanI <twinklingnerd@gmail.com>
Co-authored-by: ButterflyOfFire <ButterflyOfFire@protonmail.com>
2021-11-05 14:18:37 +01:00
Kevin Papst
bd2fe32d5a optimizations (#2904)
* calc once, then re-use
* prevent invalid theme switch
* allow to turn off weekly-quick-entries by permissions
* remove 00:00 from date-times that likely do not need a time
* fix datepicker out of window
2021-11-05 10:28:48 +01:00
Kevin Papst
c8854b0775 change data filter on project month report (#2911) 2021-11-05 09:45:12 +01:00
iusgit
042c119058 extend project orderNumber to 50 characters (#2828) 2021-11-03 16:48:35 +01:00
Kevin Papst
b92488e32f include roles and teams in user create form (#2849) 2021-11-03 16:24:22 +01:00
Kevin Papst
2bc76e7adc prevent empty migration warning (#2901) 2021-11-03 15:48:42 +01:00
Kevin Papst
d4278996ae composer upgrade, prevent dbal upgrade, new phpstan version (#2900) 2021-11-03 14:58:10 +01:00
Kevin Papst
eb63dae405 added invoice replacer for currently logged-in user (#2899) 2021-11-03 12:00:52 +01:00
Kevin Papst
1b35356f81 activate bleeding edge phpstan rules (#2898) 2021-11-02 13:33:21 +01:00
Kevin Papst
36c08b0dea fix weekly view day format (#2893)
* make duplicate project action available on details page
* move "weekly hours" form to main menu
* changed label of week chooser
* hide quick entries menu for punch mode users
2021-11-01 20:23:17 +01:00
Kevin Papst
1d32e4ecee use token in invoice delete route (#2889) 2021-10-30 15:17:06 +02:00
Kevin Papst
40061e43bb simplify building theme independent plugins (#2888) 2021-10-30 14:48:05 +02:00
Kevin Papst
7ed1998d04 fixes for new quick-entry week form (#2887) 2021-10-29 21:35:48 +02:00
Kevin Papst
fd2490c135 added workflow to trigger new docker images on release (#2882) 2021-10-28 10:42:53 +02:00
Kevin Papst
9ab098af86 weekly quick-entry form (#2793) 2021-10-18 11:12:46 +02:00
Kevin Papst
64893e0a95 release 1.15.6 (#2863) 2021-10-18 00:30:35 +02:00
Kevin Papst
6fdc304313 improve sunday FDOW handling (#2862) 2021-10-17 19:17:47 +02:00
Kevin Papst
fb265ed732 make sure that minute_increment is not zero (#2860) 2021-10-16 13:38:28 +02:00
Kevin Papst
e2be5b401c improve summary rows in reports (#2861) 2021-10-16 13:38:08 +02:00
Kevin Papst
8539938b8a use new codecov action (#2854) 2021-10-14 17:23:45 +02:00
Kevin Papst
bbf25823b8 release 1.15.5 (#2848) 2021-10-13 18:31:28 +02:00
Kevin Papst
6b066046c6 default value for billable flag and support in batch update (#2851) 2021-10-13 15:49:20 +02:00
Weblate (bot)
9226daa891 Translations update from Weblate (#2823)
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Péter Gyetvai <gyetpet@mailbox.org>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Frank.wu <me@wuzhiping.top>
Co-authored-by: web chigusa <chigusaweb66@gmail.com>
Co-authored-by: KasukeLp <kasukelp23@yahoo.com>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-10-13 14:09:15 +02:00
Kevin Papst
843cd8fb67 refactor system configuration title and wording (#2847) 2021-10-13 13:37:44 +02:00
Kevin Papst
6d7d441d42 fix missing meta fields in duplicate timesheet action (#2845) 2021-10-12 23:41:27 +02:00
Kevin Papst
1c4da30b61 fix version number 2021-10-07 16:42:17 +02:00
Kevin Papst
84e25851d3 set security options on cookies (#2825) 2021-10-07 12:40:06 +02:00
Kevin Papst
3a52daedab fix open budget calculation (#2821) 2021-10-07 11:46:48 +02:00
Kevin Papst
b7cf9cd776 added croatian language (#2817) 2021-10-06 13:41:05 +02:00
Kevin Papst
ccb184b17f Release 1.15.3 (#2813) 2021-10-05 15:30:29 +02:00
Weblate (bot)
162ff07bd5 Translations update from Weblate (#2791)
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Daniel' Ishmaev <dan.samara@gmail.com>
Co-authored-by: Luna Jernberg <droidbittin@gmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-10-05 15:22:45 +02:00
Kevin Papst
7326a452c3 re-added lock threads app 2021-10-04 17:09:59 +02:00
Kevin Papst
9453947309 export budget, timeBudget and budgetType (#2812) 2021-10-04 16:57:45 +02:00
Kevin Papst
d25653b6ed removed export and system-config subtitles (#2811) 2021-10-04 14:36:17 +02:00
Kevin Papst
ea415e33f5 do not apply rate factor multiple times (#2807) 2021-10-03 17:40:36 +02:00
Kevin Papst
b6430d5622 allow to customize dashboard permissions for latest row (#2806) 2021-10-03 17:17:01 +02:00
Kevin Papst
6fcd50ecdf hide not started projects in widget (#2805) 2021-10-03 13:53:52 +02:00
Kevin Papst
6857d92c4f respect customer visibility in project-overview report (#2804) 2021-10-03 13:41:14 +02:00
Weblate (bot)
4bb9edb2b5 Translations update from Weblate (#2781)
Co-authored-by: frooog in raain <sadsatan211@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Mokhtar Garmehi <Mokhtar_garmeh@yahoo.com>
Co-authored-by: Pham Mina <phamthinhalinh93@gmail.com>
Co-authored-by: Ana Carla Vasconcelos <anacarlavgs@gmail.com>
Co-authored-by: SC <lalocas@protonmail.com>
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: Kevin Papst <kevinpapst@users.noreply.github.com>
2021-09-24 15:16:36 +02:00
Input-BDF
dd881bb856 prevent whitespace on line endings in created docx invoices (#2756) 2021-09-24 15:02:01 +02:00
Kevin Papst
204e0bc179 1.15.2 (#2782)
* fix division by zero
* cleanup required directories, check configured data directory, do not check for freetype support
* bump version number
2021-09-24 14:47:40 +02:00
Guillaume-Duc-95
d026460106 copy the billable status when restarting timesheet (#2778) 2021-09-20 16:23:23 +02:00
Kevin Papst
934a7855b9 remove duplicate statistic events (#2776) 2021-09-18 13:15:23 +02:00
Kevin Papst
54d7c4490f added customer number in invoice export (#2775) 2021-09-18 02:49:47 +02:00
Kevin Papst
fa6af3d148 fix api doc (teams) (#2773) 2021-09-17 14:31:37 +02:00
Kevin Papst
063753b569 improve upgrade docs (for LDAP users in 1.15) (#2772) 2021-09-17 13:42:55 +02:00
Kevin Papst
baff2d78d9 prepare release 1.15 (#2707)
* bump version
* fix invisible class on labels
* fail safe removal of foreign key
* check if optional form field exists before accessing it
* silently ignore stopped timesheets
* prevent colliding parameter names
* make sure decimal duration is always rendered with two decimals
* simplify translation
* fix #2751 ANSI_QUOTES
* rename composer task
* fix billable statistic rates
* added missing translation for export
2021-09-17 00:58:25 +02:00
Weblate (bot)
8f832856a5 Translations update from Weblate (#2739)
Co-authored-by: Sérgio Morais <lalocas@protonmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: Yan Kazachkov <yankazachkov@gmail.com>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: pisquan8 <cimurro@outlook.de>
Co-authored-by: Maamon AlHaqli <mamon321@hotmail.com>
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Krishna Chand <krishna_chand67@naver.com>
Co-authored-by: Anders Johansson <johansson@aljmedia.se>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-09-16 17:16:29 +02:00
Kevin Papst
f2eec4f315 API fixes (#2766) 2021-09-16 17:06:32 +02:00
Weblate (bot)
2536105ad0 Translations update from Weblate (#2717)
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Ville Kovacs <regpile@mailbox.org>
Co-authored-by: Simona Iacob <s@zp1.net>
Co-authored-by: Andrea Martini <andrea.martini.7002@gmail.com>
Co-authored-by: Лолло Нуб <lollo.samp@mail.ru>
Co-authored-by: Peter Valo <pvalo@abcom.sk>
Co-authored-by: A M <augim3@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Ana Carla Vasconcelos <anacarlavgs@gmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-08-26 02:08:50 +02:00
Kevin Papst
98a3fc99a2 store search in session (#2735)
* allow to detect used query filters
* allow to set label in action buttons
* allow to reset last search from session
* migrate invoice archive to new search system
* display number of used search filters
2021-08-26 01:37:40 +02:00
Kevin Papst
56524a9773 data-table sorting icons and alignment (#2734) 2021-08-26 00:56:36 +02:00
Kevin Papst
fae525c82f fix duplicating timesheet if allow overlapping is deactivated (#2732) 2021-08-25 19:31:48 +02:00
Kevin Papst
5023fa6189 prevent email or username from being non-unique (#2730) 2021-08-25 15:18:46 +02:00
Kevin Papst
f743503705 added search modal for timesheet export (#2728) 2021-08-24 19:01:48 +02:00
Kevin Papst
f6a82ba119 fix team edit with deactivated users (#2716) 2021-08-11 01:23:55 +02:00
Weblate (bot)
305d3ba6fe Translations update from Weblate (#2672)
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Victor Cleaner <victor.cleaner@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: edo-2313 <edo2313@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Jack-Benny Persson <jack-benny@cyberinfo.se>
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: dottypottylotty <muzzbuzzer@outlook.com>
Co-authored-by: Adam K <uz@zu.uz>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-08-10 15:14:30 +02:00
Kevin Papst
7873bd812e fix statistics in user profile for first year not starting in january (#2712) 2021-08-10 14:39:52 +02:00
Kevin Papst
b31ded22b9 minor restyling of login screen (#2711) 2021-08-10 14:25:36 +02:00
Toby Batch
f80fe0e4d0 allow to prefill timesheet description by get param (#2580) 2021-08-08 14:29:12 +02:00
Kevin Papst
c063aed867 fixes for 1.15 (#2704) 2021-08-08 12:38:19 +02:00
Kevin Papst
b2d3272151 support multiple teamleads in each team (#2702)
* fix jumping avatars
* fix line-break after color dot for long names
2021-08-07 18:05:41 +02:00
Kevin Papst
e9986c92d6 code cleanup (#2700)
* fix doctrine definition
* do not count api calls as login
* remove unknown form options
* fix annotations
* cleanup deprecations in tests
2021-08-07 01:36:59 +02:00
Kevin Papst
3a7dba437c move supported languages logic to service (#2701) 2021-08-07 01:11:42 +02:00
Kevin Papst
cefd747e91 monthly budget, monthly report, unified report calculation (#2684) 2021-08-06 18:38:41 +02:00
Jelle Sebreghts
21f785638c added calendar configuration event (#2698) 2021-08-06 18:35:37 +02:00
Kevin Papst
df62c8a428 improved play and stop button (#2692)
* improved play and stop button if only one record is allowed
* stop button first in action menu
* fix skin colors
* autofocus term field in search form
* fix responsive column layout in invoice listing
* improve responsive header for small screens
2021-07-30 19:57:57 +02:00
Kevin Papst
37e02365c7 importer supports merge of multiple Kimai 1 installations (#2691) 2021-07-28 14:16:49 +02:00
Kevin Papst
7352c9a394 allow 65535 character in meta fields (#2690) 2021-07-27 19:41:12 +02:00
Kevin Papst
5b8510be56 fix ldap issues due to new security components (#2689) 2021-07-27 19:31:49 +02:00
Kevin Papst
6d5244aa5d LDAP: fixing issues for re-authenticating users (#2681) 2021-07-23 16:12:38 +02:00
Kevin Papst
f1f60042d8 bugfixes (#2669)
* allow storage of multiple .env without git
* clarify requirements
* fail safe statistic queries
* remove unused translations
* reduce customer statistic calls by 50%
2021-07-23 15:15:54 +02:00
Weblate (bot)
6558bcae1d Translations update from Weblate (#2665)
Co-authored-by: Viktor Żakowski <pocomito250@gmail.com>
Co-authored-by: phlostically <phlostically@mailinator.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Tur <tur+translate@simplelogin.fr>
Co-authored-by: Adaś <adam.prosniak@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Eugenia Russell <eugenia.russell2019@gmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-07-19 01:02:11 +02:00
Kevin Papst
828b9c8b4a added account number field to user (#2671) 2021-07-19 00:13:39 +02:00
Kevin Papst
7d052eba00 make sure that timezone is properly validated (#2663) 2021-07-13 23:23:18 +02:00
Weblate (bot)
43a31efeac updated french (#2661)
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
2021-07-13 16:16:20 +02:00
Kevin Papst
f12f1ae60d bugfixes and improvements (#2660) 2021-07-13 15:51:27 +02:00
Weblate (bot)
9dc94084c1 Translations update from Weblate (#2613)
Co-authored-by: phlostically <phlostically@mailinator.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: Yan Kazachkov <yankazachkov@gmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-07-13 14:49:46 +02:00
Kevin Papst
03d5d3c70c better readability for drag and drop boxes (#2659) 2021-07-13 12:12:10 +02:00
Kevin Papst
27cea996c0 configurable print export (#2657) 2021-07-13 10:11:20 +02:00
Kevin Papst
7661d43327 improve permission checks for report access (#2658) 2021-07-13 06:21:31 +02:00
Kevin Papst
a1f90ffaba limit amount of items in calendar drag and drop boxes (#2291) 2021-07-11 15:03:18 +02:00
Kevin Papst
2583fdf20e small bugfixes (#2655)
* improve report visibility
* fix timesheet duration, which breaks validation
* added contributing section in README
* do not allow to change project for existing activities - fixes #2576
* bump theme version to fix FOSUserBundle dependency
* use entity color instead of inherited one - fixes #2200
2021-07-10 00:37:03 +02:00
Kevin Papst
9ddb87a6fd added project detail report (#2651)
- avatars via css only
- random colors for entities
- improves print view
- added colors for teams
- added color to tag
2021-07-06 22:01:20 +02:00
Kevin Papst
cc6031bfde invoice number pattern for customer-name and number (#2640) 2021-06-30 17:23:20 +02:00
Kevin Papst
6d196879b4 refactor configurations (#2626)
* composition instead of inheritance
* refactored some twig extensions to runtime extensions
2021-06-26 13:03:54 +02:00
Kevin Papst
3d1fba650b improve color chooser and name validation (#2622) 2021-06-25 16:35:16 +02:00
Kevin Papst
0d5e0b3e80 fix url under certain environments (#2621) 2021-06-25 09:48:38 +02:00
Kevin Papst
7460647d58 added setting to limit the maximum length of a timesheet record (#2612) 2021-06-12 01:05:33 +02:00
Weblate (bot)
53e01177d9 Translations update from Weblate (#2577)
Co-authored-by: Maamon AlHaqli <mamon321@hotmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Tymofij Lytvynenko <till.svit@gmail.com>
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Nerdiin <dznissei10@gmail.com>
Co-authored-by: Wellington Terumi Uemura <wellingtonuemura@gmail.com>
Co-authored-by: Richard Mangahas <richard.mangahas.aeom@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: phlostically <phlostically@mailinator.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-06-12 00:56:24 +02:00
Kevin Papst
6709ef4c4c removed soft_limit setting (#2611) 2021-06-10 16:01:09 +02:00
Kevin Papst
0b519e9d08 fix datepicker appears out of screen (#2610) 2021-06-10 15:51:26 +02:00
Kevin Papst
7f20cb045c Refactor authentication system (#2602)
Make auth configuration available via UI, remove FOSUserBundle and SAML-Bundle dependency
2021-06-10 15:34:13 +02:00
Kevin Papst
286b63e2c8 allow to configure timezone for the lockdown period (#2593) 2021-06-02 00:05:44 +02:00
Kevin Papst
c1eb5ba90a lint container, workflows and composer update (#2591) 2021-06-01 19:20:12 +02:00
Kevin Papst
4859ac8ecb Support for PHP 8 (#2158)
painful 66 commits later: required PHP version bumped to 7.3, plugin updates using migrations necessary!
2021-05-31 14:53:22 +02:00
Kevin Papst
df6bd30d03 fix calendar totals for empty weeks (#2581) 2021-05-21 15:27:10 +02:00
Kevin Papst
70fd0ae269 show revenues in dashboard to admins only, not teamleads (#2573) 2021-05-16 20:36:43 +02:00
Kevin Papst
dd83872339 new permission to hide user choice in report (#2572) 2021-05-16 00:52:42 +02:00
Kevin Papst
f7aa3c1e13 financial year setting + new users working time per year report (#2547) 2021-05-14 18:40:48 +02:00
dkstiler
a9da5f8476 added greek language (#2568) 2021-05-14 18:04:46 +02:00
Weblate (bot)
06f3a1ecd4 Translations update from Weblate (#2546)
Co-authored-by: Guilherme Dimas <guilhermedimasrosa@gmail.com>
Co-authored-by: dkstiler <dkstiler@gmail.com>
Co-authored-by: P-A Lindkvist <pa.lindkvist@gmail.com>
Co-authored-by: Henrik Dankvardt <dankvardt@gmail.com>
Co-authored-by: 김주열 <quick3377@gmail.com>
Co-authored-by: Nerdiin <dznissei10@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: whenwesober <naomi16i_1298q@cikuh.com>
Co-authored-by: Ido Ido <kobid88923@mxcdd.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-05-14 17:06:37 +02:00
Kevin Papst
bed3ade221 Improve progressbar values in report (#2569) 2021-05-14 11:45:03 +02:00
Kevin Papst
f2b32b211b use dialog to duplicate timesheet (#2567)
* fix: display time budget in customer listing if set
* updated api methods with annotations
* do not require checkboxes for system configurations
* open dialog for duplicated timesheets
2021-05-12 22:32:08 +02:00
Kevin Papst
f81f9ba492 added API route to fetch infos about installed plugins (#2561) 2021-05-12 14:03:46 +02:00
Kevin Papst
f10aad927f fix timesheet collection fetch via API with end datetime set (#2552) 2021-05-05 13:02:09 +02:00
Kevin Papst
4fcd97b142 do not include running entries in invoices (#2551) 2021-05-04 12:29:20 +02:00
Kevin Papst
dad1b8b772 version 1.14.1 (#2532)
* no back links in modal pages
* remove unused service links to bountysource and gitter
* add validation for budget and time-budget fields
* display time budget if set
* remove console log
* sanitize DDE payloads
* do not show status and name in version string
2021-04-29 18:29:03 +02:00
Weblate (bot)
22af82cb15 Translations update from Weblate (#2528)
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: phlostically <phlostically@mailinator.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
2021-04-29 12:00:27 +02:00
Kevin Papst
e4b176a4d5 use events for budget calculation (#2544) 2021-04-29 11:50:48 +02:00
Kevin Papst
bb94b11e37 support tags in invoices (#2526) 2021-04-23 17:06:42 +02:00
Weblate (bot)
67d23ec6b2 Translations update from Weblate (#2474)
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: phlostically <phlostically@mailinator.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Bogi Napoleon Wennerstrøm <bogi.wennerstrom@gmail.com>
Co-authored-by: Martin Didriksen <martindidriksen@hotmail.com>
Co-authored-by: Christian Eichert <c@zp1.net>
Co-authored-by: Gontzal Manuel Pujana Onaindia <thadahdenyse@gmail.com>
Co-authored-by: DJScias <djscias@gmail.com>
Co-authored-by: Bruno Oliveira Rosa <brunokauaoliveira@gmail.com>
Co-authored-by: Mohammad Firuzabadi <mohmmad.fi@gmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-04-22 01:22:39 +02:00
Kevin Papst
ff7a402bd4 fix locale date format in report header and more (#2520)
* fix problem with empty end datetime in edit time
* fixed calendar creation with tags in "pre-defined tags" mode
* fix locale date format in report header
2021-04-21 22:23:32 +02:00
Kevin Papst
305a2fd4f6 show daily sum in calendar week and day view (#2519) 2021-04-21 16:11:31 +02:00
Kevin Papst
09c9a95fcf added activity events for plugins (#2516) 2021-04-21 15:56:17 +02:00
Kevin Papst
08a144ac31 drag & drop to delete entries from the calendar (#2513) 2021-04-20 14:50:23 +02:00
Kevin Papst
7ddcde62dd fix sunday being first day of week (#2483) 2021-04-19 18:32:21 +02:00
Kevin Papst
b6462b406d updated all packages (#2512) 2021-04-19 17:25:06 +02:00
Kevin Papst
0330f45c6a billable timesheets, inactive projects report, bookmark export search (#2503) 2021-04-18 21:51:27 +02:00
Kevin Papst
8664d94ea6 release 1.14 (#2498) 2021-04-11 14:11:09 +02:00
Kevin Papst
af9dea9226 prepare release 1.14 (#2495)
* removed un-maintained docker file
* update all packages
* fix deprecations
* only show one line descriptions for customer, projects and activities
* allow to show export column in timesheet listing
2021-04-08 18:07:57 +02:00
Kevin Papst
2ce6e815ff new report: week for users (#2494) 2021-04-07 22:20:19 +02:00
Kevin Papst
cdad5913ca highlight invisible items (#2493) 2021-04-07 19:14:33 +02:00
Kevin Papst
68bb01d064 allow to pre-define colors to choose from (#2481) 2021-04-06 00:45:54 +02:00
Weblate (bot)
f4dbdbb427 Translations update from Weblate (#2401) 2021-03-29 11:45:03 +02:00
Philipp
d84beb957c add subtotal and payment date to invoices (#2450) 2021-03-29 00:51:34 +02:00
Kevin Papst
7029bde555 improve project overview (#2467) 2021-03-28 14:46:45 +02:00
Kevin Papst
7427187647 fix api 404 on page parameter (#2468) 2021-03-28 11:30:35 +02:00
Kevin Papst
f8a5ff7315 create multiple invoices at once (#2465) 2021-03-27 20:47:53 +01:00
Kevin Papst
87d07ffaaf save default search options (#2445)
* set default times for daterange objects
* allow to show order and order by fields
* move search to modal
* more options for page size
* save export visibility in cookie
2021-03-20 01:10:45 +01:00
Kevin Papst
954ba7c937 improve project view table (#2441) 2021-03-16 15:59:31 +01:00
Kevin Papst
16e8e8a5e5 fix deprecations (move same controller) (#2440) 2021-03-16 14:06:15 +01:00
Kevin Papst
75e62b800c added version ID for simpler comparison (#2439) 2021-03-16 11:24:19 +01:00
Kevin Papst
e1c9af795c refactored page actions to event subscriber (#2420) 2021-03-15 14:18:44 +01:00
Kevin Papst
9a2fd9ba91 allow increasing invoice numbers per day (#2433) 2021-03-14 13:53:36 +01:00
Kevin Papst
c0378b4f51 add user preference: default report (#2430) 2021-03-14 13:32:47 +01:00
Kevin Papst
0a6ea6be42 hide expired projects in team-projects widget (#2432) 2021-03-14 13:24:24 +01:00
Kevin Papst
ec0eca39ff show budget and time-budget columns in listing views (#2429) 2021-03-13 20:44:10 +01:00
Kevin Papst
3e9371bd6a Timesheet validator: prevent overbooking budgets (#2422) 2021-03-13 14:41:29 +01:00
Kevin Papst
db1344573f search and export from invoice archive (#2408) 2021-03-10 01:50:27 +01:00
Willian Gustavo Veiga
c34e9f576d added new report: project view (#1738) 2021-03-09 23:19:11 +01:00
Kevin Papst
0b7d551048 added more columns to spreadsheet exports (#2413) 2021-03-08 18:32:11 +01:00
Kevin Papst
30ac5e4c24 Drop SQLite support (#2405) 2021-03-08 16:06:22 +01:00
Kevin Papst
70f7f35009 allow to select reports as initial view (#2403) 2021-03-06 12:59:13 +01:00
Kevin Papst
5583caa9e4 User profile layout (#2402) 2021-03-06 12:34:34 +01:00
Kevin Papst
d3099aca39 remove redundant translation key (#2394) 2021-03-03 13:19:06 +01:00
Weblate (bot)
765ea63aed Translations update from Weblate (#2293) 2021-03-03 12:58:42 +01:00
Kevin Papst
adf8c09fd4 refactored permission templates to match controller (#2391) 2021-03-03 11:31:29 +01:00
Maxim Butakov
5a90b36734 fix unreadable notice for delete modals (#2379) 2021-02-28 15:20:05 +01:00
Kevin Papst
2e7bfc822c Widget fixes (#2373)
* fix click on row in team-projects
* hide deactivated users in team listing
2021-02-21 00:51:46 +01:00
Kevin Papst
607d09aefb performance improvements (#2329)
* remove work from constructor
* refactor twig extensions
* upgrade theme
* replace sub-request with direct template rendering
2021-02-20 23:52:01 +01:00
Kevin Papst
9eb25c412e prevent duplicate if overlapping records are disabled (#2311) 2021-02-06 17:31:14 +01:00
Kevin Papst
8d41fa20bd prepare release 1.13 (#2290)
* make voters a final class
* upgrade dependencies
* sort project alphabetically in dashboard widget
* open detail page on row click
* do not break on null tag name
* added max height to scrollable widgets on dashboard
* added timesheet duplicate event
* allow to deactivate browser title update
* improve comment box
* moved role permissions to own menu
* removed tabs in user screen
* fix user can remove super-admin from own account
2021-02-01 23:43:47 +01:00
Kevin Papst
a034b3519e added de_AT and pt as languages (#2292) 2021-01-23 00:00:30 +01:00
Weblate (bot)
ebf0b4de51 Translations update from Weblate (#2253)
Co-authored-by: MikKrz <mik.zly@gmail.com>
Co-authored-by: badlop <badlop@process-one.net>
Co-authored-by: Peter Donka <peter.donka@gmail.com>
Co-authored-by: 知者无涯 <wasd_jkl@outlook.com>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Omer I.S <omeritzicschwartz@gmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2021-01-22 20:45:39 +01:00
Kevin Papst
c9ccdb5f77 replace "freelancer" invoice template with pdf version (#2289) 2021-01-22 19:47:05 +01:00
Kevin Papst
88b4d3842f improve API validation error messages (#2278) 2021-01-20 18:57:16 +01:00
Kevin Papst
3cd7e01a2c show calculated lockdown date in config screen (#2274) 2021-01-20 17:50:50 +01:00
Kevin Papst
bd39666759 do not break excel invoice templates with replacer inside formulas (#2272) 2021-01-19 17:25:16 +01:00
Kevin Papst
67d5cba09f improve permission checks for timesheets with activated lockdown (#2271) 2021-01-17 17:50:37 +01:00
Kevin Papst
8d72d114c7 improved duration and minute selector (#2264)
* do not close modal if form is dirty
* deprecated TimesheetConfiguration
* inject timezone in form types
* cleanup usage of UserDateTimeFactory
* allow to configure increment steps for minutes
* use 15 minutes step for datetimepicker in project edit form
* use rounding rules for increments in minute select for begin and end
* allow duration in multi user and admin timesheet forms
* make dropdown values configurable
2021-01-17 14:04:13 +01:00
Kevin Papst
e2324b7d51 fix calendar problem with UTC (#2268) 2021-01-16 00:00:47 +01:00
Kevin Papst
1eae149c75 remove drag&drop on calendar for print views (#2261) 2021-01-14 20:53:18 +01:00
Kevin Papst
2a986517f7 fix pagination on invoice templates page (#2260) 2021-01-14 13:12:34 +01:00
Kevin Papst
bf47bb1840 hide multi update in tags view if delete_tag permission is missing (#2259) 2021-01-14 12:44:56 +01:00
Kevin Papst
cb6924e6d6 added event for recent activities (#2254) 2021-01-11 19:13:14 +01:00
Philipp Strobel
1de6b5d385 use the correct javascript event name (#2210) 2021-01-08 16:27:00 +01:00
Philipp Strobel
4c73fc825c Fix dropdowns on the bottom of a page (#2212) 2021-01-08 15:42:09 +01:00
Weblate (bot)
d0a0786cce Translations update from Weblate (#2226)
* Translated using Weblate (Polish)
* Translated using Weblate (Swedish)
* Translated using Weblate (Portuguese (Brazil))
* Translated using Weblate (Slovak)

Co-authored-by: Michał Jastrzębski <michu9010@gmail.com>
Co-authored-by: Andreas Håll <ante_skalman@hotmail.com>
Co-authored-by: Augusto Milão <augusto.milao01@gmail.com>
Co-authored-by: Aris <aatiger2007@gmail.com>
2021-01-08 15:22:54 +01:00
Kevin Papst
b7390ece39 week numbers by ISO (#2248) 2021-01-08 15:20:35 +01:00
Kevin Papst
1aae4f4d0f fix first week in year shows wrong year (#2239) 2021-01-08 15:12:42 +01:00
Kevin Papst
244169d6d5 make sure to generate valid filenames (#2238) 2021-01-08 14:50:23 +01:00
Kevin Papst
a5fdf81df5 fix deprecations (#2225) 2020-12-27 20:47:09 +01:00
Kevin Papst
c1a54abc58 simplify html timesheet exporter (#2224) 2020-12-27 20:44:39 +01:00
Weblate (bot)
7e649dff3b Translations update from Weblate (#2195)
Co-authored-by: Jan Hladik <jan.hladik@iquentin.cz>
Co-authored-by: Anaa Ara <martakanuu@protonmail.com>
Co-authored-by: LL Magical <lolayami2004@gmail.com>
Co-authored-by: Jakub Fabijan <animatorzPolski@gmail.com>
Co-authored-by: Hahato Toshino <ameer.amino95@gmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: ikmaak <info@ikmaak.nl>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
2020-12-26 16:57:37 +01:00
Kevin Papst
40eda01c22 added export context (#2216) 2020-12-22 18:41:00 +01:00
Kevin Papst
74b3917e69 wrong decimal duration for more than 1000 hours (#2214) 2020-12-21 13:08:43 +01:00
Kevin Papst
6ada0b649c dashboard: fix years with 53 weeks (#2206) 2020-12-18 13:09:28 +01:00
Kevin Papst
e21fc656e4 replace striptags with validator (#2192) 2020-12-14 00:28:30 +01:00
Kevin Papst
f4f5c8fa88 escape configurable fields (#2191) 2020-12-13 03:15:46 +01:00
Kevin Papst
64d1aecf16 support mysqli in connection string (#2190) 2020-12-13 03:13:04 +01:00
Kevin Papst
26c2d5aa81 prepare release 1.12 (#2183)
* updated dependencies (#2125)
* display application name and version in console (#2150)
2020-12-10 16:25:14 +01:00
Weblate (bot)
78d8212e57 Translations update from Weblate (#2136)
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: koffevar <egor.kafisov@gmail.com>
Co-authored-by: Anaa Ara <martakanuu@protonmail.com>
Co-authored-by: 朱伟 <2676988242@qq.com>
Co-authored-by: yn <bilisenyou@outlook.com>
Co-authored-by: Woodyx <shiposhouyou@gmail.com>
Co-authored-by: thekeymethod <csokan.andras87@protonmail.ch>
Co-authored-by: Daniel de Souza Melo <jxzk@protonmail.com>
Co-authored-by: João Tiago Antunes <jtiago.cbr@gmail.com>
2020-12-10 15:06:10 +01:00
Kevin Papst
7a3388646b allow custom export repositories (#2182) 2020-12-10 15:00:14 +01:00
Kevin Papst
c3fe8d1c39 access custom-fields in twig invoice templates (#2168) 2020-12-09 10:49:48 +01:00
Kevin Papst
fcd9974a7c suppress notice for tempnam (#2166) 2020-12-09 10:01:50 +01:00
Kevin Papst
53dc322fd1 improve permission screen (#2177) 2020-12-09 01:17:48 +01:00
Kevin Papst
4cab1d323c remove support as issue types (#2173) 2020-12-08 23:11:48 +01:00
Kevin Papst
686d55de6e added config for issue templates (#2172) 2020-12-08 20:23:15 +01:00
Kevin Papst
0d2d40791a allow to reassign timesheets from delete-user form (#2159) 2020-12-03 22:52:58 +01:00
Kevin Papst
4302ecd6ea fixed broken test (#2151) 2020-11-29 19:40:51 +01:00
Weblate (bot)
fc12056ba2 updated polish translations (#2135)
Co-authored-by: Piotr Bub <pbubacz@live.com>
2020-11-21 11:43:42 +01:00
Weblate (bot)
72edce86ba Translations update from Weblate - Slovak 89% Romanian 100% (#2106)
Co-authored-by: MikeSierraRO <razvan@zeces.ro>
Co-authored-by: Anaa Ara <martakanuu@protonmail.com>
2020-11-17 11:51:42 +01:00
Kevin Papst
288fc5dd08 fix timezone for timesheets created by other users (#1509) 2020-11-13 17:46:32 +01:00
Kevin Papst
7503899a9b re-order fields in search form (#2120) 2020-11-12 11:14:43 +01:00
Kevin Papst
49a4d1069a sort teams alphabetically in team_list (#2119) 2020-11-12 11:03:37 +01:00
Kevin Papst
f916e0ce25 fix timesheet importer for entries passing midnight (#2098) 2020-11-10 16:02:30 +01:00
Kevin Papst
bd4efad22b added event to manage javascript translations (#2104) 2020-11-09 13:28:22 +01:00
Weblate (bot)
66865b553e updated Hebrew translations (#2102) 2020-11-09 10:08:09 +01:00
Weblate (bot)
62bfe0af2b updated Korean translations (#2093)
Co-authored-by: BeckeBauer <berko@gmx.de>
Co-authored-by: 김옥현 <dhrgusdlrns3@gmail.com>
2020-11-07 20:05:40 +01:00
Kevin Papst
db8714a87b fix styling issues in PDF invoice (#2097) 2020-11-07 19:31:18 +01:00
Kevin Papst
5868741fe3 use different event to reload recent activities (#2096) 2020-11-07 16:51:30 +01:00
Kevin Papst
08e284c51b sort composer packages in doctor (#2089) 2020-11-03 23:59:44 +01:00
Kevin Papst
1757ffd8ee improved api docs styling (#2085) 2020-11-02 10:58:38 +01:00
Weblate (bot)
2d3c46957f Translations update from Weblate (#2081)
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
2020-11-02 10:35:34 +01:00
Kevin Papst
ae0a6ebfb5 fix Sunday as first dow of week in date-picker (#2084) 2020-11-02 10:32:46 +01:00
Kevin Papst
aba06ba3f0 improve customer validation (#2083) 2020-11-02 00:50:26 +01:00
Weblate (bot)
b012332d82 Translations update from Weblate (#2071)
* Translated using Weblate (Portuguese (Brazil))
* Translated using Weblate (Dutch)
* Translated using Weblate (Portuguese)
* Translated using Weblate (Spanish)

Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Leonardo Ferreira <miwibib401@ezeca.com>
Co-authored-by: Bruno Arakaki <brn.arakaki@gmail.com>
Co-authored-by: IxFail <mitchelnijkamp1@msn.com>
2020-10-30 13:22:34 +01:00
Kevin Papst
4a94525320 prepare release 1.11.1 (#2073) 2020-10-28 20:35:27 +01:00
Kevin Papst
1444593bbd validate color (#2072) 2020-10-28 20:01:08 +01:00
Kevin Papst
82e1d6b917 fix assertion deprecation (#2068) 2020-10-28 16:56:09 +01:00
Kevin Papst
61c0cbc887 fix auto-stop when starting timesheet with tags (#2067) 2020-10-27 18:35:22 +01:00
Weblate (bot)
8c39b4197e Translated using Weblate (French) (#2066)
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
2020-10-27 13:36:45 +01:00
Kevin Papst
e24c647b18 release 1.11 (#2063) 2020-10-26 12:55:24 +01:00
Kevin Papst
6db21d7b34 fix xss (#2060) 2020-10-26 11:35:31 +01:00
Kevin Papst
7ae55c1980 switch to php workflow v2 (#2061) 2020-10-26 11:21:03 +01:00
IxFail
bd167b6088 Dutch translation update (#2058) 2020-10-26 01:28:46 +01:00
vilu85
b9b7bf845c Finnish translations (#2046) 2020-10-26 00:27:10 +01:00
Weblate (bot)
fc5ebb619e Translations update from Weblate (#2011)
Co-authored-by: Bruno Arakaki <brn.arakaki@gmail.com>
Co-authored-by: Katarzyna Sobolewska <kasia.sobolewska1@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: aquilesi8 <adamdavies7@protonmail.com>
Co-authored-by: koffevar <egor.kafisov@gmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: David Ordás <davorpatech@gmail.com>
Co-authored-by: iglv <ilv0609@gmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2020-10-25 23:15:46 +01:00
Kevin Papst
abd31e3785 allow to configure database version via ENV var (#2055) 2020-10-24 21:00:43 +02:00
Kevin Papst
8a8f5cb6ce Fix Composer 2 compatibility (#2056) 2020-10-24 19:33:49 +02:00
Kevin Papst
2ad8f8a4bb prevent export and invoice from breaking on equal sign (#2054) 2020-10-23 02:00:50 +02:00
Kevin Papst
4533c994a3 SQL and performance improvements (#2017) 2020-10-23 00:31:53 +02:00
Kevin Papst
6ed7ee76e7 support custom fields in timesheet batch update (#2043) 2020-10-21 17:10:11 +02:00
Kevin Papst
167a826f50 improved timesheet importer (#2049) 2020-10-21 11:44:07 +02:00
Kevin Papst
032db11b48 added dateformat and timezone options to project importer (#2042) 2020-10-16 12:53:33 +02:00
Kevin Papst
996f05c73a added project importer and grandtotal converter (#1468) 2020-10-11 22:08:00 +02:00
Kevin Papst
0ce02b4f06 some arbitrary improvements (#2032)
* show version in doctor
* added flag to import activities as global
2020-10-08 17:17:21 +02:00
Kevin Papst
fb31319fcf open create form for activity/project/customer in modals (#2025) 2020-10-08 01:12:34 +02:00
Kevin Papst
46bf01d93e lifecycle events for projects (#2013) 2020-10-07 19:44:55 +02:00
Kevin Papst
c2ec15aeb3 add a i18n api field for the users "now" datetime (#2007) 2020-10-07 00:04:20 +02:00
Kacper Rogula
bb97611349 Execute migrations in kimai:reset-dev command (#1982) 2020-10-03 14:56:51 +02:00
Kevin Papst
825e492891 Notifications by SweetAlert2 (#1508) 2020-10-02 19:37:30 +02:00
Kevin Papst
0aa6baea2f explain limited access if project or customer has team permission (#2006) 2020-10-01 19:16:31 +02:00
Weblate (bot)
cafd84cee4 Translations update from Weblate (#1994)
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Kevin Papst <kevin@kevinpapst.de>
Co-authored-by: Balázs Fülöp <fulopbalazs91@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Juri Grabowski <hosted-weblate@jugra.de>
Co-authored-by: Katarzyna Sobolewska <kasia.sobolewska1@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: Péter Gyetvai <gyetpet@mailbox.org>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: Kevin Papst <kpapst@gmx.net>
2020-10-01 10:52:11 +02:00
Kevin Papst
0751c14907 highlight reporting in navigation for user-by-week report (#2005) 2020-09-30 18:47:17 +02:00
Kevin Papst
de0a9f8303 Fix user-email validation (#2004) 2020-09-30 18:38:34 +02:00
Kevin Papst
145116f657 fix backdrop location of sweetalert2 modals (#1997) 2020-09-28 19:22:53 +02:00
Kevin Papst
6a8c820e09 Update issue templates 2020-09-25 23:58:22 +02:00
Kevin Papst
2b033710e2 events: extend system configurations and set invoice formatter (#1995) 2020-09-25 19:51:23 +02:00
Kevin Papst
23f484a14d fix restart timesheet with meta-fields (#1993) 2020-09-24 16:55:23 +02:00
Kevin Papst
4cfa7347e9 ascending order for invoice items in command (#1980) 2020-09-18 12:08:09 +02:00
Toby Batch
aa1ea98579 added cwd in kimai:reload command (#1951) 2020-09-18 11:53:53 +02:00
Kevin Papst
38249e6b5e base plugin installation command (#1953) 2020-09-18 01:10:48 +02:00
Kevin Papst
c02a882eff allow to decrement invoice counter (#1947)
* added increment for cc, ccy, ccn, ccd
2020-09-17 02:53:38 +02:00
Kevin Papst
9ef32e75c5 added drag and drop for new records via calendar (#1962) 2020-09-17 01:13:48 +02:00
Kevin Papst
14b3de4300 translation fixes for weblate compatibility (#1970) 2020-09-13 12:18:46 +02:00
Kevin Papst
02d54fd208 weblate compatibility (#1969) 2020-09-13 12:06:01 +02:00
Kevin Papst
d1e9e3e43f fixing "translation id" in hebrew and romanian (#1968) 2020-09-13 10:57:10 +02:00
Kevin Papst
f279302009 adjusted translation file references (#1966) 2020-09-13 01:44:45 +02:00
Kevin Papst
e2a742b5c1 cleanup global context (#1943) 2020-09-08 20:35:14 +02:00
Kevin Papst
217960de60 configure first day of week for calendar and date-picker (#1952) 2020-09-05 18:31:37 +02:00
Kevin Papst
ed528eb19a bump tests to phpstan level 5 (#1922) 2020-09-05 16:48:19 +02:00
Kevin Papst
b69ea0648c bugfix release 1.10.2 (#1942) 2020-08-30 12:19:20 +02:00
IxFail
193d193352 Dutch translation update (#1939) 2020-08-30 11:37:04 +02:00
Kevin Papst
8e801c6829 added support for ordering meta fields (#1940) 2020-08-30 11:24:12 +02:00
Kevin Papst
e7da2b408a fixing formatting issues for money without currency (#1932) 2020-08-29 16:30:35 +02:00
Luca Buoncristiani
14255d71f7 Update italian translations (1.10) (#1929)
Co-authored-by: Luca Buoncristiani <luca.buoncristiani@bonificaumbra.it>
2020-08-27 13:33:46 +02:00
Kevin Papst
a258516322 bump version 1.10.1 (#1927) 2020-08-27 11:48:08 +02:00
Kevin Papst
d25bcfe29c use configured language for non-twig invoice templates (#1924) 2020-08-26 23:41:47 +02:00
Kevin Papst
8464cd925e query widget data for user (#1917) 2020-08-24 18:37:42 +02:00
Kevin Papst
c2c4f087e4 cleanup for 1.10 (#1906) 2020-08-22 19:15:54 +02:00
IxFail
064d083db6 updated dutch translations (#1905) 2020-08-21 21:52:24 +02:00
Kevin Papst
ede21fe46a more extension points for plugins (#1904) 2020-08-21 19:38:17 +02:00
Kevin Papst
5c79e68c77 reporting cleanup (#1903)
- show form error when trying to load invalid user
- added non-breaking space to first table cells to allow copy&paste to excel
- added several css classes to allow customizations
- remove team assignment check for user selection
2020-08-21 11:31:42 +02:00
Kevin Papst
e6d01f2a23 allow to order tables by more fields (#1902) 2020-08-21 10:48:57 +02:00
Kevin Papst
af4101c9fe fix distinct vs group_by (#1901) 2020-08-20 22:29:28 +02:00
Kevin Papst
b9ee811cbf helper for handling billable fields (#1900) 2020-08-20 15:03:57 +02:00
Kevin Papst
7a1dada9dc stacked bars in dashboard widget (#1893)
* cleanup global context usage in widgets
* allow empty strings as time format
* fix order and update of user settings form
* added link to reporting documentation
2020-08-19 13:19:08 +02:00
Kevin Papst
1de76ba266 added link to reporting documentation 2020-08-19 10:46:08 +02:00
Kevin Papst
ec612d1046 fix duration calculation for multiple entries of same activity 2020-08-19 10:08:25 +02:00
Kevin Papst
15c3acfa0b updated test 2020-08-19 00:37:49 +02:00
Kevin Papst
6aac85bddb updated test 2020-08-19 00:03:11 +02:00
Kevin Papst
d8ae7635d1 fix order and update of user settings form 2020-08-18 23:22:16 +02:00
Kevin Papst
fbb3dd89cf removed unused dependency 2020-08-18 15:08:43 +02:00
Kevin Papst
8d1e24ae90 removed unused dependency 2020-08-18 15:06:49 +02:00
Kevin Papst
367a901207 allow empty strings as time format 2020-08-18 15:03:46 +02:00
Kevin Papst
9597015413 cleanup global context usage in widgets 2020-08-18 11:39:41 +02:00
Kevin Papst
08696651fd Merge branch 'master' into stacked-working-hours 2020-08-17 19:04:00 +02:00
Kevin Papst
661c3897b0 new form type to select a daytime in system-configuration (#1895) 2020-08-17 00:18:51 +02:00
Kevin Papst
4200208c7b Bugfix in JS for project/activity selection in timesheet edit dialog (#1894) 2020-08-16 16:20:24 +02:00
Kevin Papst
aba7e05ee2 Merge branch 'master' into stacked-working-hours 2020-08-16 12:24:18 +02:00
Kevin Papst
ed4b82f4b1 Weekly reporting view (#1892) 2020-08-16 12:23:41 +02:00
Kevin Papst
2d4cfbe821 Weekly reporting view (#1892) 2020-08-16 12:20:33 +02:00
Kevin Papst
34a1e07824 adapt test 2020-08-16 01:50:40 +02:00
Kevin Papst
bb4233ddfe use stacked bars in dashboard to show different working tasks per day 2020-08-16 01:43:38 +02:00
Kevin Papst
2449a1e8bf fix serialized dates from API (#1888) 2020-08-12 21:56:47 +02:00
synaestic
dc9bc8a8ca assign existing tag on timesheet import (#1877) (#1883) 2020-08-12 21:54:00 +02:00
Kevin Papst
0914ebf737 added team permissions for activities (#1872) 2020-08-08 18:50:04 +02:00
Kevin Papst
dc162bf385 bump serializer bundle (#1867) 2020-08-04 10:28:43 +02:00
Kevin Papst
86add9b9f7 fix batch update resets export state (#1866) 2020-08-04 10:05:05 +02:00
Kevin Papst
476e6ca150 remove usage of getenv from codebase (#1861) 2020-08-02 01:02:04 +02:00
Kevin Papst
6f8c0e3cb6 disable headings in markdown (#1843) 2020-07-26 21:06:33 +02:00
Kevin Papst
6bd2390acf fix test 2020-07-26 20:47:06 +02:00
Kevin Papst
599b18ac99 link reporting screen for users and month charts (#1842) 2020-07-26 20:28:48 +02:00
Kevin Papst
130e6ac057 handle hidden users in teams (#1841)
* allow to add disabled users to team
* allow to include disabled users in user-select
* include disabled users when editing team members ONLY if they are already part of team
2020-07-26 13:13:06 +02:00
Kevin Papst
ecea92942c prepare release 1.10 (#1837) 2020-07-21 17:07:40 +02:00
Kevin Papst
12e348544d invoice: increase number, customer specific counter (#1836) 2020-07-21 16:11:52 +02:00
Kevin Papst
4b8f743fa6 added some more tests (#1835) 2020-07-21 15:27:29 +02:00
Kevin Papst
aee063bb3c added annotation based exporter (#1831)
* added user exporter
* added project exporter
* added customer exporter
* added activity exporter
2020-07-21 03:37:51 +02:00
Kevin Papst
93a23f3fa3 fix group by with ONLY_FULL_GROUP_BY mode (#1830) 2020-07-18 10:16:15 +02:00
Kevin Papst
7907b43ef7 UI improvement (#1829)
* open "create new project/activity" form in modal
* re-use action translation for create action title
* do not pre-select first customer in list - fixes #1828
* sort user preferences in sections for better visual grouping
* show comment date only in hover of user avatar
2020-07-17 20:32:11 +02:00
Kevin Papst
c843dba29a API improvements (#1826) 2020-07-17 20:30:16 +02:00
Kevin Papst
5864c7e127 added username to export (#1827) 2020-07-17 02:32:39 +02:00
Kevin Papst
f57b314466 Preparations for coming features (#1818)
* removed unused convert_tz code
* added timestampable listener to support automatic updates of change columns
* added modified_at, billable and category timesheet fields
* new timesheet API query parameter modified_after
2020-07-13 23:35:06 +02:00
Kevin Papst
f2dd2331a5 polish detail pages (#1817) 2020-07-12 20:11:13 +02:00
Kevin Papst
164af7ae02 added reporting screen (#1805) 2020-07-12 14:05:14 +02:00
Kevin Papst
b97d9f692d added team permissions on user queries (#1815) 2020-07-12 01:18:57 +02:00
Kevin Papst
32c1e3258e invoices: unified money, number and date formats and fully respect configured language (#1814) 2020-07-10 15:09:52 +02:00
Kevin Papst
19e4ebf88c several enhancements (#1808)
* show all fields in timesheet api docs
* fix action dropdown links #1806
* apply same validation rules for passwords and for api-tokens #1753
* added "duplicate team" action #1136
2020-07-10 03:57:15 +02:00
Kevin Papst
ea39d5c74d new translations to clarify working time periods (#1802) 2020-07-04 19:44:44 +02:00
Kevin Papst
682681afa5 added domain logic for permission checks (#1803) 2020-07-04 13:29:03 +02:00
Honza Kopecký
091740f407 timesheet lockdown with grace period (#1644) 2020-07-02 18:54:17 +02:00
Kevin Papst
e22830790b new system-config to prevent overlapping records (#1720) 2020-06-28 22:09:50 +02:00
Kevin Papst
e566305426 updated composer dependencies (#1796) 2020-06-28 15:43:38 +02:00
Maarten Kooyman
8530a03449 added support for weekly invoices (#1780) 2020-06-28 15:13:35 +02:00
Oscariremma
1a24aabb6f Fixed SV locale date format (#1790) 2020-06-25 19:34:53 +02:00
Kevin Papst
381f46cdd8 Invoices: show only custom documents in upload form (#1786) 2020-06-19 18:31:08 +02:00
Kevin Papst
470a6b30e0 configuration improvements (#1784) 2020-06-16 18:35:09 +02:00
Kevin Papst
ea4e930cbb Store sessions in database (#1736) 2020-06-11 16:54:51 +02:00
Kevin Papst
a75bdc52f6 Invoice: switch from and to address (#1776) 2020-06-11 15:19:29 +02:00
Kevin Papst
21d8e8bbb8 improve configurations for usage in multi-environment setups (#1775) 2020-06-11 01:26:57 +02:00
Oscariremma
57b4473bc8 Updated swedish translations (#1774) 2020-06-09 22:54:58 +02:00
Kevin Papst
ae6beff5d8 Allow to recalculate rates via bulk edit (#1768) 2020-06-05 12:25:56 +02:00
Kevin Papst
da5563960b Added Vietnamese translations (#1758) 2020-06-04 16:21:12 +02:00
Kevin Papst
3b6df1cb84 fix by-project and by-customer queries creating empty invoices (#1756) 2020-06-02 12:56:08 +02:00
Kevin Papst
91736e89cc added hebrew translations (#1724)
first day of week in datepicker is locale specific now
2020-05-24 13:58:13 +02:00
Kevin Papst
e2a4998341 added romanian translations (#1722) 2020-05-23 12:07:57 +02:00
Kevin Papst
3c511df7f7 fix required fields in API (#1718) 2020-05-19 11:24:30 +02:00
Kevin Papst
e61ffe4db4 create timesheet for multiple users (#1716) 2020-05-18 23:35:43 +02:00
Kevin Papst
9da46bbdcd re-using menu translation for tags (#1717) 2020-05-18 16:03:45 +02:00
Kevin Papst
e3749c8e8f added pdf template and other invoice improvements (#1693) 2020-05-10 16:19:59 +02:00
Kevin Papst
4e1ffedc20 make export layout definitions changeable (#1684) 2020-05-02 12:55:30 +02:00
Kevin Papst
35f864256f include project name in invoice name (#1672) 2020-04-29 18:51:33 +02:00
Kevin Papst
ad2698dae2 fixed multilineIndent (#1669) 2020-04-28 17:13:02 +02:00
Kevin Papst
46a7b13293 display more columns in invoice template listing (#1663) 2020-04-25 16:34:15 +02:00
Kevin Papst
cbd7124cec fix removing single permissions from event (#1661) 2020-04-24 19:02:21 +02:00
Kevin Papst
0debc70ead support budget and timeBudget in API calls (#1658) 2020-04-23 18:49:48 +02:00
Kevin Papst
f8331b74c7 using font awesome icon to render color dot (#1656) 2020-04-23 16:47:50 +02:00
Kevin Papst
12c4205785 added translate filter for meta-fields in detail pages (#1655) 2020-04-23 16:45:50 +02:00
Kevin Papst
8f5c4dc4ec fix API annotation for customer collection (#1654) 2020-04-23 16:28:03 +02:00
Kevin Papst
711139ad4c delete invoices (#1652)
* include all meta fields as template variables
* support invoice preview via command
* support deletion of generated invoices
2020-04-23 12:19:07 +02:00
Kevin Papst
cfe0724620 prepare release 1.9 (#1635) 2020-04-19 19:31:43 +02:00
Kevin Papst
aeef1dae2e bump version 2020-04-19 15:39:19 +02:00
Kevin Papst
0f3fa8fdbe Code improvements (#1649)
* use global namespace for faster lookups
* phpstan level 5
2020-04-19 14:37:14 +02:00
Lukas
7b25e9acaf added events for timesheet actions (#1598) 2020-04-15 18:07:14 +02:00
Kevin Papst
be00a519b3 show expired projects in search filter (#1641) 2020-04-15 16:19:32 +02:00
Kevin Papst
4254c2590a remove roles from users when role is deleted (#1640) 2020-04-15 15:44:28 +02:00
Kevin Papst
57254bf175 fix duration calculation in admin timesheet view (#1639) 2020-04-15 11:48:18 +02:00
Kevin Papst
fe8fecafeb minimize count and visibility columns for better table alignments (#1636) 2020-04-15 11:46:54 +02:00
jojoemade
0bc9e74853 added colors to tags (#1631) 2020-04-14 14:18:11 +02:00
KuboF Hromoslav
1dfceeea99 Update Czech translation (#1630) 2020-04-12 18:36:59 +02:00
Kevin Papst
68d703d1e3 added internal rates (#1591) 2020-04-08 14:50:15 +02:00
KuboF Hromoslav
82c713031e Esperanto translations (#1607) 2020-04-07 22:19:52 +02:00
Kevin Papst
5579c3d04f allow to create default team if none with the project/customer name exists (#1624) 2020-04-07 19:07:06 +02:00
Kevin Papst
5e7b0c7826 fixed restart timesheet with tags with active record (#1622) 2020-04-07 17:30:39 +02:00
Kevin Papst
9e1e1a7a96 added event before rendering permissions (#1599) 2020-03-30 01:22:57 +02:00
Kevin Papst
7becddc3a4 allow all characters as search term in API (#1597) 2020-03-30 01:06:18 +02:00
Kevin Papst
dfe949264f update funding for sponsors information 2020-03-28 10:49:32 +01:00
Kevin Papst
163fb1be4a fix non-ascii character break invoice download (#1592) 2020-03-27 00:55:23 +01:00
Kevin Papst
33f76f426c customize html and pdf exports with twig templates (#1582) 2020-03-23 01:51:02 +01:00
Kevin Papst
1ca1b00d11 added json, xml and txt invoice renderer (#1576) 2020-03-21 01:01:47 +01:00
Kevin Papst
c89750fe94 ensure user attributes are truncated (#1580) 2020-03-21 00:21:06 +01:00
Adrian Rudnik
fb2ce0d30a xlsx improvements (#1518) 2020-03-20 18:29:26 +01:00
Kevin Papst
b8c5323ece added command to create invoices via bash (#1574) 2020-03-20 13:14:05 +01:00
Kevin Papst
7b13ea860a switch to kimai specific user-bundle fork (#1568) 2020-03-17 21:24:51 +01:00
Kevin Papst
4b1cfa5840 added multi-select for customer, project and activity (#1557) 2020-03-16 14:06:26 +01:00
Kevin Papst
6eeb01ad48 validate v1 data before import (#1552) 2020-03-15 23:34:14 +01:00
Kevin Papst
e6e6a1eeea added invoice archive & configurable invoice numbers (#1541) 2020-03-14 01:16:58 +01:00
Kevin Papst
dd64eb98f9 support more columns in listings (#1548) 2020-03-13 18:21:16 +01:00
Kevin Papst
9cd111bae5 fix timesheet permissions for non-admins (#1542) 2020-03-13 18:02:35 +01:00
Kevin Papst
137dcbf7e1 update to latest composer libs (#1545) 2020-03-12 19:33:11 +01:00
Kevin Papst
fbdc668a22 show short summary under export table (#1547) 2020-03-12 17:05:51 +01:00
Kevin Papst
7f931cbfd3 highlight overlapping times (#1534) 2020-03-12 16:35:25 +01:00
Kevin Papst
7b7a962e04 fix deprecations in migrations (#1544) 2020-03-12 00:42:47 +01:00
Sebastian Schreiber
e4610af235 migrate from zend-ldap to laminas-ldap (#1530) 2020-03-12 00:34:23 +01:00
Kevin Papst
e7458e76e1 improve kimai v1 importer for projects without customer (#1543) 2020-03-12 00:20:22 +01:00
Luca Buoncristiani
04a43378d8 Italian translations update (#1538) 2020-03-11 16:38:28 +01:00
Honza Kopecký
809acb47bf Import groups from Kimai v1 (#1532) 2020-03-08 12:34:17 +01:00
Kevin Papst
ace2788b57 fix pdf creation for very large exports (#1533) 2020-03-08 00:24:49 +01:00
Sebastian Schreiber
d60e738bd2 Escape distinguished name in ldap search (#1528) 2020-03-06 16:16:31 +01:00
Kevin Papst
a50a59bbca inline css for html invoices (#1526) 2020-03-06 03:21:39 +01:00
Adrian Rudnik
76a6ea57a1 fixed sweet alerts blocking major ui elements (#1517) 2020-03-06 03:02:30 +01:00
Kevin Papst
9907694ef6 disable title on small reponsive screens (#1527) 2020-03-06 02:20:30 +01:00
Kevin Papst
20b3c31cbd code cleanup for phpstan level 4 (#1522) 2020-03-05 02:15:16 +01:00
Kevin Papst
25a74ffeee fixing user specific rates in kimai v1 importer (#1521) 2020-03-05 00:18:31 +01:00
Kevin Papst
b8273c4441 prepare release 1.8 (#1505) 2020-03-01 14:25:59 +01:00
Kevin Papst
8d3a305ec3 handle negative duration (#1510) 2020-03-01 13:46:37 +01:00
Kevin Papst
72b73bc9a6 support multi-line values in excel invoices (#1507) 2020-02-29 20:06:45 +01:00
Kevin Papst
ec31d206e2 added UI configuration for default start-time (#1506) 2020-02-28 16:06:00 +01:00
Kevin Papst
bf11de82fc Upload invoice documents via UI (#1495) 2020-02-27 23:19:19 +01:00
Kevin Papst
34d2228d5d option to use decimal format in user timesheet export (#1489) 2020-02-24 18:03:30 +01:00
Kevin Papst
0e507804b1 human friendly name for bundles (#1491) 2020-02-22 22:56:37 +01:00
Kevin Papst
6c7477f1a9 edit single system config section in modal (#1490) 2020-02-22 18:53:03 +01:00
Kevin Papst
886fc8d74e action to duplicate timesheet record (#1478) 2020-02-18 15:32:33 +01:00
Kevin Papst
7b218c33f9 added generic rules for auth firewall (#1472) 2020-02-18 13:54:03 +01:00
Kevin Papst
c67617262f duplicate projects with all settings (#1465) 2020-02-13 16:47:20 +01:00
Kevin Papst
057c824e9f replaced mailer library, added email templates (#1462) 2020-02-12 16:05:13 +01:00
Kevin Papst
1b4f4bba22 added option to use only pre-defined tags (#1463) 2020-02-12 15:35:42 +01:00
Philipp Strobel
847798eec1 use different arrow for paginated box widget (#1431) 2020-02-10 22:16:16 +01:00
Kevin Papst
52c7437076 added user-specific rates (#1455) 2020-02-10 20:29:43 +01:00
Kevin Papst
465d7166d4 remove pagerfanta bundle (#1458) 2020-02-10 16:38:26 +01:00
MikKrz
733aa58de8 added Polish translations (#1426) 2020-02-10 00:23:37 +01:00
Kevin Papst
9620627ea6 fix dates in project API (#1451) 2020-02-10 00:16:44 +01:00
Kevin Papst
e06314e802 replace travis with github actions (#1452) 2020-02-09 23:54:04 +01:00
Kevin Papst
986d922855 invoices: choose language and duration format (#1438) 2020-02-04 20:27:36 +01:00
Kevin Papst
75dcae6fbe update to Symfony 4.4 (#1275) 2020-02-04 15:29:26 +01:00
Luca Buoncristiani
375691365e added missing italian translation files (#1437) 2020-02-04 13:31:20 +01:00
infeeeee
5c5af05408 fixed glyphs in pdf export table header (#1435) 2020-02-03 17:18:39 +01:00
Kevin Papst
69cf7015d8 optimize detail pages, add new permission for comment posting (#1432) 2020-02-02 18:16:23 +01:00
Kevin Papst
6a533579b7 added support for saml login (#1408) 2020-01-31 19:47:34 +01:00
Kevin Papst
3ff46e06c0 code improvements (#1423)
* removed unused mapping information
* added support for further field types
* fixed changing date objects for begin and end
* added more project fields as invoice variables
* theme update and asset rebuild
2020-01-31 17:07:14 +01:00
Kevin Papst
9e2fccb105 added pagination to working time widget (#1418) 2020-01-30 21:51:27 +01:00
Kevin Papst
f2c6d8aee1 better handling of custom field columns in tables (#1419) 2020-01-29 15:18:10 +01:00
Kevin Papst
836a530b86 API methods to handle teams and users (#1384) 2020-01-28 22:28:31 +01:00
Kevin Papst
a31e027c54 updated slovak translations (#1414) 2020-01-28 22:02:37 +01:00
Kevin Papst
a1b554b3ac various code improvements (#1415) 2020-01-28 21:57:34 +01:00
Kevin Papst
79c4c237fa support for auto linking urls with file protocol (#1402) 2020-01-22 17:25:11 +01:00
Kevin Papst
85a35a3063 prepare release 1.7 (#1388) 2020-01-19 21:50:29 +01:00
Kevin Papst
e0e391efbe prepare release 1.7 (#1388) 2020-01-19 21:49:41 +01:00
Kevin Papst
9eff89963c prepare release 1.7 (#1388) 2020-01-19 21:49:04 +01:00
Lukas
352fac26f3 added missing raw filter (#1393) 2020-01-19 21:47:40 +01:00
Lukas
8c16bf126a added missing raw filter (#1393) 2020-01-19 21:47:17 +01:00
Lukas
42de968f8f added missing raw filter (#1393) 2020-01-19 21:46:47 +01:00
ikusimakusi
ed8f024e42 added Basque translations (#1382) 2020-01-17 15:28:12 +01:00
Kevin Papst
e534369c5b fix widgets imports (#1387) 2020-01-17 00:02:02 +01:00
Kevin Papst
fed797df93 order user roles and permissions (#1372) 2020-01-16 16:54:42 +01:00
Kevin Papst
6a44dbfe83 detail pages for customers and projects (#1371) 2020-01-16 16:25:28 +01:00
Kevin Papst
28c5357f11 allow to hide rate or duration in html export summary (#1370) 2020-01-14 13:46:07 +01:00
Kevin Papst
a8824f92a1 added support on PHP 7.4 (#1355) 2020-01-07 21:43:33 +01:00
Kevin Papst
e57b69973f re-written exporter for simpler extendability (#1349) 2020-01-07 21:20:45 +01:00
Kevin Papst
d6798eed1e added project start and end date (#1303)
* added sortable js library
* activity in invoice is optional
* added javascript widget for paginated boxes
* fix activity dropdown for globals only
* added timesheet service to reduce code duplication
* use repository to query for teams in dropdowns
* added project validator
* validate project start and end against timesheet
* include begin and end in dynamic form requests for projects
* added timezone and language option to import flag, improve timesheet import speed
* deactivate cross-timezone filter
* add virtual fields to field order list
* composer update
* added param to ignore dates
* position loader icon fixed - fixes #1330
* permission problem when creating a new project - fixes #1340
* remove dev dependencies webserver and thanks bundle
* stop information leak (begin and end date) in duration mode - fixes #1307
* unify timesheet edit dialog for user and admins
* fix security issue, own rates exposed to unauthorized users in multi-update dialog
2020-01-05 02:49:01 +01:00
hmr-it-jr
50383673ca added api endpoint to return instance specific timesheet config (#1335) 2020-01-04 01:44:11 +01:00
Kevin Papst
022dbe32e5 allow empty date in export (#1338) 2019-12-31 20:24:47 +01:00
Kevin Papst
baac4eb698 set rates via batch update (#1326) 2019-12-29 16:52:43 +01:00
Kevin Papst
8aa8f452d5 added exported filter in timesheet view (#1327) 2019-12-29 13:55:15 +01:00
Kevin Papst
54255cf923 validate tag name (#1325) 2019-12-16 16:55:34 +01:00
Kevin Papst
bd3d82d224 fix daterange filter for multi-timezone setups (#1321) 2019-12-16 16:23:03 +01:00
Kevin Papst
b14ad9f3ab renamed translation files from .xliff to .xlf (#1314) 2019-12-09 13:25:31 +01:00
Kevin Papst
261b4646e4 added chinese translations (#1310) 2019-12-09 12:56:38 +01:00
Kevin Papst
d62e1c4468 simplify to add widgets via plugin (#1308) 2019-12-06 23:36:03 +01:00
benzel
5b0266c462 clarified translation (#1309) 2019-12-06 22:05:33 +01:00
Kevin Papst
06118dcabb loose email validation on customer (#1224) 2019-12-02 15:06:06 +01:00
Kevin Papst
984c852ab6 Release 1.6.2 (#1289)
* include user teams in user entity
* prevent unauthorized access via API
* improve teamlead permission handling in team timesheets
* add team data to user entity
* add security tests
* highlight menu for invoice template copy
* unified handling of invoice data across all templates
* access to the current users data in invoice templates
* permission improvement in invoice form
* allow to skip record rows
* allow to add new invoice locations without overwriting the global ones
* allow to order user preferences
* change permission for normal users with access to view_other_timesheets
* properly validate invoice template field length
* allow to replace multiple variables in cell values text
* upgraded office invoice template
* doctrine deprecation fix
* upgrade phpoffice/phpword
* fix future begin check for default rounding rules
* dashboard widget counter: respect visibility and teams - fixes #1161
* fix future begin check for default rounding rules
* added new events for pre and post invoice rendering
* fix permission issue for users without team seeing all records
* prevent error in spreadsheet renderer for empty invoices
2019-12-02 10:57:03 +01:00
Badgie
47414cfd0e Danish translations (#1296) 2019-12-02 00:25:28 +01:00
Kevin Papst
fdf4fdfb6a fix sqlite installer (#1283) 2019-11-26 19:32:16 +01:00
Kevin Papst
5ee0396a02 Release 1.6.1 (#1282) 2019-11-26 17:22:31 +01:00
Kevin Papst
724eb9c658 release 1.6 (#1277) 2019-11-24 15:29:01 +01:00
Kevin Papst
936ab05882 prepare release 1.6 (#1270) 2019-11-24 00:02:43 +01:00
Kevin Papst
817a43b178 added csv importer for timesheet and project data (#1216) 2019-11-23 22:02:55 +01:00
Kevin Papst
0f16f42887 Select2 search enhancement (#1276) 2019-11-23 14:26:35 +01:00
Kevin Papst
641c199c8c API: allow to set user for timesheet (#1259) 2019-11-23 14:21:46 +01:00
Aptevo
04dd6b900f added de_CH language (#1272) 2019-11-23 11:29:29 +01:00
Kevin Papst
536a05210e enable interactive features of Swagger UI (#1273) 2019-11-22 21:59:25 +01:00
Kevin Papst
60d16016e8 allow to configure calendar slot duration (#1269) 2019-11-22 01:30:25 +01:00
Kevin Papst
9222075c7a Added update command (#1268) 2019-11-21 23:15:41 +01:00
1willem
641dcdff85 Dutch translation files (#1226) 2019-11-18 21:48:16 +01:00
Kevin Papst
668649be7a use homepage link to redirect from error page (#1260) 2019-11-18 12:29:21 +01:00
Kevin Papst
0de936d474 fix the password reset page layout (#1261) 2019-11-18 12:28:59 +01:00
Kevin Papst
47bd8b0ce7 added new invoice fields, improved invoice templates (#1258) 2019-11-18 12:01:26 +01:00
Kevin Papst
2287c9951c invoice: money fields without currency, title cleanup (#1254) 2019-11-15 18:54:08 +01:00
Kevin Papst
94c28ebbc5 Update and delete multi timesheets and tags (#1240) 2019-11-15 01:25:31 +01:00
Kevin Papst
b4739f8f03 allow to include bundle configuration in system configuration screen (#1235) 2019-11-14 19:25:38 +01:00
Kevin Papst
c32cd36861 fix reset input for forms (#1251) 2019-11-14 19:09:55 +01:00
Kevin Papst
945562b34d fixes wrong foreign keys on timesheet_tags join table (#1242) 2019-11-14 01:57:58 +01:00
Kevin Papst
af0f89774e User roles and permission management via Admin UI (#1231) 2019-11-10 18:53:56 +01:00
Kevin Papst
c6c4098759 rounding rules via admin screen, round begin when starting record (#1229) 2019-11-10 13:57:05 +01:00
Kevin Papst
fa1c79e15c support different formats in user timesheet exports (#1222) 2019-11-08 16:10:38 +01:00
Kevin Papst
0705c26513 highlight menus on sub-pages (#1220) 2019-11-07 11:19:10 +01:00
Kevin Papst
140fa4b7c0 prepare release 1.5 (#1214) 2019-11-03 17:31:33 +01:00
Kevin Papst
cd88901868 re-style error pages (#1210) 2019-10-31 14:20:53 +01:00
Kevin Papst
8eec97deb5 respect users timezone in statistic widgets (#1207) 2019-10-31 00:17:31 +01:00
Kevin Papst
4331a7c9f4 fix error pages (#1209) 2019-10-31 00:16:25 +01:00
Kevin Papst
f4fcfeba9d dynamic branding options (#1205) 2019-10-30 14:22:34 +01:00
Kevin Papst
48aefb1862 fix avatar path for subdirectory installations (#1201) 2019-10-30 14:00:48 +01:00
Kevin Papst
bda9584346 reformat edit forms (#1192) 2019-10-30 13:59:53 +01:00
Kevin Papst
ed1154af67 fix absolute url (#1200) 2019-10-30 13:45:59 +01:00
Kevin Papst
c368eacd87 fixed renamed filter name, added that disabled users cannot be filtered (#1193) 2019-10-27 13:56:45 +01:00
Kevin Papst
949c59230f improve list views (#1191) 2019-10-25 16:21:30 +02:00
Kevin Papst
1114538f41 duration field in timesheet edit dialog (#1180) 2019-10-24 23:46:22 +02:00
Kevin Papst
b0f83291ee upgraded to phpunit 8 (#1155) 2019-10-24 17:35:05 +02:00
Kevin Papst
e91e4ff430 support search term in API collection requests (#1185) 2019-10-24 17:31:15 +02:00
Ahmed Şeref
4ad290d4bd added turkish translations (#1160) 2019-10-24 17:30:34 +02:00
Kevin Papst
37c6e2c0af added order-date field to project (#1186) 2019-10-24 17:04:21 +02:00
Kevin Papst
b4a45afce9 fix API login in subdirectory installations (#1181) 2019-10-24 11:06:12 +02:00
yayitazale
330ff6eb47 updated spanish translations (#1183) 2019-10-24 10:53:23 +02:00
Adrian Rudnik
c681eb1bdb docker fix to hide details for 500 errors (#1173) 2019-10-21 16:22:29 +02:00
Kevin Papst
de533e7031 Kimai 1.4.2 (#1167) 2019-10-14 12:30:11 +02:00
Kevin Papst
ae6ed3f23f Bugfixes for Kimai 1.4.1 (#1158)
* fix missing translation for preset datatable headers
* better dependency check for avatar service
* show only own teams to teamleads
2019-10-07 16:04:19 +02:00
Kevin Papst
32aa209b75 prepare release 1.4 (#1154) 2019-10-04 07:36:59 +02:00
Kevin Papst
e8c25d8ac5 added avatars, show user teams in list, new team dashboard widgets (#1150) 2019-10-03 13:44:16 +02:00
Kevin Papst
718ad4b398 fix migrations for older database versions (#1148) 2019-09-27 14:52:22 +02:00
Kevin Papst
8d81a394ee updated theme, fix deprecations in events dispatching (#1145) 2019-09-25 19:12:12 +02:00
Kevin Papst
d661c8b54e fix closing search dropdown (#1142)
* using a different library for javascript selects, fixing the closing search dropdown
* fix closing dropdown for daterangepicker
* added missing search button for mobile on customer page
* fix meta fields with same names than existing columns
2019-09-25 18:34:25 +02:00
Kevin Papst
e45e782e7d fix invoice preview (#1129) 2019-09-20 16:00:03 +02:00
Kevin Papst
5aa422dde1 Display meta-fields in datatables (#1116) 2019-09-19 20:28:31 +02:00
Kevin Papst
24cbe3d281 post 1.3 fixes (#1106) 2019-09-17 22:20:36 +02:00
Kevin Papst
13f5e76015 added czech translations (#1075) 2019-09-16 23:19:46 +02:00
Kevin Papst
35c65ba890 composer update (#1104) 2019-09-13 17:16:53 +02:00
Kevin Papst
8f2b82b832 bump version to 1.3 (#1102) 2019-09-12 00:36:48 +02:00
Kevin Papst
7f1a206a52 support installation specific translations (#1099) 2019-09-11 23:18:35 +02:00
Kevin Papst
8c503a226e fix API version string (#1096) 2019-09-11 21:44:14 +02:00
Kevin Papst
cdcf9eaf67 handles session timeouts in modals (#1092) 2019-09-10 17:07:34 +02:00
Kevin Papst
a651e55dc9 added table-column ordering (#1086) 2019-09-09 23:47:42 +02:00
Kevin Papst
d041a3f4f9 improved tag auto-completion and added tag management (#1083) 2019-09-04 23:47:33 +02:00
Kevin Papst
3ea05cb705 support multiple invoice repositories (#1084) 2019-09-04 22:50:53 +02:00
hmr-it-jr
3f27ab1e44 added mail icons (#1077) 2019-09-04 19:49:03 +02:00
Kevin Papst
e99b170d0a refactored search with free search term support (#1064) 2019-09-04 18:54:06 +02:00
Kevin Papst
49e1a1c410 use timesheet description in invoices (#1079) 2019-09-04 10:54:21 +02:00
Kevin Papst
93ab3d5666 post release 1.2 fixes (#1068)
* fix release command
* fix permission name in UPGRADING
* added missing korean calendar translations
* fixed entry.total for invoice with fixed rates
2019-09-03 11:59:34 +02:00
2552 changed files with 196566 additions and 50074 deletions

View File

@@ -1,33 +1,30 @@
codecov:
notify:
require_ci_to_pass: yes
notify:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "80...100"
status:
project:
default:
threshold: 0.5%
patch:
default:
threshold: 50%
changes: no
precision: 2
round: down
range: "80...100"
status:
project:
default:
threshold: 0.5%
patch: off
changes: no
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "diff, flags, files"
behavior: default
require_changes: yes
require_base: no
require_head: yes
branches: null
layout: "diff, flags, files"
behavior: default
require_changes: yes
require_base: no
require_head: yes
branches: null

View File

@@ -3,7 +3,8 @@ root = true
; Unix-style newlines
[*]
end_of_line = LF
end_of_line = lf
charset = utf-8
[*.php]
indent_style = space

View File

@@ -1,31 +1,16 @@
# This file is a "template" of which env vars need to be defined for your application
# Copy this file to .env file for development, create environment variables when deploying to production
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
# Configure your database connection and set the correct server version:
# for MySQL "serverVersion=5.7" and for MariaDB "serverVersion=mariadb-10.5.8"
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=5.7
# Email will be sent with this address as sender
MAILER_FROM=kimai@example.com
###> symfony/framework-bundle ###
# Email connection (disabled by default) more info at https://www.kimai.org/documentation/emails.html
MAILER_URL=null://null
# do not change, unless you are developing for Kimai
APP_ENV=prod
# should be changed to a unique character sequence
APP_SECRET=change_this_to_something_unique
#TRUSTED_PROXIES=127.0.0.1,127.0.0.2
#TRUSTED_HOSTS=localhost,example.com
###< symfony/framework-bundle ###
###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data/kimai.sqlite"
# Configure your db driver and server_version in config/packages/doctrine.yaml
# DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
DATABASE_URL=sqlite:///%kernel.project_dir%/var/data/kimai.sqlite
###< doctrine/doctrine-bundle ###
###> symfony/swiftmailer-bundle ###
# For Gmail as a transport, use: "gmail://username:password@localhost"
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
# Delivery is disabled by default via "null://localhost"
MAILER_URL=null://localhost
###< symfony/swiftmailer-bundle ###
###> nelmio/cors-bundle ###
# unlikely, that you need to change this one
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
###< nelmio/cors-bundle ###
# Running behind reverse proxies? Use those:
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2
# TRUSTED_HOSTS=localhost,example.com

4
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,2 @@
#github: [kevinpapst]
custom: https://www.kimai.org/donate/
github: [kevinpapst]
custom: ["https://www.kimai.org/donate/", "https://www.paypal.me/kevinpapst"]

View File

@@ -1,6 +1,9 @@
---
name: Bug report
about: Create a report to help us improve Kimai
about: Found a problem? Create a report to help us improve Kimai
title: ''
labels: bug
assignees: ''
---
@@ -8,27 +11,17 @@ about: Create a report to help us improve Kimai
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. See error
**Logfile**
```
Add the last lines from your logfile at var/log/prod.log, around the time when the problem happened.
Add the last lines from your logfile at "var/log/prod.log" or "Doctor > Logs", around the time when the problem happened.
```
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop/Smartphone**
Please complete the following information if this seems to be a frontend issue:
- Device: [e.g. iPhone6s]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
- Kimai version
- PHP version
- Kimai version: ?.?
- PHP version: ?.?
- Device: [Ubuntu Laptop 16 inch, Windows Desktop 27 inch, iPhone 6s]
- Browser [e.g. Firefox 81, Chrome 85, Safari 14]

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: "Support / Questions / Discussions"
url: https://github.com/kevinpapst/kimai2/discussions
about: "This forum is for support questions: eg. if you need help with your Kimai installation, want to know how to use a Kimai feature or wonder where to start creating a new Kimai plugin."

View File

@@ -1,6 +1,9 @@
---
name: Feature request
about: Suggest an idea to make Kimai better
title: ''
labels: feature request
assignees: ''
---
@@ -13,5 +16,5 @@ A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
**Screenshot**
Add a mockup / screenshot of your idea here.

View File

@@ -7,6 +7,6 @@ A clear and concise description of what this pull request adds or changes.
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
## Checklist
- [ ] I verified that my code applies to the guidelines (`composer kimai:code-check`)
- [ ] I verified that my code applies to the guidelines (`composer code-check`)
- [ ] I updated the documentation (see [here](https://github.com/kimai/www.kimai.org/tree/master/_documentation))
- [ ] I agree that this code is used in Kimai and will be published under the [MIT license](https://github.com/kevinpapst/kimai2/blob/master/LICENSE)

24
.github/lock.yml vendored
View File

@@ -1,24 +0,0 @@
# Configuration for lock-threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 60
# Issues and pull requests with these labels will not be locked. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs.
If you use Kimai on a daily basis, please [consider donating](https://www.kimai.org/donate/) to
support further development of Kimai.
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: false
# Limit to only `issues` or `pulls`
# only: pulls

69
.github/workflows/coverage.yaml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: Coverage
on:
pull_request: null
push:
branches:
- master
jobs:
tests:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: false
MYSQL_ROOT_PASSWORD: kimai
MYSQL_DATABASE: kimai
ports:
- 3306/tcp
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
matrix:
php: ['8.0']
name: Coverage (${{ matrix.php }})
steps:
- name: Clone Kimai
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: pcov
extensions: mbstring, xml, ctype, iconv, intl, mysql, zip, gd, ldap
- name: Determine composer cache directory
id: composer-cache
run: "echo \"::set-output name=directory::$(composer config cache-dir)\""
- name: Cache Composer dependencies
uses: actions/cache@v2
with:
path: "${{ steps.composer-cache.outputs.directory }}"
key: ${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
- name: Install dependencies
run: composer install
- name: Install LDAP package
run: composer require laminas/laminas-ldap
- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Run tests
run: vendor/bin/phpunit tests/ --coverage-clover=coverage.xml
env:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
APP_ENV: dev
MAILER_URL: null://localhost
TEST_WITH_BUNDLES: 1
- name: Upload code coverage
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
fail_ci_if_error: true

17
.github/workflows/docker.yaml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: 'Docker Build'
on:
release:
types: [released]
jobs:
build:
name: Dispatch release event to tobybatch
runs-on: ubuntu-latest
steps:
- name: Emit repository_dispatch
uses: mvasigh/dispatch-action@1.1.6
with:
token: ${{ secrets.DOCKER_ACCESS_TOKEN }}
repo: kimai2
owner: tobybatch
event_type: kimai_release

56
.github/workflows/lock.yaml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: 'Lock Threads'
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v3
with:
github-token: ${{ github.token }}
issue-inactive-days: '180'
exclude-issue-created-before: ''
exclude-issue-created-after: ''
exclude-issue-created-between: ''
exclude-issue-closed-before: ''
exclude-issue-closed-after: ''
exclude-issue-closed-between: ''
include-any-issue-labels: ''
include-all-issue-labels: ''
exclude-any-issue-labels: ''
add-issue-labels: ''
remove-issue-labels: ''
issue-comment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs.
If you use Kimai on a daily basis, please [consider donating](https://www.kimai.org/donate/) to
support further development of Kimai.
issue-lock-reason: 'resolved'
pr-inactive-days: '180'
exclude-pr-created-before: ''
exclude-pr-created-after: ''
exclude-pr-created-between: ''
exclude-pr-closed-before: ''
exclude-pr-closed-after: ''
exclude-pr-closed-between: ''
include-any-pr-labels: ''
include-all-pr-labels: ''
exclude-any-pr-labels: ''
add-pr-labels: ''
remove-pr-labels: ''
pr-comment: ''
pr-lock-reason: ''
process-only: ''
log-output: false

21
.github/workflows/lockfiles.yaml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Check .lock files
on:
pull_request: null
push:
branches:
- master
jobs:
lockfiles:
runs-on: ubuntu-latest
name: Verify lock file integrity
steps:
- name: Clone Kimai
uses: actions/checkout@v2
- name: Prevent file change
uses: xalvarez/prevent-file-change-action@v1
with:
githubToken: ${{ secrets.GITHUB_TOKEN }}
pattern: .*.lock
trustedAuthors: kevinpapst, dependabot

98
.github/workflows/testing.yaml vendored Normal file
View File

@@ -0,0 +1,98 @@
name: Tests
on:
pull_request: null
push:
branches:
- master
jobs:
integration:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: false
MYSQL_ROOT_PASSWORD: kimai
MYSQL_DATABASE: kimai
ports:
- 3306/tcp
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
matrix:
php: ['7.4', '8.0', '8.1']
name: Integration (${{ matrix.php }})
steps:
- name: Clone Kimai
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: mbstring, xml, ctype, iconv, intl, mysql, zip, gd, ldap
tools: cs2pr:1.1.0
- name: Determine composer cache directory
id: composer-cache
run: "echo \"::set-output name=directory::$(composer config cache-dir)\""
- name: Cache Composer dependencies
uses: actions/cache@v2
with:
path: "${{ steps.composer-cache.outputs.directory }}"
key: ${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
- name: Install dependencies
run: composer install
- name: Validate Composer
run: composer validate --strict
- name: Warmup cache for PHPStan
run: APP_ENV=dev bin/console kimai:reload -n
- name: Check codestyles
run: vendor/bin/php-cs-fixer fix --dry-run --verbose --config=.php-cs-fixer.dist.php --using-cache=no --show-progress=none --format=checkstyle | cs2pr
- name: Run PHPStan on Codebase
run: vendor/bin/phpstan analyse src -c phpstan.neon --level=5 --no-progress --error-format=checkstyle | cs2pr
- name: Run PHPStan on Tests
run: vendor/bin/phpstan analyse tests -c tests/phpstan.neon --level=5 --no-progress --error-format=checkstyle | cs2pr
- name: Lint codebase
run: composer linting
- name: Install LDAP package
run: composer require laminas/laminas-ldap
- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Run unit tests
run: composer kimai:tests-unit
env:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
APP_ENV: dev
MAILER_URL: null://localhost
- name: Run integration tests
run: composer kimai:tests-integration
env:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
APP_ENV: dev
MAILER_URL: null://localhost
- name: Run migrations on MySQL
run: |
bin/console doctrine:database:drop --if-exists --force -n
bin/console doctrine:database:create --if-not-exists -n
bin/console doctrine:migrations:migrate -n
bin/console doctrine:migrations:migrate first -n
env:
DATABASE_URL: mysql://root:kimai@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/kimai?serverVersion=5.7
APP_ENV: dev
MAILER_URL: null://localhost

View File

@@ -1,5 +1,5 @@
unreleased=true
future-release=1.2
exclude-labels=duplicate,question,invalid,wontfix,release
enhancement_labels=>enhancement,Enhancement,feature request,translation i18n,technical debt
future-release=1.19
exclude-labels=duplicate,support,question,invalid,wontfix,release,waiting for feedback,documentation
enhancement_labels=>enhancement,Enhancement,feature request,translation i18n,technical debt,documentation
issues-wo-labels=false

24
.gitignore vendored
View File

@@ -1,23 +1,37 @@
# ========== KIMAI ==========
# IDE
# some hosters require a htaccess to change the PHP version
.htaccess
.env-*
.idea/
.DS_Store
# for symfony local webserver
php.ini
/bin/*
!bin/console
/config/packages/local.yaml
/config/packages/*-local.yaml
/config/packages/*/local.yaml
/config/packages/*/*-local.yaml
/config/bundles-local.php
public/avatars/*.png
templates/invoice/renderer/.~lock*
translations/branding.*.xlf
/var/data/*
!/var/data/.gitkeep
!/var/data/kimai_test.sqlite
/var/coverage/
/var/cache/*
!var/cache/.gitkeep
/var/invoices/*
/var/export/*
/var/packages/*
!var/packages/.gitkeep
/var/plugins/*
!var/plugins/.gitkeep
@@ -34,10 +48,6 @@ templates/invoice/renderer/.~lock*
/vendor/
###< symfony/framework-bundle ###
###> symfony/web-server-bundle ###
.web-server-pid
###< symfony/web-server-bundle ###
###> phpunit/phpunit ###
/phpunit.xml
/.phpunit.result.cache
@@ -46,6 +56,7 @@ templates/invoice/renderer/.~lock*
###> friendsofphp/php-cs-fixer ###
.php_cs
.php_cs.cache
.php-cs-fixer.cache
###< friendsofphp/php-cs-fixer ###
###> symfony/phpunit-bridge ###
@@ -58,3 +69,4 @@ templates/invoice/renderer/.~lock*
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###
/nbproject/*

View File

@@ -7,7 +7,8 @@ For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
COMMENT;
return PhpCsFixer\Config::create()
$fixer = new PhpCsFixer\Config();
$fixer
->setRiskyAllowed(true)
->setRules([
'encoding' => true,
@@ -19,7 +20,7 @@ return PhpCsFixer\Config::create()
'function_declaration' => true,
'indentation_type' => true,
'line_ending' => true,
'lowercase_constants' => true,
'constant_case' => ['case' => 'lower'],
'lowercase_keywords' => true,
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
'header_comment' => ['header' => $fileHeaderComment, 'separate' => 'both'],
@@ -46,7 +47,7 @@ return PhpCsFixer\Config::create()
'statements' => ['return'],
],
'cast_spaces' => true,
'class_attributes_separation' => ['elements' => ['method']],
'class_attributes_separation' => ['elements' => ['method' => 'one']],
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => true,
'function_typehint_space' => true,
@@ -100,10 +101,10 @@ return PhpCsFixer\Config::create()
],
'phpdoc_annotation_without_dot' => true,
'phpdoc_indent' => true,
'phpdoc_inline_tag' => true,
'phpdoc_inline_tag_normalizer' => true,
'phpdoc_no_access' => true,
'phpdoc_no_alias_tag' => true,
'phpdoc_no_empty_return' => true,
'phpdoc_no_empty_return' => false,
'phpdoc_no_package' => true,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_return_self_reference' => true,
@@ -130,7 +131,7 @@ return PhpCsFixer\Config::create()
'standardize_increment' => true,
'standardize_not_equals' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline_array' => false,
'trailing_comma_in_multiline' => false,
'trim_array_spaces' => true,
'unary_operator_spaces' => true,
'whitespace_after_comma_in_array' => true,
@@ -141,6 +142,18 @@ return PhpCsFixer\Config::create()
'method',
'property',
]],
'native_function_invocation' => [
'include' => [
'@compiler_optimized'
],
'scope' => 'namespaced'
],
'native_function_type_declaration_casing' => true,
'no_alias_functions' => [
'sets' => [
'@internal'
]
],
])
->setFinder(
PhpCsFixer\Finder::create()
@@ -151,3 +164,5 @@ return PhpCsFixer\Config::create()
)
->setFormat('checkstyle')
;
return $fixer;

View File

@@ -1,78 +0,0 @@
dist: xenial
language: php
cache:
directories:
- $HOME/.composer/cache/files
matrix:
include:
- stage: Code quality
env: CODECOVERAGE=0 DB=sqlite
php: '7.2'
script:
- composer validate --no-check-all --strict
- composer kimai:codestyle
- composer kimai:phpstan
- stage: Test
php: '7.2'
env: CODECOVERAGE=0 DB=mysql
sudo: required
services:
- mysql
- stage: Test
php: '7.3'
env: CODECOVERAGE=0 DB=mysql
sudo: required
services:
- mysql
- stage: Test
php: '7.2'
env: CODECOVERAGE=0 DB=sqlite
- stage: Test
#php: '7.4snapshot'
php: '7.3'
env: CODECOVERAGE=0 DB=sqlite
- stage: Test
php: '7.2'
env: CODECOVERAGE=0 DB=mariadb
addons:
mariadb: '10.2'
services:
- mysql
before_script:
- mysql -u root -e 'CREATE USER IF NOT EXISTS travis@localhost; GRANT ALL ON *.* TO travis@localhost;'
- stage: Test
php: '7.3'
env: CODECOVERAGE=0 DB=mariadb
addons:
mariadb: '10.2'
services:
- mysql
before_script:
- mysql -u root -e 'CREATE USER IF NOT EXISTS travis@localhost; GRANT ALL ON *.* TO travis@localhost;'
- stage: Code coverage
php: '7.3'
env: CODECOVERAGE=1 DB=sqlite
after_success:
- if [[ $CODECOVERAGE == 1 ]]; then bash <(curl -s https://codecov.io/bash); fi
before_install:
- if [[ $CODECOVERAGE == 0 ]]; then phpenv config-rm xdebug.ini; fi;
- composer self-update
- php -i
- php -m
install:
- composer install
- composer require zendframework/zend-ldap
script:
- if [[ $CODECOVERAGE == 1 ]]; then vendor/bin/phpunit tests/ --coverage-clover=coverage.xml; fi;
- if [[ $CODECOVERAGE == 0 ]]; then vendor/bin/phpunit tests/; fi;
- if [[ $DB == 'sqlite' ]]; then cp tests/.env.dist.sqlite .env; fi;
- if [[ $DB == "mysql" || $DB == "mariadb" ]]; then cp tests/.env.dist.mysql .env; fi;
- bin/console doctrine:database:create -n
- bin/console doctrine:migrations:migrate -n
- bin/console doctrine:migrations:migrate first -n

View File

@@ -1,75 +1,3 @@
# Change Log
# Changelog
## [1.2](https://github.com/kevinpapst/kimai2/tree/1.2) (2019-08-28)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/1.1...1.2)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/1.2)
## [1.1](https://github.com/kevinpapst/kimai2/tree/1.1) (2019-07-24)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/1.0.1...1.1)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/1.1)
## [1.0.1](https://github.com/kevinpapst/kimai2/tree/1.0.1) (2019-07-05)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/1.0...1.0.1)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/1.0.1)
## [1.0](https://github.com/kevinpapst/kimai2/tree/1.0) (2019-07-02)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.9...1.0)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/1.0)
## [0.9](https://github.com/kevinpapst/kimai2/tree/0.9) (2019-04-29)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.8.1...0.9)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.9)
## [0.8.1](https://github.com/kevinpapst/kimai2/tree/0.8.1) (2019-02-22)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.8...0.8.1)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.8.1)
## [0.8](https://github.com/kevinpapst/kimai2/tree/0.8) (2019-02-19)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.7...0.8)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.8)
## [0.7](https://github.com/kevinpapst/kimai2/tree/0.7) (2019-01-28)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.6.1...0.7)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.7)
## [0.6.1](https://github.com/kevinpapst/kimai2/tree/0.6.1) (2018-11-19)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.6...0.6.1)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.6.1)
## [0.6](https://github.com/kevinpapst/kimai2/tree/0.6) (2018-11-18)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.5...0.6)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.6)
## [0.5](https://github.com/kevinpapst/kimai2/tree/0.5) (2018-09-27)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.4...0.5)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.5)
## [0.4](https://github.com/kevinpapst/kimai2/tree/0.4) (2018-09-01)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.3...0.4)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.4)
## [0.3](https://github.com/kevinpapst/kimai2/tree/0.3) (2018-07-22)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.2...0.3)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.3)
## [0.2](https://github.com/kevinpapst/kimai2/tree/0.2) (2018-06-23)
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.1...0.2)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.2)
## [0.1](https://github.com/kevinpapst/kimai2/tree/0.1) (2018-01-10)
Release notes including the changelog can be found [here](https://github.com/kevinpapst/kimai2/releases/tag/0.1)
See [GitHub release page](https://github.com/kevinpapst/kimai2/releases).

View File

@@ -1,54 +0,0 @@
# This file is part of the Kimai time-tracking app.
#
# For the full copyright and license information, please view the LICENSE
# file that was distributed with this source code.
FROM php:7.2.9-apache-stretch AS tmp_kimai2_base
RUN apt update && \
apt install -y --allow-unauthenticated \
git \
haveged \
libicu-dev \
libjpeg-dev \
libldap2-dev \
libldb-dev \
libpng-dev \
mysql-client \
unzip \
wget \
zip \
&& \
EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)" && \
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" && \
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]; then >&2 echo 'ERROR: Invalid installer signature'; rm composer-setup.php; exit 1; fi && \
php composer-setup.php --quiet && \
rm composer-setup.php && \
mv /var/www/html/composer.phar /usr/bin/composer && \
docker-php-ext-install \
gd \
intl \
ldap \
pdo_mysql \
zip && \
apt remove -y wget && \
apt -y autoremove && \
apt clean
FROM tmp_kimai2_base
RUN git clone https://github.com/kevinpapst/kimai2.git /opt/kimai && \
sed "s/prod/dev/g" /opt/kimai/.env.dist > /opt/kimai/.env && \
composer install --working-dir=/opt/kimai --dev --optimize-autoloader && \
/opt/kimai/bin/console doctrine:database:create && \
/opt/kimai/bin/console doctrine:schema:create && \
/opt/kimai/bin/console doctrine:migrations:version --add --all && \
/opt/kimai/bin/console cache:warmup && \
chown -R www-data:www-data /opt/kimai/var && \
chown www-data:www-data /opt/kimai/vendor/mpdf/mpdf/tmp
WORKDIR /opt/kimai
EXPOSE 8001
USER www-data
CMD /opt/kimai/bin/console server:run 0.0.0.0:8001

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017-2019 Kevin Papst @ https://www.kevinpapst.de
Copyright (c) 2017-2022 Kevin Papst @ https://www.kevinpapst.de
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,39 +1,45 @@
# Kimai 2 - online time-tracker
<p align="center">
<img src="https://raw.githubusercontent.com/kimai/images/master/repository-header.png" alt="Kimai logo">
</p>
[![Latest Stable Version](https://poser.pugx.org/kevinpapst/kimai2/v/stable)](https://packagist.org/packages/kevinpapst/kimai2)
[![License](https://poser.pugx.org/kevinpapst/kimai2/license)](https://packagist.org/packages/kevinpapst/kimai2)
[![Travis Status](https://travis-ci.org/kevinpapst/kimai2.svg?branch=master)](https://travis-ci.org/kevinpapst/kimai2)
[![Code Coverage](https://codecov.io/gh/kevinpapst/kimai2/branch/master/graph/badge.svg)](https://codecov.io/gh/kevinpapst/kimai2)
[![Gitter](https://badges.gitter.im/kimai2/support.svg)](https://gitter.im/kimai2/support)
[![Bountysource](https://img.shields.io/bountysource/team/kimai2/activity)](https://www.bountysource.com/teams/kimai2)
<p align="center">
<a href="https://github.com/kevinpapst/kimai2/actions"><img alt="CI Status" src="https://github.com/kevinpapst/kimai2/workflows/CI/badge.svg"></a>
<a href="https://codecov.io/gh/kevinpapst/kimai2"><img alt="Code Coverage" src="https://codecov.io/gh/kevinpapst/kimai2/branch/master/graph/badge.svg"></a>
<a href="https://packagist.org/packages/kevinpapst/kimai2"><img alt="Latest stable version" src="https://poser.pugx.org/kevinpapst/kimai2/v/stable"></a>
<a href="https://packagist.org/packages/kevinpapst/kimai2"><img alt="License" src="https://poser.pugx.org/kevinpapst/kimai2/license"></a>
<a href="https://twitter.com/kimai_org"><img alt="Twitter" src="https://img.shields.io/badge/follow-%40kimai__org-00acee"></a>
</p>
<h1 align="center">Kimai - time-tracker</h1>
Kimai is a free, open source and online time-tracking software designed for small businesses and freelancers.
It is built with modern technologies such as Symfony, Bootstrap, RESTful API, Doctrine, AdminLTE, Webpack, ES6 etc.
It is built with modern technologies such as [Symfony](https://github.com/symfony/symfony), [Bootstrap](https://github.com/twbs/bootstrap),
[RESTful API](https://github.com/FriendsOfSymfony/FOSRestBundle), [Doctrine](https://github.com/doctrine/),
[AdminLTE](https://github.com/kevinpapst/AdminLTEBundle/), [Webpack](https://github.com/webpack/webpack), ES6 and [many](composer.json) [more](package.json).
## Introduction
- [Home](https://www.kimai.org) - The house of Kimai
- [Blog](https://www.kimai.org/blog/) - Get the latest news
- [Documentation](https://www.kimai.org/documentation/) - Learn how to use
- [Translations](https://www.kimai.org/documentation/translations.html) - Kimai in your language
- [Migration](https://www.kimai.org/documentation/migration-v1.html) - Import data from v1
- [Home](https://www.kimai.org) - Kimai project homepage
- [Blog](https://www.kimai.org/blog/) - Read the latest news
- [Documentation](https://www.kimai.org/documentation/) - Learn how to use Kimai
- [Translations](https://hosted.weblate.org/projects/kimai/#languages) - Kimai in your language
- [Migration](https://www.kimai.org/documentation/migration-v1.html) - Import data from Kimai 1
### Requirements
- PHP 7.2 or higher
- Database (MySQL, MariaDB, SQLite)
- Webserver (nginx, Apache)
- A modern browser
- [Other libraries](https://www.kimai.org/download/)
- PHP 7.4 or higher (PHP 8 supported, PHP 8.1 does not support LDAP yet)
- MariaDB or MySQL
- A webserver and subdomain
- PHP extensions: `gd`, `intl`, `json`, `mbstring`, `pdo`, `xsl`, `zip`
### About
This is new version of the open source timetracker Kimai. It is stable and production ready, ships
with most advanced features from Kimai 1 and many new ones, including but not limited to:
This is the new version of the open source time tracker Kimai. It is stable and production ready, ships
with many advanced features, including but not limited to:
JSON API, invoicing, data exports, multi-timer and punch-in punch-out mode, tagging, multi-user and multi-timezones,
LDAP and built-in authentication, customizable role permissions, responsive and ready for your mobile device,
hourly and fixed rates, advanced filtering, money and time budgets with report, support for plugins and many more.
authentication via SAML/LDAP/Database, customizable role and team permissions, responsive and ready for your mobile device,
user specific rates, advanced search & filtering, money and time budgets, reporting, support for plugins and many more.
## Installation
@@ -41,11 +47,10 @@ hourly and fixed rates, advanced filtering, money and time budgets with report,
- [Docker](https://www.kimai.org/documentation/docker.html) - containerized
- [Development](https://www.kimai.org/documentation/installation.html#development-installation) - on your local machine
- [1-click installer](https://www.kimai.org/documentation/installation.html#hosting-and-1-click-installations) - hosted environments
- [FTP](https://www.kimai.org/documentation/installation.html#ftp-installation) - unfortunately still widely used ;-)
### Updating Kimai
- [Update Kimai](https://www.kimai.org/documentation/updates.html) - the documentation
- [Update Kimai](https://www.kimai.org/documentation/updates.html) - get the latest version
- [UPGRADING guide](UPGRADING.md) - version specific steps
### Plugins
@@ -60,15 +65,24 @@ It is open for changes and input from the community, your [ideas and questions](
> Kimai 2 uses a rolling release concept for delivering updates.
> You can upgrade Kimai at any time, you don't need to wait for the next official release.
> The master branch is always deployable, release tags are only snapshots of the current development version.
Release versions will be created on a regular base (approx. every second month) and you can use these tags if you are familiar with git.
Every code change, whether it's a new feature or a bug fix, will be done on the master branch.
I have to do it this way, as I develop Kimai in my free time and want to put my effort into the software instead of backporting changes for old versions.
Release versions will be created on a regular base (approx. one release every 2 months).
Every code change, whether it's a new feature or a bugfix, will be done on the master branch.
Kimai is actively developed in my spare time, I put my effort into the software instead of backporting changes.
## Credits
## Contributing
Kimai 2 is developed with modern frameworks like
[Symfony v4](https://github.com/symfony/symfony),
[Doctrine](https://github.com/doctrine/),
[AdminLTEBundle](https://github.com/kevinpapst/AdminLTEBundle/) (based on [AdminLTE theme](https://github.com/almasaeed2010/AdminLTE)) and
[many](composer.json) [more](package.json).
You want to contribute to this repository? This is so great!
The best way to start is to [open a new issue](https://github.com/kevinpapst/kimai2/issues) for bugs or feature requests or a [discussion](https://github.com/kevinpapst/kimai2/discussions) for questions, support and such.
In case you want to contribute, but you wouldn't know how, here are some suggestions:
- Spread the word: More user means more people testing and contributing to Kimai - which in turn means better stability and more and better features. Please vote for Kimai on platforms lie Slant, Product Hunt, Softpedia or AlternativeTo, you can tweet about it, share it on LinkedIn, reddit or any of your favorite social media platforms. Every little bit helps!
- Answer questions: You know the answer to another user's problem? Share your knowledge!
- Make a feature request: Something can be done better? Something essential missing? Let us know!
- Report bugs
- Contribute: You don't have to be programmer to help. The documentation and translation could use some love as well.
- Sponsor the project
There is one simple rule in our "Code of conduct": Don't be an ass!

View File

@@ -5,19 +5,20 @@
As announced in the [README](README.md) I only support the latest available release and master.
| Version | Supported |
| ------- | ------------------ |
|---------|--------------------|
| master | :white_check_mark: |
| 1.2 | :white_check_mark: |
| < 1.2 | :x: |
| 1.19 | :white_check_mark: |
| < 1.19 | :x: |
## Reporting a Vulnerability
Please report any security related vulnerability in the [advisories section at GitHub](https://github.com/kevinpapst/kimai2/security/advisories) or via email to info@keleo.de or kpapst@gmx.net.
Please read the [Bughunter](https://www.kimai.org/documentation/bughunter.html) documentation before posting,
and then you can report any security related vulnerability in the [advisories section at GitHub](https://github.com/kevinpapst/kimai2/security/advisories) or via email to kpapst@gmx.net.
I will work as fast as I can to fix the problem and publish a bugfix release / security update.
Depending on the size of the required fixes, this might take a couple of hours or a couple of days.
You can expect that your message will be answered ASAP, but please take into account that I am living in the timezone Europe/Berlin.
You can expect that your message will be answered ASAP.
If your issue is valid and I verified and fixed it, you will be mentioned in the release notes within the Security section at the top of the release.
I am grateful for any (discrete) disclosure of vulnerabilities!
You will be mentioned in the release notes if your issue is valid and after it was fixed.
I am grateful for any (discrete) disclosure of vulnerabilities!

View File

@@ -8,28 +8,219 @@ you can upgrade your Kimai installation to the latest stable release.
Check below if there are more version specific steps required, which need to be executed after the normal update process.
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
## [1.16](https://github.com/kevinpapst/kimai2/releases/tag/1.16)
**DEVELOPER**
- Removed `formDateTime` field from API model `I18nConfig`
## [1.15](https://github.com/kevinpapst/kimai2/releases/tag/1.15)
**Many database changes: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
Updating the database might take quite a while, depending on the amount of timesheet entries and speed of your database server (~1 minute per 100k records).
**ATTENTION**
- This release bumps the minimum required [PHP version to 7.3](https://www.kimai.org/blog/2021/php8-support-php72-dropped/)
- Self-registration is disabled by default
- Self-registration now always requires email confirmation
- All plugins that use own databases need to be updated as well
- Removed the YearChart widget and the related configs named `userRecapThisYear`, `userRecapLastYear`, `userRecapTwoYears`, `userRecapThreeYears`
**LDAP & SAML**
Please verify your config with the [LDAP](https://www.kimai.org/documentation/ldap.html) and [SAML](https://www.kimai.org/documentation/saml.html) documentation, especially:
- SAML users: activate it by setting the `kimai.saml.activate: true` config key
- LDAP users: activate it by setting the `kimai.ldap.activate: true` config key
- LDAP and SAML users need to remove the complete `security` section from their `local.yaml`
**DEVELOPER**
PHP 8 compatibility forced to upgrade MANY libraries, including but not limited to:
- Removed FOSUserBundle and hslavich/oneloginsaml
- Doctrine Migrations, whose new major version forces plugin updates
- Gedmo v3 (which include BC breaks in definitions)
- Doctrine DBAL and others, which required PHP 7.3 as well
**API BC break**: Due to team structure changes, it was impossible to keep the (writing) API structure. Please adjust your code accordingly!
## [1.14](https://github.com/kevinpapst/kimai2/releases/tag/1.14)
**CRITICAL BC break**: SQLite support was removed. If you are using SQLite, you have to [read this blog post](https://www.kimai.org/blog/2021/sqlite-and-ftp-support-removed/) and migrate to MySQL/MariaDB first!
**New database tables and fields: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
Permission changes:
- `history_invoice` - removed permission entirely
## [1.13](https://github.com/kevinpapst/kimai2/releases/tag/1.13)
- Deprecated `now` variable in export templates: create it yourself with `{% set now = create_date('now', app.user) %}`
- Changed invoice filename generation (check if you use cronjob for invoices)
- **BC break**: duration entered as plain numbers will now be treated as decimal duration in hours instead of seconds
## [1.12](https://github.com/kevinpapst/kimai2/releases/tag/1.12)
- Export templates can now include items from plugins (eg. Expenses).
## [1.10](https://github.com/kevinpapst/kimai2/releases/tag/1.10)
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
- Invoice renderer `CSV` was removed
- Sessions are now stored in the database (all users have to re-login after upgrade)
- New permissions: `lockdown_grace_timesheet`, `lockdown_override_timesheet`, `view_all_data`
- Fixed team permissions on user queries: depending on your previous team & permission setup your users might see less data (SUPER_ADMINS see all data, but new: ADMINS only see all data if they own the `view_all_data` permission)
- Markdown does not support headings anymore, text like `# foo` is not converted to `<h1 id="foo">foo</h1>` anymore
### Developer
- **BC break**: removed registration of `.env` with `putenv()` - do not rely on `getenv()` as it is not thread-safe
- **BC break**: interface method signature `HtmlToPdfConverter::convertToPdf()` changed
- **BC break**: the macros `badge` and `label` do not apply the `|trans` filter any more
- **BC Break**: removed `getVisible()` (deprecated since 1.4) method on Customer, Project and Activity (use `isVisible()` instead, templates are still working)
- **BC Break**: API changes
- some representation names changed (eg. from `ActivityMetaField` to `ActivityMeta`, `TimesheetSubCollection` vs `TimesheetCollectionExpanded`), you could use `class_alias()` if you use auto-generated code from Swagger-Gen or alike
- new result types were introduced
- result data changed in some areas to smooth out inconsistencies (eg. TeamEntity fields changed in nested results)
## [1.9](https://github.com/kevinpapst/kimai2/releases/tag/1.9)
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
- The directory `var/data/invoices/` will be used to store archived invoice files (check file permissions)
- The default invoice number format changed. If you want to use the old one: configure `{date}` as format - see [invoice documentation](https://www.kimai.org/documentation/invoices.html)
- HTML invoice templates are now treated like other files and offered as download. If you are using relative URLs for including assets (CSS, images) you need to either inline them (see the default templates) or use absolute URLs
- Invoice templates that use the templates variables `${activity.X}` or `${project.X}` should be checked and possibly adapted, as multi-select is now possible for filtering
- Invoice templates have access to all meta-fields as variables, not only the ones marked as visible
- Rates configuration/structure changed for customer, project and activity
- **Invoice templates**: rates variables were removed
Permission changes:
- `history_invoice` - NEW: grants all features for the new invoice archive (by default for all admins)
### Developer
- **BC break**: `InvoiceItemInterface` has new methods `getType()` and `getCategory()`
- **BC break**: API fields changed - see new `/rates` endpoints
## [1.8](https://github.com/kevinpapst/kimai2/releases/tag/1.8)
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
- New PHP requirement: `ext-xsl` - which should be pre-installed in most environments when `ext-xml` is loaded
- New mailer library: check if emails are still working (eg. by using the "password forgotten" function) or if you need to adjust your configuration, [see docs at symfony.com](https://symfony.com/doc/current/components/mailer.html#transport)
- Support for line breaks in multiline invoice fields for spreadsheets (check your invoice templates after the update)
Permission changes:
- `comments_create_customer` - NEW: permission that allows to add new comments for customers
- `comments_create_team_customer` - NEW: permission that allows to add new comments for team members of the current customer
- `comments_create_teamlead_customer` - NEW: permission that allows to add new comments for a teamlead of the current customer
- `comments_create_project` - NEW: permission that allows to add new comments for project
- `comments_create_team_project` - NEW: permission that allows to add new comments for team members of the current project
- `comments_create_teamlead_project` - NEW: permission that allows to add new comments for a teamlead of the current project
- `edit_teamlead_project` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
- `edit_teamlead_customer` - removed default permission from ROLE_TEAMLEAD (if you use it: change it in the Role & Permission UI)
- `upload_invoice_template` - NEW: permission that allows to upload invoice documents from the UI
## [1.7](https://github.com/kevinpapst/kimai2/releases/tag/1.7)
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
New permissions:
- `comments_customer` - show comment list on customer detail page (new feature)
- `details_customer` - show detail information for customers (customer number, vat, rates, meta-fields, assigned teams ...)
- `comments_project` - show comment list on project detail page (new feature)
- `details_project` - show detail information for projects (rates, meta-fields, assigned teams ...)
If you are using teams, please read on: The following list of permissions are now also available in the UI and they (can) replace the `X_project` and `X_customer` permissions.
They are more strict, as they allow only access to team specific items, the older permissions without `_teamlead_`/`_team_` work on a global level instead.
- `view_teamlead_customer`, `edit_teamlead_customer`, `budget_teamlead_customer`, `permissions_teamlead_customer`, `comments_teamlead_customer`, `details_teamlead_customer` - allows access to customer data when user is teamlead of a team assigned to the customer (replaces more global permission like `view_customer` for teamleads)
- `view_team_customer`, `edit_team_customer`, `budget_team_customer`, `comments_team_customer`, `details_team_customer` - allows access to customer data when user is member of a team assigned to the customer (replaces more global permission like `view_customer` for users)
- `view_teamlead_project`, `edit_teamlead_project`, `budget_teamlead_project`, `permissions_teamlead_project`, `comments_teamlead_project`, `details_teamlead_project` - allows access to customer data when user is teamlead of a team assigned to the project (replaces more global permission like `view_project` for teamleads)
- `view_team_project`, `edit_team_project`, `budget_team_project`, `comments_team_project`, `details_team_project` - allows access to customer data when user is member of a team assigned to the project (replaces more global permission like `view_project` for users)
### ExpenseBundle
**ATTENTION** due to incompatibilities in the underlying frameworks users of the ExpenseBundle need to do one more step:
You need to delete the bundle before updating: `rm -r var/plugins/ExpenseBundle`, otherwise you will run into errors during the update.
After the Kimai update was successful, you have to re-install the latest bundle version, which is compatible with Kimai 1.7 only.
### Developer
- Projects now have a start and end date and the API will only return those, which are either unconfigured or currently active, you might want to reload the list of projects once the user entered begin and end datetime OR use the new `ignoreDates` parameter.
- Doctrine bundle was updated to v2, check your code for [the usage of RegistryInterface and ObjectManager](https://github.com/doctrine/DoctrineBundle/blob/master/UPGRADE-2.0.md)
- Removed the webserver bundle and the command `server:run` - see [docs](https://www.kimai.org/documentation/developers.html)
## [1.6](https://github.com/kevinpapst/kimai2/releases/tag/1.6), [1.6.1](https://github.com/kevinpapst/kimai2/releases/tag/1.6.1), [1.6.2](https://github.com/kevinpapst/kimai2/releases/tag/1.6.2)
**New database tables and fields were created, don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**
- Invoice changes:
- Moved CSV, ODS and XSLX invoice templates to [another repository](https://github.com/Keleo/kimai2-invoice-templates). Using them? Install them manually (see [invoice documentation](https://www.kimai.org/documentation/invoices.html)).
- Added new invoice fields (VAT, contact, payment details) and customer field (VAT). Used the twig settings before? Move them to the respective invoice template settings.
- Permissions can be managed via Admin UI. Please move your permission settings from [local.yaml to your database](https://www.kimai.org/documentation/permissions.html).
- Important permission change: regular users with the `view_other_timesheet` permission could see all timesheets. This was a legacy from the time before team permissions were introduced. If you rely on this behavior, you need to create a team with all users and the teamlead being the user who needs access to all timesheets.
### Developer
Please add default permissions to your [plugin](https://www.kimai.org/documentation/plugins.html).
## [1.5](https://github.com/kevinpapst/kimai2/releases/tag/1.5)
[Update as usual](https://www.kimai.org/documentation/updates.html)
## [1.4](https://github.com/kevinpapst/kimai2/releases/tag/1.4)
**There is a new directory, which needs to be writable by the webserver: `public/avatars/`.**
New permission (used in new dashboard widget):
- `view_team_member` - display team assignments (names, teamleads and members) for the current user
Activated Javascript select component by default (check mobile devices).
### Developer: BC breaks
- Dashboard widgets and rows need to define their `type` by FQCN
- Switched to Symfony 4.3 event types, this could fail in plugins, but only if they didn't use the official constants for event names
## [1.3](https://github.com/kevinpapst/kimai2/releases/tag/1.3)
Added `manage_tag` permission for new tag features
### Developer: BC breaks
- Refactored toolbars and search, plugins needs to be checked
- Invoices now supports multiple repositories, some method signatures had to be changed (eg. `calculateSumIdentifier()`)
## [1.2](https://github.com/kevinpapst/kimai2/releases/tag/1.2)
### Possible BC breaks
**If you are still using 0.7 or below, you need to upgrade to 1.1 before upgrading to this version.**
- Deleted timezone conversion command. If you are still using 0.7 or below, you need to upgrade to 1.1 before upgrading to this version.
- Deleted timezone conversion command.
- Minimum password length raised from 5 to 8 character (applies only for password changes and new users)
- Maximum customer name length lowered to 150 character
- Maximum project name length lowered to 150 character
- Maximum activity name length lowered to 150 character
- Added new permission: `manage_invoice_template`
- Removed permissions: `view_invoice_template`, `create_invoice_template`, `edit_invoice_template`, `delete_invoice_template`
- Removed permission: `view_invoice` (using `create_export` only)
- Removed permission: `view_export` (using `create_export` only)
### Developer: BC breaks
- Custom export renderer need to check for usage of `Timesheet::getEnd()` as running entries can now be exported as well
## [1.1](https://github.com/kevinpapst/kimai2/releases/tag/1.1)
[Update as usual](https://www.kimai.org/documentation/updates.html), nothing special for this release if you upgrade from 1.0 / 1.0.1.
## [1.0.1](https://github.com/kevinpapst/kimai2/releases/tag/1.0.1)
[Update as usual](https://www.kimai.org/documentation/updates.html), nothing special for this release if you upgrade from 1.0.
## [1.0](https://github.com/kevinpapst/kimai2/releases/tag/1.0)
This release contains several changes, as I still have the goal to stabilize the code base to prevent

View File

@@ -11,27 +11,75 @@ global.$ = global.jQuery = $;
require('bootstrap-sass');
require('jquery-slimscroll');
require('bootstrap-select');
require('select2');
require('select2/dist/js/i18n/ar');
require('select2/dist/js/i18n/cs');
require('select2/dist/js/i18n/da');
require('select2/dist/js/i18n/de');
require('select2/dist/js/i18n/el');
require('select2/dist/js/i18n/es');
require('select2/dist/js/i18n/eu');
require('select2/dist/js/i18n/fa');
require('select2/dist/js/i18n/fi');
require('select2/dist/js/i18n/fr');
require('select2/dist/js/i18n/he');
require('select2/dist/js/i18n/hu');
require('select2/dist/js/i18n/it');
require('select2/dist/js/i18n/ja');
require('select2/dist/js/i18n/ko');
require('select2/dist/js/i18n/nb');
require('select2/dist/js/i18n/nl');
require('select2/dist/js/i18n/pl');
require('select2/dist/js/i18n/pt');
require('select2/dist/js/i18n/pt-BR');
require('select2/dist/js/i18n/ro');
require('select2/dist/js/i18n/ru');
require('select2/dist/js/i18n/sk');
require('select2/dist/js/i18n/sv');
require('select2/dist/js/i18n/tr');
require('select2/dist/js/i18n/vi');
require('select2/dist/js/i18n/zh-CN');
const Moment = require('moment');
global.moment = Moment;
require('moment/locale/ar');
require('moment/locale/cs');
require('moment/locale/da');
require('moment/locale/de');
require('moment/locale/de-at');
require('moment/locale/de-ch');
require('moment/locale/el');
require('moment/locale/eo');
require('moment/locale/es');
require('moment/locale/eu');
require('moment/locale/fa');
require('moment/locale/fi');
require('moment/locale/fo');
require('moment/locale/fr');
require('moment/locale/he');
require('moment/locale/hu');
require('moment/locale/it');
require('moment/locale/ja');
require('moment/locale/ko');
require('moment/locale/nb');
require('moment/locale/nl');
require('moment/locale/pl');
require('moment/locale/pt');
require('moment/locale/pt-br');
require('moment/locale/ro');
require('moment/locale/ru');
require('moment/locale/sk');
require('moment/locale/sv');
require('moment/locale/tr');
require('moment/locale/vi');
require('moment/locale/zh-cn');
require('daterangepicker');
// ------ AdminLTE framework ------
require('../vendor/kevinpapst/adminlte-bundle/Resources/assets/admin-lte.scss');
require('./sass/bootstrap.scss');
require('./sass/fontawesome.scss');
require('admin-lte/dist/css/AdminLTE.min.css');
require('admin-lte/dist/css/skins/_all-skins.css');
require('../vendor/kevinpapst/adminlte-bundle/Resources/assets/admin-lte-extensions.scss');
@@ -46,6 +94,10 @@ require('./sass/app.scss');
// ------ Kimai itself ------
require('./js/KimaiWebLoader.js');
global.KimaiPaginatedBoxWidget = require('./js/widgets/KimaiPaginatedBoxWidget').default;
global.KimaiReloadPageWidget = require('./js/widgets/KimaiReloadPageWidget').default;
global.KimaiCookies = require('./js/widgets/KimaiCookies').default;
global.KimaiStorage = require('./js/widgets/KimaiStorage').default;
// ------ Autocomplete for tags only ------
require('jquery-ui/ui/widgets/autocomplete');
require('jquery-ui/ui/widgets/autocomplete');

View File

@@ -1,17 +1,39 @@
// can be removed, once fullcalendar was updated and works without draggable jquery ui objects
require('jquery-ui/ui/widgets/draggable');
require('fullcalendar');
require('fullcalendar/dist/gcal.min');
require('fullcalendar/dist/locale/de');
require('fullcalendar/dist/locale/it');
require('fullcalendar/dist/locale/fr');
require('fullcalendar/dist/locale/es');
require('fullcalendar/dist/locale/ru');
require('fullcalendar/dist/locale/ar');
require('fullcalendar/dist/locale/cs');
require('fullcalendar/dist/locale/da');
require('fullcalendar/dist/locale/de');
require('fullcalendar/dist/locale/de-at');
require('fullcalendar/dist/locale/de-ch');
require('fullcalendar/dist/locale/el');
require('fullcalendar/dist/locale/es');
require('fullcalendar/dist/locale/eu');
require('fullcalendar/dist/locale/fa');
require('fullcalendar/dist/locale/fi');
require('fullcalendar/dist/locale/fr');
require('fullcalendar/dist/locale/he');
require('fullcalendar/dist/locale/hu');
require('fullcalendar/dist/locale/pt-br');
require('fullcalendar/dist/locale/sv');
require('fullcalendar/dist/locale/it');
require('fullcalendar/dist/locale/ja');
require('fullcalendar/dist/locale/ko');
require('fullcalendar/dist/locale/nb');
require('fullcalendar/dist/locale/nl');
require('fullcalendar/dist/locale/pl');
require('fullcalendar/dist/locale/pt');
require('fullcalendar/dist/locale/pt-br');
require('fullcalendar/dist/locale/ro');
require('fullcalendar/dist/locale/ru');
require('fullcalendar/dist/locale/sk');
require('fullcalendar/dist/locale/sv');
require('fullcalendar/dist/locale/tr');
require('fullcalendar/dist/locale/zh-cn');
require('fullcalendar/dist/locale/vi');
require('fullcalendar/dist/locale/en-gb'); // the last imported file is used as fallback for locales that do not exist (like EO)
require('fullcalendar/dist/fullcalendar.min.css');

1348
assets/css/emails.css Normal file

File diff suppressed because it is too large Load Diff

2
assets/invoice-pdf.js Normal file
View File

@@ -0,0 +1,2 @@
require('./sass/invoice-pdf.scss');

2
assets/invoice.js Normal file
View File

@@ -0,0 +1,2 @@
require('./sass/invoice.scss');

View File

@@ -16,7 +16,6 @@ import KimaiContainer from "./KimaiContainer";
import KimaiActiveRecordsDuration from './plugins/KimaiActiveRecordsDuration.js';
import KimaiDatatableColumnView from './plugins/KimaiDatatableColumnView.js';
import KimaiThemeInitializer from "./plugins/KimaiThemeInitializer";
import KimaiJqueryPluginInitializer from "./plugins/KimaiJqueryPluginInitializer";
import KimaiDateRangePicker from "./plugins/KimaiDateRangePicker";
import KimaiDatatable from "./plugins/KimaiDatatable";
import KimaiToolbar from "./plugins/KimaiToolbar";
@@ -31,30 +30,39 @@ import KimaiEvent from "./plugins/KimaiEvent";
import KimaiAPILink from "./plugins/KimaiAPILink";
import KimaiAlert from "./plugins/KimaiAlert";
import KimaiAutocomplete from "./plugins/KimaiAutocomplete";
import KimaiToolbarAction from "./plugins/KimaiToolbarAction";
import KimaiFormSelect from "./plugins/KimaiFormSelect";
import KimaiForm from "./plugins/KimaiForm";
import KimaiDatePicker from "./plugins/KimaiDatePicker";
import KimaiConfirmationLink from "./plugins/KimaiConfirmationLink";
import KimaiMultiUpdateTable from "./plugins/KimaiMultiUpdateTable";
import KimaiDateUtils from "./plugins/KimaiDateUtils";
import KimaiEscape from "./plugins/KimaiEscape";
export default class KimaiLoader {
constructor(configurations, translations) {
// set the current locale for all javascript components
moment.locale(configurations['locale']);
moment.locale(configurations['locale'].replace('_', '-').toLowerCase());
const kimai = new KimaiContainer(
new KimaiConfiguration(configurations),
new KimaiTranslation(translations)
);
kimai.registerPlugin(new KimaiEscape());
kimai.registerPlugin(new KimaiEvent());
kimai.registerPlugin(new KimaiAPI());
kimai.registerPlugin(new KimaiAlert());
kimai.registerPlugin(new KimaiActiveRecordsDuration('[data-since]'));
kimai.registerPlugin(new KimaiDateUtils());
kimai.registerPlugin(new KimaiFormSelect('.selectpicker'));
kimai.registerPlugin(new KimaiConfirmationLink('confirmation-link'));
kimai.registerPlugin(new KimaiActiveRecordsDuration());
kimai.registerPlugin(new KimaiDatatableColumnView('data-column-visibility'));
kimai.registerPlugin(new KimaiThemeInitializer());
kimai.registerPlugin(new KimaiJqueryPluginInitializer());
kimai.registerPlugin(new KimaiDateRangePicker('.content-wrapper'));
kimai.registerPlugin(new KimaiDateTimePicker('.content-wrapper'));
kimai.registerPlugin(new KimaiDatatable('table.dataTable'));
kimai.registerPlugin(new KimaiToolbar());
kimai.registerPlugin(new KimaiDateRangePicker('input[data-daterangepickerenable="on"]'));
kimai.registerPlugin(new KimaiDateTimePicker('input[data-datetimepicker="on"]'));
kimai.registerPlugin(new KimaiDatePicker('input[data-datepickerenable="on"]'));
kimai.registerPlugin(new KimaiDatatable('section.content', 'table.dataTable'));
kimai.registerPlugin(new KimaiToolbar('form.searchform', 'toolbar-action'));
kimai.registerPlugin(new KimaiSelectDataAPI('select[data-related-select]'));
kimai.registerPlugin(new KimaiAlternativeLinks('.alternative-link'));
kimai.registerPlugin(new KimaiAjaxModalForm('.modal-ajax-form'));
@@ -62,7 +70,9 @@ export default class KimaiLoader {
kimai.registerPlugin(new KimaiActiveRecords('li.messages-menu', 'li.messages-menu-empty'));
kimai.registerPlugin(new KimaiAPILink('api-link'));
kimai.registerPlugin(new KimaiAutocomplete('.js-autocomplete'));
kimai.registerPlugin(new KimaiToolbarAction('toolbar-action'));
kimai.registerPlugin(new KimaiForm());
kimai.registerPlugin(new KimaiThemeInitializer());
kimai.registerPlugin(new KimaiMultiUpdateTable());
//kimai.registerPlugin(new KimaiPauseRecord('li.messages-menu ul.menu li'));
// notify all listeners that Kimai plugins can now be registered

View File

@@ -42,7 +42,7 @@ export default class KimaiPlugin {
}
/**
* This function returns null, if xou call it BEFORE init().
* This function returns null, if you call it BEFORE init().
*
* @returns {KimaiContainer}
*/
@@ -50,4 +50,27 @@ export default class KimaiPlugin {
return this._core;
}
/**
* @param {string} name
* @returns {(string|number|boolean)}
*/
getConfiguration(name) {
return this.getContainer().getConfiguration().get(name);
}
/**
* @param {string} name
* @returns {KimaiPlugin}
*/
getPlugin(name) {
return this.getContainer().getPlugin(name);
}
/**
* @param {string} title
* @returns {string}
*/
escape(title) {
return this.getPlugin('escape').escapeForHtml(title);
};
}

View File

@@ -11,14 +11,25 @@
export default class KimaiTranslation {
/**
* @param {Array<string, string>} translations
*/
constructor(translations) {
this._translations = translations;
}
/**
* @param {string} name
* @returns {string}
*/
get(name) {
return this._translations[name];
}
/**
* @param {string} name
* @returns {boolean}
*/
has(name) {
return name in this._translations;
}

View File

@@ -33,7 +33,30 @@ export default class KimaiAPI extends KimaiPlugin {
});
}
post(url, data, callbackSuccess, callbackError) {
if (callbackError === null || callbackError === undefined) {
callbackError = this.getPostErrorHandler();
}
jQuery.ajax({
url: url,
headers: {
'X-AUTH-SESSION': true,
'Content-Type':'application/json'
},
method: 'POST',
data: data,
dataType: 'json',
success: callbackSuccess,
error: callbackError
});
}
patch(url, data, callbackSuccess, callbackError) {
if (callbackError === null || callbackError === undefined) {
callbackError = this.getPatchErrorHandler();
}
jQuery.ajax({
url: url,
headers: {
@@ -49,6 +72,10 @@ export default class KimaiAPI extends KimaiPlugin {
}
delete(url, callbackSuccess, callbackError) {
if (callbackError === null || callbackError === undefined) {
callbackError = this.getDeleteErrorHandler();
}
jQuery.ajax({
url: url,
headers: {
@@ -62,4 +89,64 @@ export default class KimaiAPI extends KimaiPlugin {
});
}
getDeleteErrorHandler() {
const self = this;
return function(xhr, err) {
self.handleError('action.delete.error', xhr, err);
};
}
getPatchErrorHandler() {
const self = this;
return function(xhr, err) {
self.handleError('action.update.error', xhr, err);
};
}
getPostErrorHandler() {
const self = this;
return function(xhr, err) {
self.handleError('action.update.error', xhr, err);
};
}
/**
* @param {string} message
* @param {jqXHR} xhr
* @param {string} err
*/
handleError(message, xhr, err) {
let resultError = err;
if (xhr.responseJSON && xhr.responseJSON.message) {
resultError = xhr.responseJSON.message;
// find validation errors
if (xhr.status === 400 && xhr.responseJSON.errors) {
let collected = ['<u>' + resultError + '</u>'];
// form errors that are not attached to a field (like extra fields)
if (xhr.responseJSON.errors.errors) {
for (let error of xhr.responseJSON.errors.errors) {
collected.push(error);
}
}
if (xhr.responseJSON.errors.children) {
for (let field in xhr.responseJSON.errors.children) {
let tmpField = xhr.responseJSON.errors.children[field];
if (tmpField.hasOwnProperty('errors') && tmpField.errors.length > 0) {
for (let error of tmpField.errors) {
collected.push(error);
}
}
}
}
if (collected.length > 0) {
resultError = collected;
}
}
} else if (xhr.status && xhr.statusText) {
resultError = '[' + xhr.status + '] ' + xhr.statusText;
}
this.getPlugin('alert').error(message, resultError);
}
}

View File

@@ -5,7 +5,7 @@
* file that was distributed with this source code.
*/
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
import KimaiPlugin from "../KimaiPlugin";
/**
* Needs to be initialized with a class name.
@@ -18,7 +18,7 @@ import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTabl
*
* @param selector
*/
export default class KimaiAPILink extends KimaiClickHandlerReducedInTableRow {
export default class KimaiAPILink extends KimaiPlugin {
constructor(selector) {
super();
@@ -75,10 +75,7 @@ export default class KimaiAPILink extends KimaiClickHandlerReducedInTableRow {
if (attributes.msgError) {
message = attributes.msgError;
}
if (xhr.responseJSON && xhr.responseJSON.message) {
err = xhr.responseJSON.message;
}
alert.error(message, err);
API.handleError(message, xhr, err);
};
if (method === 'PATCH') {
@@ -87,6 +84,12 @@ export default class KimaiAPILink extends KimaiClickHandlerReducedInTableRow {
data = attributes.payload;
}
API.patch(url, data, successHandle, errorHandle);
} else if (method === 'POST') {
let data = {};
if (attributes.payload) {
data = attributes.payload;
}
API.post(url, data, successHandle, errorHandle);
} else if (method === 'DELETE') {
API.delete(url, successHandle, errorHandle);
}

View File

@@ -24,37 +24,39 @@ export default class KimaiActiveRecords extends KimaiPlugin {
}
init() {
const menu = document.querySelector(this.selector);
this.menu = document.querySelector(this.selector);
// the menu can be hidden if user has no permissions to see it
if (menu === null) {
if (this.menu === null) {
return;
}
const dropdown = menu.querySelector('ul.dropdown-menu');
this.attributes = dropdown.dataset;
this.itemList = dropdown.querySelector('li > ul.menu');
this.label = menu.querySelector('a > span.label');
this.attributes = this.menu.dataset;
const self = this;
const handle = function() { self.reloadActiveRecords(); };
document.addEventListener('kimai.timesheetUpdate', handle);
document.addEventListener('kimai.timesheetDelete', handle);
document.addEventListener('kimai.activityUpdate', handle);
document.addEventListener('kimai.activityDelete', handle);
document.addEventListener('kimai.projectUpdate', handle);
document.addEventListener('kimai.projectDelete', handle);
document.addEventListener('kimai.customerUpdate', handle);
}
emptyList() {
this.itemList.innerHTML = '';
document.addEventListener('kimai.customerDelete', handle);
}
_toggleMenu(hasEntries) {
const menu = document.querySelector(this.selector);
const menuEmpty = document.querySelector(this.selectorEmpty);
this.menu.style.display = hasEntries ? 'inline-block' : 'none';
if (!hasEntries) {
// make sure that template entries in the menu are removed, otherwise they
// might still be shown in the browsers title
for (let record of this.menu.querySelectorAll('[data-since]')) {
record.dataset['since'] = '';
}
}
menu.style.display = hasEntries ? 'inline-block' : 'none';
const menuEmpty = document.querySelector(this.selectorEmpty);
if (menuEmpty !== null) {
menuEmpty.style.display = !hasEntries ? 'inline-block' : 'none';
}
@@ -63,42 +65,52 @@ export default class KimaiActiveRecords extends KimaiPlugin {
setEntries(entries) {
this._toggleMenu(entries.length > 0);
const template = this.menu.querySelector('[data-template="active-record"]');
const label = this.menu.querySelector('a > span.label');
if (label !== null) {
label.innerText = entries.length === 0 ? '' : entries.length;
}
if (entries.length === 0) {
this.label.innerText = '';
this.emptyList();
return;
}
let htmlToInsert = '';
const durations = this.getContainer().getPlugin('timesheet-duration');
for (let timesheet of entries) {
htmlToInsert +=
`<li>` +
`<a href="${ this.attributes['href'].replace('000', timesheet.id) }" data-event="kimai.timesheetStop kimai.timesheetUpdate" class="api-link" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">` +
`<div class="pull-left">` +
`<i class="${ this.attributes['icon'] } fa-2x"></i>` +
`</div>` +
`<h4>` +
`<span>${ timesheet.activity.name }</span>` +
`<small>` +
`<span data-title="true" data-since="${ timesheet.begin }" data-format="${ this.attributes['format'] }">${ durations.formatDuration(timesheet.duration, this.attributes['format']) }</span>` +
`</small>` +
`</h4>` +
`<p>${ timesheet.project.name } (${ timesheet.project.customer.name })</p>` +
`</a>` +
`</li>`;
}
if (this.label.dataset.warning < entries.length) {
this.label.classList = 'label label-danger';
if (template === null) {
this._replaceInNode(this.menu, entries[0]);
} else {
this.label.classList = 'label label-warning';
}
this.label.innerText = entries.length;
this.itemList.innerHTML = htmlToInsert;
const container = template.parentElement;
container.innerHTML = '';
durations.updateRecords();
for (let timesheet of entries) {
const newNode = template.cloneNode(true);
container.appendChild(this._replaceInNode(newNode, timesheet));
}
}
this.getContainer().getPlugin('timesheet-duration').updateRecords();
}
_replaceInNode(node, timesheet) {
const date = this.getContainer().getPlugin('date');
const allReplacer = node.querySelectorAll('[data-replacer]');
for (let node of allReplacer) {
const replacerName = node.dataset['replacer'];
if (replacerName === 'url') {
node.href = this.attributes['href'].replace('000', timesheet.id);
} else if (replacerName === 'activity') {
node.innerText = timesheet.activity.name;
} else if (replacerName === 'project') {
node.innerText = timesheet.project.name;
} else if (replacerName === 'customer') {
node.innerText = timesheet.project.customer.name;
} else if (replacerName === 'duration') {
node.dataset['since'] = timesheet.begin;
node.innerText = date.formatDuration(timesheet.duration);
}
}
return node;
}
reloadActiveRecords() {

View File

@@ -9,21 +9,16 @@
* [KIMAI] KimaiActiveRecordsDuration: activate the updates for all active timesheet records on this page
*/
import moment from 'moment';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiActiveRecordsDuration extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'timesheet-duration';
}
init() {
this.updateBrowserTitle = !!this.getConfiguration('updateBrowserTitle');
this.updateRecords();
const self = this;
const handle = function() { self.updateRecords(); };
@@ -38,20 +33,29 @@ export default class KimaiActiveRecordsDuration extends KimaiPlugin {
updateRecords() {
let durations = [];
const activeRecords = document.querySelectorAll(this.selector);
const activeRecords = document.querySelectorAll('[data-since]:not([data-since=""])');
if (activeRecords.length === 0) {
document.title = document.querySelector('body').dataset['title'];
if (this.updateBrowserTitle) {
if (document.body.dataset['title'] === undefined) {
this.updateBrowserTitle = false;
} else {
document.title = document.body.dataset['title'];
}
}
return;
}
for(let record of activeRecords) {
const since = record.getAttribute('data-since');
const format = record.getAttribute('data-format');
const duration = this.formatDuration(since, format);
if (record.getAttribute('data-title') !== null) {
const DATE = this.getPlugin('date');
for (let record of activeRecords) {
const since = record.dataset['since'];
const duration = DATE.formatDuration(since);
// only use the ones from the menu for the title
if (record.dataset['replacer'] !== undefined && record.dataset['title'] !== null && duration !== '?') {
durations.push(duration);
}
// but update all on the page (running entries in list pages)
record.textContent = duration;
}
@@ -59,6 +63,10 @@ export default class KimaiActiveRecordsDuration extends KimaiPlugin {
return;
}
if (!this.updateBrowserTitle) {
return;
}
let title = durations.shift();
let prefix = ' | ';
@@ -67,19 +75,4 @@ export default class KimaiActiveRecordsDuration extends KimaiPlugin {
}
document.title = title;
}
formatDuration(since, format) {
const duration = moment.duration(moment(new Date()).diff(moment(since)));
let hours = parseInt(duration.asHours()).toString();
let minutes = duration.minutes();
let seconds = duration.seconds();
// special case for hours, as they can overflow the 24h barrier - Kimai does not support days as duration unit
if (hours.length === 1) {
hours = '0' + hours;
}
return format.replace('%h', hours).replace('%m', ('0'+minutes).substr(-2)).replace('%s', ('0'+seconds).substr(-2));
}
}

View File

@@ -13,9 +13,9 @@
*/
import jQuery from 'jquery';
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
import KimaiReducedClickHandler from "./KimaiReducedClickHandler";
export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableRow {
export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
constructor(selector) {
super();
@@ -28,8 +28,33 @@ export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableR
init() {
const self = this;
this.isDirty = false;
this._addClickHandlerReducedInTableRow(this.selector, function(href) {
this.modal = jQuery('#remote_form_modal');
this.modal
.on('hide.bs.modal', function (e) {
if (self.isDirty) {
if (jQuery('#remote_form_modal .modal-body .remote_modal_is_dirty_warning').length === 0) {
const msg = self.getContainer().getTranslation().get('modal.dirty');
jQuery('#remote_form_modal .modal-body').prepend('<p class="'+(self.modal.hasClass('modal-danger') ? 'well well-sm ' : '') + 'text-danger small remote_modal_is_dirty_warning">' + msg + '</p>');
}
e.preventDefault();
return;
}
jQuery(self._getFormIdentifier()).off('change', self._isDirtyHandler);
self.isDirty = false;
self.getContainer().getPlugin('event').trigger('modal-hide');
})
.on('hidden.bs.modal', function () {
// kill all references, so GC can kick in
self.getContainer().getPlugin('form').destroyForm(self._getFormIdentifier());
jQuery('#remote_form_modal .modal-body').replaceWith('');
})
.on('show.bs.modal', function () {
self.getContainer().getPlugin('event').trigger('modal-show');
});
this._addClickHandler(this.selector, function(href) {
self.openUrlInModal(href);
});
}
@@ -39,12 +64,9 @@ export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableR
if (errorHandler === undefined) {
errorHandler = function(xhr, err) {
if (xhr.status !== undefined && xhr.status === 403) {
const alert = self.getContainer().getPlugin('alert');
alert.error(xhr.statusText);
return;
if (xhr.status === undefined || xhr.status !== 403) {
window.location = url;
}
window.location = url;
};
}
@@ -57,24 +79,33 @@ export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableR
});
}
/**
* Returns the CSS selector for the modal form.
*
* @returns {string}
* @private
*/
_getFormIdentifier() {
return '#remote_form_modal .modal-content form';
}
_openFormInModal(html) {
const self = this;
// the modal that we use to render the form in
let formIdentifier = '#remote_form_modal .modal-content form';
let formIdentifier = this._getFormIdentifier();
// if any of these is found in a response, the form will be re-displayed
let flashErrorIdentifier = 'div.alert-error';
// messages to show above the form
let flashMessageIdentifier = 'div.alert';
let form = jQuery(formIdentifier);
let remoteModal = jQuery('#remote_form_modal');
let remoteModal = this.modal;
// will be (re-)activated later
form.off('submit');
// load new form from given content
if (jQuery(html).find('#form_modal .modal-content').length > 0 ) {
// switch classes, in case the modal type changed
if (jQuery(html).find('#form_modal .modal-content').length > 0) {
// Support changing modal importance/types
remoteModal.on('hidden.bs.modal', function () {
if (remoteModal.hasClass('modal-danger')) {
remoteModal.removeClass('modal-danger');
@@ -85,16 +116,26 @@ export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableR
remoteModal.addClass('modal-danger');
}
// Support changing modal sizes
let modalDialog = remoteModal.find('.modal-dialog');
let largeModal = jQuery(html).find('.modal-dialog').hasClass('modal-lg');
if (largeModal && !modalDialog.hasClass('modal-lg')) {
modalDialog.addClass('modal-lg');
}
if (!largeModal && modalDialog.hasClass('modal-lg')) {
modalDialog.removeClass('modal-lg');
}
jQuery('#remote_form_modal .modal-content').replaceWith(
jQuery(html).find('#form_modal .modal-content')
);
// activate new loaded widgets
self.getContainer().getPlugin('date-time-picker').activateDateTimePicker(formIdentifier);
self.getContainer().getPlugin('autocomplete').activateAutocomplete(formIdentifier + " .js-autocomplete");
jQuery('#remote_form_modal [data-dismiss=modal]').on('click', function() {
self.isDirty = false;
});
// activate selectpicker if beta test is active
jQuery('.selectpicker').selectpicker('refresh');
// activate new loaded widgets
self.getContainer().getPlugin('form').activateForm(formIdentifier);
}
// show error flash messages
@@ -113,18 +154,24 @@ export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableR
});
// -----------------------------------------------------------------------
// workaround for autofocus attribute, as the modal "steals" it
remoteModal.on('shown.bs.modal', function () {
jQuery(this).find('input[type=text],textarea,select').filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
});
remoteModal.modal('show');
// the new form that was loaded via ajax
form = jQuery(formIdentifier);
this._isDirtyHandler = function(e) {
self.isDirty = true;
}
form.on('change', this._isDirtyHandler);
// click handler for modal save button, to send forms via ajax
form.on('submit', function(event){
form.on('submit', function(event) {
// if the form has a target, we let the normal HTML flow happen
if (form.attr('target') !== undefined) {
return true;
}
// otherwise we do some AJAX magic to process the form in the background
const btn = jQuery(formIdentifier + ' button[type=submit]').button('loading');
const eventName = form.attr('data-form-event');
const events = self.getContainer().getPlugin('event');
@@ -165,6 +212,7 @@ export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableR
if (msg === null || msg === undefined) {
msg = 'action.update.success';
}
self.isDirty = false;
remoteModal.modal('hide');
alert.success(msg);
}
@@ -181,6 +229,10 @@ export default class KimaiAjaxModalForm extends KimaiClickHandlerReducedInTableR
err = '[' + xhr.status +'] ' + xhr.statusText;
}
alert.error(message, err);
// this is useful for changing form fields and retrying to save (and in development to test form changes)
setTimeout(function() {
btn.button('reset');
}, 1500);
}
});
});

View File

@@ -18,6 +18,10 @@ export default class KimaiAlert extends KimaiPlugin {
return 'alert';
}
/**
* @param {string} title
* @param {string|array} message
*/
error(title, message) {
const translation = this.getContainer().getTranslation();
if (translation.has(title)) {
@@ -26,14 +30,48 @@ export default class KimaiAlert extends KimaiPlugin {
if (translation.has(message)) {
message = translation.get(message);
}
Swal.fire({
type: 'error',
title: title.replace('%reason%', ''),
text: message,
});
if (Array.isArray(message)) {
Swal.fire({
icon: 'error',
title: title.replace('%reason%', ''),
html: message.join('<br>'),
});
} else {
Swal.fire({
icon: 'error',
title: title.replace('%reason%', ''),
text: message,
});
}
}
warning(message) {
this._show('warning', message);
}
success(message) {
this._toast('success', message);
}
info(message) {
this._show('info', message);
}
_show(type, message) {
const translation = this.getContainer().getTranslation();
if (translation.has(message)) {
message = translation.get(message);
}
Swal.fire({
icon: type,
title: message,
});
}
_toast(type, message) {
const translation = this.getContainer().getTranslation();
if (translation.has(message)) {
@@ -42,10 +80,11 @@ export default class KimaiAlert extends KimaiPlugin {
Swal.fire({
timer: 2000,
timerProgressBar: true,
toast: true,
position: 'top-end',
position: 'top',
showConfirmButton: false,
type: 'success',
icon: type,
title: message,
});
}
@@ -65,7 +104,7 @@ export default class KimaiAlert extends KimaiPlugin {
Swal.fire({
title: message,
type: 'question',
icon: 'question',
showCancelButton: true,
confirmButtonText: translation.get('confirm'),
cancelButtonText: translation.get('cancel')

View File

@@ -13,9 +13,9 @@
*/
import jQuery from 'jquery';
import KimaiClickHandlerReducedInTableRow from "./KimaiClickHandlerReducedInTableRow";
import KimaiReducedClickHandler from "./KimaiReducedClickHandler";
export default class KimaiAlternativeLinks extends KimaiClickHandlerReducedInTableRow {
export default class KimaiAlternativeLinks extends KimaiReducedClickHandler {
constructor(selector) {
super();
@@ -23,7 +23,7 @@ export default class KimaiAlternativeLinks extends KimaiClickHandlerReducedInTab
}
init() {
this._addClickHandlerReducedInTableRow(this.selector, function(href) {
this._addClickHandler(this.selector, function(href) {
window.location = href;
});
}

View File

@@ -20,7 +20,7 @@ export default class KimaiAutocomplete extends KimaiPlugin {
}
init() {
this.activateAutocomplete(this.selector);
this.minChars = this.getConfiguration('autoComplete');
}
getId() {
@@ -35,60 +35,72 @@ export default class KimaiAutocomplete extends KimaiPlugin {
return this.splitTagList(term).pop();
}
activateAutocomplete(selector)
{
const apiUrl = jQuery(selector).attr('data-autocomplete-url');
activateAutocomplete(selector) {
const self = this;
const API = self.getContainer().getPlugin('api');
jQuery(selector + ' ' + this.selector).each(function(index) {
const currentField = jQuery(this);
const apiUrl = currentField.attr('data-autocomplete-url');
const API = self.getContainer().getPlugin('api');
jQuery(selector)
// don't navigate away from the field on tab when selecting an item
.on("keydown", function (event) {
if (event.keyCode === jQuery.ui.keyCode.TAB &&
jQuery(this).autocomplete("instance").menu.active) {
event.preventDefault();
}
})
.autocomplete({
source: function (request, response) {
const lastEntry = self.extractLastTag(request.term);
API.get(apiUrl, {'name': lastEntry}, function(data){
response(data);
});
},
search: function () {
// custom minLength
var term = self.extractLastTag(this.value);
if (term.length < 2) {
return false;
currentField
// don't navigate away from the field on tab when selecting an item
.on("keydown", function (event) {
if (event.keyCode === jQuery.ui.keyCode.TAB &&
jQuery(this).autocomplete("instance").menu.active) {
event.preventDefault();
}
},
focus: function () {
// prevent value inserted on focus
return false;
},
select: function (event, ui) {
var terms = self.splitTagList(this.value);
})
.autocomplete({
source: function (request, response) {
const lastEntry = self.extractLastTag(request.term);
API.get(apiUrl, {'name': lastEntry}, function(data){
response(data);
});
},
search: function () {
// custom minLength
var term = self.extractLastTag(this.value);
if (term.length < self.minChars) {
return false;
}
},
focus: function () {
// prevent value inserted on focus
return false;
},
select: function (event, ui) {
var terms = self.splitTagList(this.value);
// remove the current input
terms.pop();
// remove the current input
terms.pop();
// check if selected tag is already in list
if (!terms.includes(ui.item.value)) {
// add the selected item
terms.push(ui.item.value);
// check if selected tag is already in list
if (!terms.includes(ui.item.value)) {
// add the selected item
terms.push(ui.item.value);
}
// add placeholder to get the comma-and-space at the end
terms.push("");
this.value = terms.join(", ");
$(this).trigger('change');
return false;
}
}
// add placeholder to get the comma-and-space at the end
terms.push("");
)
;
});
}
this.value = terms.join(", ");
$(this).trigger('change');
return false;
}
}
);
destroyAutocomplete(selector) {
jQuery(selector + ' ' + this.selector).each(function(index) {
const currentField = jQuery(this);
currentField.autocomplete("destroy");
currentField.removeData('autocomplete');
});
}
}

View File

@@ -0,0 +1,70 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import KimaiPlugin from "../KimaiPlugin";
/**
* Needs to be initialized with a class name.
*
* Allows to assign the given selector to any element, which then is used as click-handler
* calling an API method and trigger the event from data-event attribute afterwards.
*
* @param selector
*/
export default class KimaiConfirmationLink extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
init() {
const self = this;
document.addEventListener('click', function(event) {
let target = event.target;
while (target !== null && !target.matches('body')) {
if (target.classList.contains(self.selector)) {
const attributes = target.dataset;
// is this a link?
let url = attributes['href'];
// or another HTML element with a custom href
if (!url) {
url = target.getAttribute('href');
}
// or is this a button?
let form = null;
if (target.type === 'submit' && target.form !== undefined) {
form = target.form;
}
if (attributes.question !== undefined) {
self.getContainer().getPlugin('alert').question(attributes.question, function(value) {
if (value) {
if (form === null) {
document.location = url;
} else {
if (url !== null) {
form.action = url;
}
form.submit();
}
}
});
}
event.preventDefault();
event.stopPropagation();
}
target = target.parentNode;
}
});
}
}

View File

@@ -14,9 +14,10 @@ import KimaiPlugin from "../KimaiPlugin";
export default class KimaiDatatable extends KimaiPlugin {
constructor(selector) {
constructor(contentAreaSelector, tableSelector) {
super();
this.selector = selector;
this.contentArea = contentAreaSelector;
this.selector = tableSelector;
}
getId() {
@@ -34,6 +35,8 @@ export default class KimaiDatatable extends KimaiPlugin {
const attributes = dataTable.dataset;
const events = attributes['reloadEvent'];
this.fixDropdowns();
if (events === undefined) {
return;
}
@@ -44,18 +47,25 @@ export default class KimaiDatatable extends KimaiPlugin {
for (let eventName of events.split(' ')) {
document.addEventListener(eventName, handle);
}
document.addEventListener('pagination-change', handle);
document.addEventListener('filter-change', handle);
}
reloadDatatable() {
const self = this;
const contentArea = this.contentArea;
const durations = this.getContainer().getPlugin('timesheet-duration');
const form = jQuery('.toolbar form');
const toolbarSelector = this.getContainer().getPlugin('toolbar').getSelector();
const form = jQuery(toolbarSelector);
let loading = '<div class="overlay"><i class="fas fa-sync fa-spin"></i></div>';
jQuery('section.content').append(loading);
jQuery(contentArea).append(loading);
// remove the empty fields to prevent errors
let formData = jQuery('.toolbar form :input')
let formData = jQuery(toolbarSelector + ' :input')
.filter(function(index, element) {
return jQuery(element).val() != '';
return jQuery(element).val() !== '';
})
.serialize();
@@ -64,10 +74,11 @@ export default class KimaiDatatable extends KimaiPlugin {
type: form.attr('method'),
data: formData,
success: function(html) {
jQuery('section.content').replaceWith(
jQuery(html).find('section.content')
jQuery(contentArea).replaceWith(
jQuery(html).find(contentArea)
);
durations.updateRecords();
self.fixDropdowns();
},
error: function(xhr, err) {
form.submit();
@@ -75,4 +86,21 @@ export default class KimaiDatatable extends KimaiPlugin {
});
}
/**
* show dropdown menu upwards, if it is outside the visible viewport
*/
fixDropdowns() {
const docHeight = jQuery(document).height();
jQuery(this.selector + ' [data-toggle=dropdown]').each(function() {
const parent = jQuery(this).parent();
const menu = parent.find('.dropdown-menu');
if (parent && menu) {
if ((parent.offset().top + parent.outerHeight() + menu.outerHeight()) > docHeight) {
parent.addClass('dropup').removeClass('dropdown');
}
}
});
}
}

View File

@@ -44,7 +44,7 @@ export default class KimaiDatatableColumnView extends KimaiPlugin {
});
for (let checkbox of this.modal.querySelectorAll('form input[type=checkbox]')) {
checkbox.addEventListener('click', function () {
self.changeVisibility(checkbox.getAttribute('name'));
self.changeVisibility(checkbox.getAttribute('name'), checkbox.checked);
});
}
}
@@ -55,7 +55,7 @@ export default class KimaiDatatableColumnView extends KimaiPlugin {
for (let checkbox of form.querySelectorAll('input[type=checkbox]')) {
settings[checkbox.getAttribute('name')] = checkbox.checked;
}
Cookies.set(form.getAttribute('name'), JSON.stringify(settings), {expires: 365});
Cookies.set(form.getAttribute('name'), JSON.stringify(settings), {expires: 365, SameSite: 'Strict'});
jQuery(this.modal).modal('toggle');
}
@@ -70,24 +70,40 @@ export default class KimaiDatatableColumnView extends KimaiPlugin {
jQuery(this.modal).modal('toggle');
}
changeVisibility(columnName) {
const table = document.getElementById('datatable_' + this.id).getElementsByClassName('dataTable')[0];
let column = 0;
let foundColumn = false;
for (let columnElement of table.getElementsByTagName('th')) {
if (columnElement.getAttribute('data-field') === columnName) {
foundColumn = true;
break;
changeVisibility(columnName, checked) {
const tables = document.getElementsByClassName('datatable_' + this.id);
for (let tableBox of tables) {
let column = 0;
let foundColumn = false;
let table = tableBox.getElementsByClassName('dataTable')[0];
for (let columnElement of table.getElementsByTagName('th')) {
if (columnElement.getAttribute('data-field') === columnName) {
foundColumn = true;
break;
}
if (columnElement.getAttribute('colspan') !== null) {
console.log('Tables with colspans are not supported!');
}
column++;
}
column++;
}
if (!foundColumn) {
return;
}
if (!foundColumn) {
return;
}
for (let rowElement of table.getElementsByTagName('tr')) {
rowElement.children[column].classList.toggle('hidden');
for (let rowElement of table.getElementsByTagName('tr')) {
if (rowElement.children[column] === undefined) {
continue;
}
if (checked) {
rowElement.children[column].classList.remove('hidden');
} else {
rowElement.children[column].classList.add('hidden');
}
}
}
}

View File

@@ -11,7 +11,6 @@
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
import moment from 'moment';
export default class KimaiDatePicker extends KimaiPlugin {
@@ -24,26 +23,34 @@ export default class KimaiDatePicker extends KimaiPlugin {
return 'date-picker';
}
init() {
this.activateDatePicker(this.selector);
}
activateDatePicker(selector) {
let translator = this.getContainer().getTranslation();
jQuery(selector + ' input[data-datepickerenable="on"]').each(function(index) {
const TRANSLATE = this.getContainer().getTranslation();
const DATE_UTILS = this.getContainer().getPlugin('date');
const firstDow = this.getConfiguration('first_dow_iso') % 7;
jQuery(selector + ' ' + this.selector).each(function(index) {
let localeFormat = jQuery(this).data('format');
jQuery(this).daterangepicker({
singleDatePicker: true,
showDropdowns: true,
autoUpdateInput: false,
drops: 'down',
locale: {
format: localeFormat,
firstDay: 1,
applyLabel: translator.get('confirm'),
cancelLabel: translator.get('cancel'),
customRangeLabel: translator.get('customRange'),
daysOfWeek: moment.weekdaysShort(),
monthNames: moment.months(),
firstDay: firstDow,
applyLabel: TRANSLATE.get('confirm'),
cancelLabel: TRANSLATE.get('cancel'),
customRangeLabel: TRANSLATE.get('customRange'),
daysOfWeek: DATE_UTILS.getWeekDaysShort(),
monthNames: DATE_UTILS.getMonthNames(),
}
});
jQuery(this).on('show.daterangepicker', function (ev, picker) {
if (picker.element.offset().top - jQuery(window).scrollTop() + picker.container.outerHeight() + 30 > jQuery(window).height()) {
// "up" is not possible here, because the code is triggered on many mobile phones and the picker then appears out of window
picker.drops = 'auto';
picker.move();
}
});
@@ -54,4 +61,13 @@ export default class KimaiDatePicker extends KimaiPlugin {
});
}
destroyDatePicker(selector) {
jQuery(selector + ' ' + this.selector).each(function(index) {
if (jQuery(this).data('daterangepicker') !== undefined) {
jQuery(this).daterangepicker('destroy');
jQuery(this).data('daterangepicker').remove();
}
});
}
}

View File

@@ -24,50 +24,73 @@ export default class KimaiDateRangePicker extends KimaiPlugin {
return 'date-range-picker';
}
init() {
this.activateDateRangePicker(this.selector);
}
activateDateRangePicker(selector) {
let translator = this.getContainer().getTranslation();
jQuery(selector + ' input[data-daterangepickerenable="on"]').each(function(index) {
const TRANSLATE = this.getContainer().getTranslation();
const DATE_UTILS = this.getContainer().getPlugin('date');
const firstDow = this.getConfiguration('first_dow_iso') % 7;
jQuery(selector + ' ' + this.selector).each(function(index) {
let localeFormat = jQuery(this).data('format');
let separator = jQuery(this).data('separator');
let rangesList = {};
rangesList[translator.get('today')] = [moment(), moment()];
rangesList[translator.get('yesterday')] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
rangesList[translator.get('thisWeek')] = [moment().startOf('week'), moment().endOf('week')];
rangesList[translator.get('lastWeek')] = [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')];
rangesList[translator.get('thisMonth')] = [moment().startOf('month'), moment().endOf('month')];
rangesList[translator.get('lastMonth')] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
rangesList[translator.get('thisYear')] = [moment().startOf('year'), moment().endOf('year')];
rangesList[translator.get('lastYear')] = [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')];
rangesList[TRANSLATE.get('today')] = [moment(), moment()];
rangesList[TRANSLATE.get('yesterday')] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
rangesList[TRANSLATE.get('thisWeek')] = [moment().startOf('isoWeek'), moment().endOf('isoWeek')];
rangesList[TRANSLATE.get('lastWeek')] = [moment().subtract(1, 'week').startOf('isoWeek'), moment().subtract(1, 'week').endOf('isoWeek')];
if (firstDow === 0) { // sunday = 0
rangesList[TRANSLATE.get('thisWeek')] = [moment().startOf('isoWeek').subtract(1, 'day'), moment().endOf('isoWeek').subtract(1, 'day')];
rangesList[TRANSLATE.get('lastWeek')] = [moment().subtract(1, 'week').startOf('isoWeek').subtract(1, 'day'), moment().subtract(1, 'week').endOf('isoWeek').subtract(1, 'day')];
}
rangesList[TRANSLATE.get('thisMonth')] = [moment().startOf('month'), moment().endOf('month')];
rangesList[TRANSLATE.get('lastMonth')] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
rangesList[TRANSLATE.get('thisYear')] = [moment().startOf('year'), moment().endOf('year')];
rangesList[TRANSLATE.get('lastYear')] = [moment().subtract(1, 'year').startOf('year'), moment().subtract(1, 'year').endOf('year')];
jQuery(this).daterangepicker({
showDropdowns: true,
autoUpdateInput: false,
autoApply: false,
linkedCalendars: false,
linkedCalendars: true,
drops: 'down',
locale: {
separator: separator,
format: localeFormat,
firstDay: 1,
applyLabel: translator.get('confirm'),
cancelLabel: translator.get('cancel'),
customRangeLabel: translator.get('customRange'),
daysOfWeek: moment.weekdaysShort(),
monthNames: moment.months(),
firstDay: firstDow,
applyLabel: TRANSLATE.get('confirm'),
cancelLabel: TRANSLATE.get('cancel'),
customRangeLabel: TRANSLATE.get('customRange'),
daysOfWeek: DATE_UTILS.getWeekDaysShort(),
monthNames: DATE_UTILS.getMonthNames(),
},
ranges: rangesList,
alwaysShowCalendars: true
});
jQuery(this).on('show.daterangepicker', function (ev, picker) {
if (picker.element.offset().top - jQuery(window).scrollTop() + picker.container.outerHeight() + 30 > jQuery(window).height()) {
// "up" is not possible here, because the code is triggered on many mobile phones and the picker then appears out of window
picker.drops = 'auto';
picker.move();
}
});
jQuery(this).on('apply.daterangepicker', function(ev, picker) {
jQuery(this).val(picker.startDate.format(localeFormat) + ' - ' + picker.endDate.format(localeFormat));
jQuery(this).data('begin', picker.startDate.format(localeFormat));
jQuery(this).data('end', picker.endDate.format(localeFormat));
jQuery(this).trigger("change");
});
});
}
destroyDateRangePicker(selector) {
jQuery(selector + ' ' + this.selector).each(function(index) {
if (jQuery(this).data('daterangepicker') !== undefined) {
jQuery(this).daterangepicker('destroy');
jQuery(this).data('daterangepicker').remove();
}
});
}
}

View File

@@ -11,7 +11,6 @@
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
import moment from 'moment';
export default class KimaiDateTimePicker extends KimaiPlugin {
@@ -24,30 +23,37 @@ export default class KimaiDateTimePicker extends KimaiPlugin {
return 'date-time-picker';
}
init() {
this.activateDateTimePicker(this.selector);
}
activateDateTimePicker(selector) {
let translator = this.getContainer().getTranslation();
let configuration = this.getContainer().getConfiguration();
const TRANSLATE = this.getContainer().getTranslation();
const DATE_UTILS = this.getContainer().getPlugin('date');
const firstDow = this.getConfiguration('first_dow_iso') % 7;
const is24hours = this.getConfiguration('twentyFourHours');
jQuery(selector + ' input[data-datetimepicker="on"]').each(function(index) {
jQuery(selector + ' ' + this.selector).each(function(index) {
let localeFormat = jQuery(this).data('format');
jQuery(this).daterangepicker({
singleDatePicker: true,
timePicker: true,
timePicker24Hour: configuration.get('twentyFourHours'),
timePicker24Hour: is24hours,
showDropdowns: true,
autoUpdateInput: false,
drops: 'down',
locale: {
format: localeFormat,
firstDay: 1,
applyLabel: translator.get('confirm'),
cancelLabel: translator.get('cancel'),
customRangeLabel: translator.get('customRange'),
daysOfWeek: moment.weekdaysShort(),
monthNames: moment.months(),
firstDay: firstDow,
applyLabel: TRANSLATE.get('confirm'),
cancelLabel: TRANSLATE.get('cancel'),
customRangeLabel: TRANSLATE.get('customRange'),
daysOfWeek: DATE_UTILS.getWeekDaysShort(),
monthNames: DATE_UTILS.getMonthNames(),
}
});
jQuery(this).on('show.daterangepicker', function (ev, picker) {
if (picker.element.offset().top - jQuery(window).scrollTop() + picker.container.outerHeight() + 30 > jQuery(window).height()) {
// "up" is not possible here, because the code is triggered on many mobile phones and the picker then appears out of window
picker.drops = 'auto';
picker.move();
}
});
@@ -58,4 +64,13 @@ export default class KimaiDateTimePicker extends KimaiPlugin {
});
}
destroyDateTimePicker(selector) {
jQuery(selector + ' ' + this.selector).each(function(index) {
if (jQuery(this).data('daterangepicker') !== undefined) {
jQuery(this).daterangepicker('destroy');
jQuery(this).data('daterangepicker').remove();
}
});
}
}

View File

@@ -0,0 +1,110 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiDateUtils: responsible for handling date specific tasks
*/
import KimaiPlugin from '../KimaiPlugin';
import moment from 'moment';
export default class KimaiDateUtils extends KimaiPlugin {
getId() {
return 'date';
}
/**
* @param {string} dateTime
* @returns {string}
*/
getFormattedDate(dateTime) {
return moment(dateTime).format(this.getConfiguration('formatDate'));
}
getWeekDaysShort() {
return moment.localeData().weekdaysShort();
}
getMonthNames() {
return moment.localeData().months();
}
formatDuration(since) {
const duration = moment.duration(moment(new Date()).diff(moment(since)));
return this.formatMomentDuration(duration);
}
formatSeconds(seconds) {
const duration = moment.duration('PT' + seconds + 'S');
return this.formatMomentDuration(duration);
}
/**
* @param {moment.Duration} duration
* @returns {string|*}
*/
formatMomentDuration(duration) {
const hours = parseInt(duration.asHours()).toString();
const minutes = duration.minutes();
const seconds = duration.seconds();
return this.formatTime(hours, minutes, seconds);
}
formatTime(hours, minutes, seconds) {
if (hours < 0 || minutes < 0 || seconds < 0) {
return '?';
}
// special case for hours, as they can overflow the 24h barrier - Kimai does not support days as duration unit
if (hours.length === 1) {
hours = '0' + hours;
}
const format = this.getConfiguration('formatDuration');
return format.replace('%h', hours).replace('%m', ('0' + minutes).substr(-2)).replace('%s', ('0' + seconds).substr(-2));
}
/**
* @param {string} duration
* @returns {int}
*/
getSecondsFromDurationString(duration)
{
duration = duration.trim().toUpperCase();
let momentDuration = moment.duration(NaN);
if (duration.indexOf(':') !== -1) {
momentDuration = moment.duration(duration);
} else if (duration.indexOf('.') !== -1 || duration.indexOf(',') !== -1) {
duration = duration.replace(/,/, '.');
duration = (parseFloat(duration) * 3600).toString();
momentDuration = moment.duration('PT' + duration + 'S');
} else if (duration.indexOf('H') !== -1 || duration.indexOf('M') !== -1 || duration.indexOf('S') !== -1) {
/* D for days does not work, because 'PT1H' but with days 'P1D' is used */
momentDuration = moment.duration('PT' + duration);
} else {
let c = parseInt(duration);
let d = parseInt(duration).toFixed();
if (!isNaN(c) && duration === d) {
duration = (c * 3600).toString();
momentDuration = moment.duration('PT' + duration + 'S');
}
}
if (!momentDuration.isValid()) {
return 0;
}
return momentDuration.asSeconds();
}
}

View File

@@ -0,0 +1,35 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiEscape: sanitize strings
*/
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiEscape extends KimaiPlugin {
getId() {
return 'escape';
}
/**
* @param {string} title
* @returns {string}
*/
escapeForHtml(title) {
const tagsToReplace = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
};
return title.replace(/[&<>]/g, function(tag) {
return tagsToReplace[tag] || tag;
});
};
}

View File

@@ -25,7 +25,7 @@ export default class KimaiEvent extends KimaiPlugin {
for(let event of name.split(' ')) {
let triggerEvent = new Event(event);
if (details !== undefined) {
triggerEvent = new CustomEvent(name, {detail: details});
triggerEvent = new CustomEvent(event, {detail: details});
}
document.dispatchEvent(triggerEvent);
}

View File

@@ -0,0 +1,56 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiForm: basic functions for all forms
*/
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiForm extends KimaiPlugin {
getId() {
return 'form';
}
activateForm(formSelector, container) {
this.getContainer().getPlugin('date-range-picker').activateDateRangePicker(formSelector);
this.getContainer().getPlugin('date-time-picker').activateDateTimePicker(formSelector);
this.getContainer().getPlugin('date-picker').activateDatePicker(formSelector);
this.getContainer().getPlugin('autocomplete').activateAutocomplete(formSelector);
this.getContainer().getPlugin('form-select').activateSelectPicker(formSelector, container);
}
destroyForm(formSelector) {
this.getContainer().getPlugin('form-select').destroySelectPicker(formSelector);
this.getContainer().getPlugin('autocomplete').destroyAutocomplete(formSelector);
this.getContainer().getPlugin('date-picker').destroyDatePicker(formSelector);
this.getContainer().getPlugin('date-time-picker').destroyDateTimePicker(formSelector);
this.getContainer().getPlugin('date-range-picker').destroyDateRangePicker(formSelector);
}
/**
* @param {HTMLFormElement} form
* @param {Object} overwrites
* @returns {string}
*/
convertFormDataToQueryString(form, overwrites = {})
{
let serialized = [];
let data = new FormData(form);
for (const key in overwrites) {
data.set(key, overwrites[key]);
}
for (let row of data) {
serialized.push(encodeURIComponent(row[0]) + "=" + encodeURIComponent(row[1]));
}
return serialized.join('&');
}
}

View File

@@ -0,0 +1,281 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiFormSelect: enhanced functionality for HTML select's
*/
import KimaiPlugin from "../KimaiPlugin";
import jQuery from "jquery";
export default class KimaiFormSelect extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
getId() {
return 'form-select';
}
init() {
// selects the original value inside select2 dropdowns, as the "reset" event (the updated option)
// is not automatically catched by select2
jQuery('body').on('reset', 'form', function(event) {
setTimeout(function() {
jQuery(event.target).find(this.selector).trigger('change');
}, 10);
});
const self = this;
// Function to match the name of the parent and not only the names of the children
// Based on the original matcher function of Select2: https://github.com/select2/select2/blob/5765090318c4d382ae56463cfa25ba8ca7bdd495/src/js/select2/defaults.js#L272
// More information: https://select2.org/searching | https://github.com/select2/docs/blob/develop/pages/11.searching/docs.md
this.matcher = function (params, data) {
// Always return the object if there is nothing to compare
if (jQuery.trim(params.term) === '') {
return data;
}
// Check whether options has children
let hasChildren = data.children && data.children.length > 0;
// Split search param by space to search for all terms and convert all to uppercase
let terms = params.term.toUpperCase().split(' ');
let original = data.text.toUpperCase();
// Always return the parent option including its children, when the name matches one of the params
// Check if the text contains all or at least one of the terms
let foundAll = true;
let foundOne = false;
let missingTerms = [];
terms.forEach(function(item, index) {
if (original.indexOf(item) > -1) {
foundOne = true;
} else {
foundAll = false;
missingTerms.push(item);
}
});
// If the option element contains all terms, return it
if (foundAll) {
return data;
}
// Do a recursive check for options with children
if (hasChildren) {
// If the parent already contains one or more search terms, proceed only with the missing ones
// First: Clone the original params object...
let newParams = jQuery.extend(true, {}, params);
if (foundOne) {
newParams.term = missingTerms.join(' ');
} else {
newParams.term = params.term;
}
// Clone the data object if there are children
// This is required as we modify the object to remove any non-matches
let match = jQuery.extend(true, {}, data);
// Check each child of the option
for (let c = data.children.length - 1; c >= 0; c--) {
let child = data.children[c];
let matches = self.matcher(newParams, child);
// If there wasn't a match, remove the object in the array
if (matches === null) {
match.children.splice(c, 1);
}
}
// If any children matched, return the new object
if (match.children.length > 0) {
return match;
}
}
// If the option or its children do not contain the term, don't return anything
return null;
}
}
activateSelectPickerByElement(node, container) {
let options = {};
if (container !== undefined) {
options = {
dropdownParent: jQuery(container),
};
}
options = {...options, ...{
language: this.getConfiguration('locale').replace('_', '-'),
theme: "bootstrap",
matcher: this.matcher,
dropdownAutoWidth: true,
width: "resolve"
}};
const element = jQuery(node);
if (node.dataset['renderer'] !== undefined && node.dataset['renderer'] === 'color') {
const templateResultFunc = function (state) {
return jQuery('<span><span style="background-color:'+state.id+'; width: 20px; height: 20px; display: inline-block; margin-right: 10px;">&nbsp;</span>' + state.text + '</span>');
};
const colorOptions = {...options, ...{
templateSelection: templateResultFunc,
templateResult: templateResultFunc
}};
element.select2(colorOptions);
} else {
element.select2(options);
}
// this is a bugfix for safari, which does render the dropdown only with correct width upon the second opening
// see https://github.com/select2/select2/issues/4678
element.on('select2:open', function (ev) {
if (element.data('performing-reopen') === undefined || element.data('performing-reopen') === null) {
element.data('performing-reopen', true);
element.select2('close');
element.select2('open');
}
});
}
activateSelectPicker(selector, container) {
const self = this;
jQuery(selector + ' ' + this.selector).each(function(i, el) {
self.activateSelectPickerByElement(el, container);
});
}
destroySelectPicker(selector) {
jQuery(selector + ' ' + this.selector).select2('destroy');
}
updateOptions(selectIdentifier, data) {
let select = jQuery(selectIdentifier);
let emptyOption = jQuery(selectIdentifier + ' option[value=""]');
const selectedValue = select.val();
select.find('option').remove().end().find('optgroup').remove().end();
if (emptyOption.length !== 0) {
select.append(this._createOption(emptyOption.text(), ''));
}
let emptyOpts = [];
let options = [];
let titlePattern = null;
if (select[0] !== undefined && select[0].dataset !== undefined && select[0].dataset['optionPattern'] !== undefined) {
titlePattern = select[0].dataset['optionPattern'];
}
if (titlePattern === null || titlePattern === '') {
titlePattern = '{name}';
}
for (const [key, value] of Object.entries(data)) {
if (key === '__empty__') {
for (const entity of value) {
emptyOpts.push(this._createOption(this._getTitleFromPattern(titlePattern, entity), entity.id));
}
continue;
}
let optGroup = this._createOptgroup(key);
for (const entity of value) {
optGroup.appendChild(this._createOption(this._getTitleFromPattern(titlePattern, entity), entity.id));
}
options.push(optGroup);
}
select.append(options);
select.append(emptyOpts);
// if available, re-select the previous selected option (mostly usable for global activities)
select.val(selectedValue);
// if we don't trigger the change, the other selects won't reset
select.trigger('change');
// if select2 is active, this will tell the select to refresh
if (select.hasClass('selectpicker')) {
select.trigger('change.select2');
}
}
/**
* @param {string} pattern
* @param {array} entity
* @private
*/
_getTitleFromPattern(pattern, entity) {
const DATE_UTILS = this.getPlugin('date');
const regexp = new RegExp('{[^}]*?}','g');
let title = pattern;
let match = null;
while ((match = regexp.exec(pattern)) !== null) {
const field = match[0].substr(1, match[0].length - 2);
let value = entity[field] === undefined ? null : entity[field];
if ((field === 'start' || field === 'end')) {
if (value === null) {
value = '?';
} else {
value = DATE_UTILS.getFormattedDate(value);
}
}
title = title.replace(new RegExp('{' + field + '}', 'g'), value ?? '');
}
title = title.replace(/- \?-\?/, '');
title = title.replace(/\r\n|\r|\n/g, ' ');
title = title.substr(0, 110);
const chars = '- ';
let start = 0, end = title.length;
while (start < end && chars.indexOf(title[start]) >= 0) {
++start;
}
while (end > start && chars.indexOf(title[end - 1]) >= 0) {
--end;
}
return (start > 0 || end < title.length) ? title.substring(start, end) : title;
}
/**
* @param {string} label
* @param {string} value
* @returns {HTMLElement}
* @private
*/
_createOption(label, value) {
let option = document.createElement('option');
option.innerText = label;
option.value = value;
return option;
}
/**
* @param {string} label
* @returns {HTMLElement}
* @private
*/
_createOptgroup(label) {
let optGroup = document.createElement('optgroup');
optGroup.label = label;
return optGroup;
}
}

View File

@@ -1,24 +0,0 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiJqueryPluginInitializer: initialize jQuery plugins
*/
import jQuery from 'jquery';
import KimaiPlugin from '../KimaiPlugin';
export default class KimaiJqueryPluginInitializer extends KimaiPlugin {
init() {
// activate the dropdown functionality
jQuery('.dropdown-toggle').dropdown();
// activate the tooltip functionality
jQuery('[data-toggle="tooltip"]').tooltip();
}
}

View File

@@ -0,0 +1,73 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiMultiUpdateForm: handle the multi update checkbox list and form
*/
import KimaiPlugin from '../KimaiPlugin';
import jQuery from "jquery";
export default class KimaiMultiUpdateTable extends KimaiPlugin {
init() {
const self = this;
jQuery('body').
on('change', '#multi_update_all', function(event) {
jQuery('.multi_update_single').prop('checked', jQuery(event.target).prop('checked'));
self.toggleForm();
})
.on('change', '.multi_update_single', function(event) {
self.toggleForm();
})
.on('change', '#multi_update_table_action', function(event) {
const selectedItem = jQuery('#multi_update_table_action option:selected');
const selectedVal = selectedItem.val();
if (selectedVal === '') {
return;
}
const form = jQuery('#multi_update_form form');
const selectedText = selectedItem.text();
const ids = self.getSelectedIds();
const question = form.attr('data-question').replace(/%action%/, selectedText).replace(/%count%/, ids.length);
self.getContainer().getPlugin('alert').question(question, function(value) {
if (value) {
form.attr('action', selectedVal).submit();
} else {
jQuery('#multi_update_table_action').val('').trigger('change');
}
});
});
}
getSelectedIds()
{
let ids = [];
jQuery('.multi_update_single:checked').each(function(i){
ids[i] = $(this).val();
});
return ids;
}
toggleForm()
{
const ids = this.getSelectedIds();
jQuery('#multi_update_table_entities').val(ids.join(','));
if (ids.length > 0) {
jQuery('#multi_update_form').show();
} else {
jQuery('#multi_update_form').hide();
}
}
}

View File

@@ -40,10 +40,15 @@ export default class KimaiRecentActivities extends KimaiPlugin {
// don't block initial browser rendering
setTimeout(handle, 500);
document.addEventListener('kimai.timesheetStop', handle);
document.addEventListener('kimai.recentActivities', handle);
document.addEventListener('kimai.timesheetUpdate', handle);
document.addEventListener('kimai.timesheetDelete', handle);
document.addEventListener('kimai.activityUpdate', handle);
document.addEventListener('kimai.activityDelete', handle);
document.addEventListener('kimai.projectUpdate', handle);
document.addEventListener('kimai.projectDelete', handle);
document.addEventListener('kimai.customerUpdate', handle);
document.addEventListener('kimai.customerDelete', handle);
}
emptyList() {
@@ -60,9 +65,10 @@ export default class KimaiRecentActivities extends KimaiPlugin {
for (let timesheet of entries) {
let label = this.attributes['template']
.replace('%customer%', timesheet.project.customer.name)
.replace('%project%', timesheet.project.name)
.replace('%activity%', timesheet.activity.name);
.replace('%customer%', this.escape(timesheet.project.customer.name))
.replace('%project%', this.escape(timesheet.project.name))
.replace('%activity%', this.escape(timesheet.activity.name))
;
htmlToInsert +=
`<li>` +

View File

@@ -6,27 +6,28 @@
*/
/*!
* [KIMAI] KimaiClickHandlerReducedInTableRow: abstract class
* [KIMAI] KimaiReducedClickHandler: abstract class
*/
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
export default class KimaiClickHandlerReducedInTableRow extends KimaiPlugin {
export default class KimaiReducedClickHandler extends KimaiPlugin {
_addClickHandlerReducedInTableRow(selector, callback)  {
_addClickHandler(selector, callback) {
jQuery('body').on('click', selector, function(event) {
// just in case an inner element is editable, than this should not be triggered
if (event.target.parentNode.isContentEditable || event.target.isContentEditable) {
if (event.target.parentNode.isContentEditable || event.target.isContentEditable) {
return;
}
// handles the "click" on table rows to open an entry for editing: when a button within a row is clicked,
// we don't want the table row event to be processed - so we intercept it
// handles the "click" on table rows or list elements
let target = event.target;
if (event.currentTarget.matches('tr')) {
if (event.currentTarget.matches('tr') || event.currentTarget.matches('li')) {
while (target !== null && !target.matches('body')) {
if (target.matches('a') || target.matches ('button')) {
// when an element within the row is clicked, that can trigger stuff itself, we don't want the event to be processed
// don't act if a link, button or form element was clicked
if (target.matches('a') || target.matches ('button') || target.matches ('input')) {
return;
}
target = target.parentNode;
@@ -41,6 +42,10 @@ export default class KimaiClickHandlerReducedInTableRow extends KimaiPlugin {
href = jQuery(this).attr('href');
}
if (href === undefined || href === null || href === '') {
return;
}
callback(href);
});
}

View File

@@ -11,6 +11,7 @@
import jQuery from 'jquery';
import KimaiPlugin from "../KimaiPlugin";
import moment from 'moment';
export default class KimaiSelectDataAPI extends KimaiPlugin {
@@ -32,46 +33,103 @@ export default class KimaiSelectDataAPI extends KimaiPlugin {
const API = this.getContainer().getPlugin('api');
jQuery('body').on('change', selector, function(event) {
let apiUrl = jQuery(this).attr('data-api-url').replace('-s-', jQuery(this).val());
const targetSelect = '#' + jQuery(this).attr('data-related-select');
const targetSelect = '#' + this.dataset['relatedSelect'];
// if the related target select does not exist, we do not need to load the related data
if (jQuery(targetSelect).length === 0) {
return;
}
if (jQuery(this).val() === '') {
if (jQuery(this).attr('data-empty-url') === undefined) {
let formPrefix = this.dataset['formPrefix'];
if (formPrefix === undefined || formPrefix === null) {
formPrefix = '';
} else if (formPrefix.length > 0) {
formPrefix += '_';
}
let newApiUrl = self._buildUrlWithFormFields(this.dataset['apiUrl'], formPrefix);
const selectValue = jQuery(this).val();
// Problem: select a project with activities and then select a customer that has no project
// results in a wrong URL, it triggers "activities?project=" instead of using the "emptyUrl"
if (selectValue === undefined || selectValue === null || selectValue === '' || (Array.isArray(selectValue) && selectValue.length === 0)) {
if (this.dataset['emptyUrl'] === undefined) {
self._updateSelect(targetSelect, {});
jQuery(targetSelect).attr('disabled', 'disabled');
return;
}
apiUrl = jQuery(this).attr('data-empty-url').replace('-s-', jQuery(this).val());
newApiUrl = self._buildUrlWithFormFields(this.dataset['emptyUrl'], formPrefix);
}
jQuery(targetSelect).removeAttr('disabled');
API.get(apiUrl, {}, function(data){
API.get(newApiUrl, {}, function(data){
self._updateSelect(targetSelect, data);
});
});
}
_buildUrlWithFormFields(apiUrl, formPrefix) {
let newApiUrl = apiUrl;
apiUrl.split('?')[1].split('&').forEach(item => {
const [key, value] = item.split('=');
const decoded = decodeURIComponent(value);
const test = decoded.match(/%(.*)%/);
if (test !== null) {
const targetField = jQuery('#' + formPrefix + test[1]);
let newValue = '';
if (targetField.length === 0) {
// happens for example:
// - in duration only mode, when the end field is not found
// console.log('ERROR: Cannot find field with name "' + test[1] + '" by selector: #' + formPrefix + test[1]);
} else {
if (targetField.val() !== null) {
newValue = targetField.val();
if (newValue !== '') {
// having that special case here is far from being perfect... but for now it works ;-)
if (targetField.data('daterangepicker') !== undefined) {
if (key === 'begin' || key === 'start' || targetField.data('daterangepicker').singleDatePicker) {
newValue = targetField.data('daterangepicker').startDate.format(moment.HTML5_FMT.DATETIME_LOCAL_SECONDS);
} else if (key === 'end') {
newValue = targetField.data('daterangepicker').endDate.format(moment.HTML5_FMT.DATETIME_LOCAL_SECONDS);
}
} else if (targetField.data('format') !== undefined) {
if (moment(newValue, targetField.data('format')).isValid()) {
newValue = moment(newValue, targetField.data('format')).format(moment.HTML5_FMT.DATETIME_LOCAL_SECONDS);
}
}
} else {
// happens for example:
// - when the end date is not set on a timesheet record and the project list is loaded (as the URL contains the %end% replacer)
// console.log('Empty value found for field with name "' + test[1] + '" by selector: #' + formPrefix + test[1]);
}
} else {
// happens for example:
// - when a customer without projects is selected
// console.log('ERROR: Empty field with name "' + test[1] + '" by selector: #' + formPrefix + test[1]);
}
}
if (Array.isArray(newValue)) {
newValue = newValue.join(',');
}
newApiUrl = newApiUrl.replace(value, newValue);
}
});
return newApiUrl;
}
_updateSelect(selectName, data) {
let select = jQuery(selectName);
let emptyOption = jQuery(selectName + ' option[value=""]');
select.find('option').remove().end().find('optgroup').remove().end();
if (emptyOption.length !== 0) {
select.append('<option value="">' + emptyOption.text() + '</option>');
}
const options = {};
for (const apiData of data) {
let title = apiData.parentTitle;
if (title === null) {
title = '__empty__';
let title = '__empty__';
if (apiData.hasOwnProperty('parentTitle') && apiData.parentTitle !== null) {
title = apiData.parentTitle;
}
if (!options.hasOwnProperty(title)) {
options[title] = [];
@@ -84,32 +142,9 @@ export default class KimaiSelectDataAPI extends KimaiPlugin {
ordered[key] = options[key];
});
let htmlOptions = '';
let emptyOptions = '';
for (const [key, value] of Object.entries(ordered)) {
if (key === '__empty__') {
for (const entity of value) {
emptyOptions += '<option value="' + entity.id + '">' + entity.name + '</option>';
}
continue;
}
htmlOptions += '<optgroup label="' + key + '">';
for (const entity of value) {
htmlOptions += '<option value="' + entity.id + '">' + entity.name + '</option>';
}
htmlOptions += '</optgroup>';
}
select.append(htmlOptions);
select.append(emptyOptions);
// if we don't trigger the change, the other selects won't be resetted
select.trigger('change');
// if the beta test kimai.theme.select_type is active, this will tell the selects to refresh
jQuery('.selectpicker').selectpicker('refresh');
/** @var {KimaiFormSelect} select */
const select = this.getContainer().getPlugin('form-select');
select.updateOptions(selectName, ordered);
}
}

View File

@@ -15,7 +15,60 @@ import KimaiPlugin from '../KimaiPlugin';
export default class KimaiThemeInitializer extends KimaiPlugin {
init() {
this.registerGlobalAjaxErrorHandler();
this.registerAutomaticAlertRemove('div.alert-success', 5000);
// activate the dropdown functionality
jQuery('.dropdown-toggle').dropdown();
// activate the tooltip functionality
jQuery('[data-toggle="tooltip"]').tooltip();
// activate all form plugins
this.getContainer().getPlugin('form').activateForm('.content-wrapper form', 'body');
this.getContainer().getPlugin('form').activateForm('form.searchform', 'body');
this.registerModalAutofocus('#modal_search');
this.registerModalAutofocus('#remote_form_modal');
}
/**
* workaround for autofocus attribute, as the modal "steals" it
*
* @param {string} selector
*/
registerModalAutofocus(selector) {
let modal = jQuery(selector);
if (modal.length === 0) {
return;
}
modal.on('shown.bs.modal', function () {
let form = modal.find('form');
let formAutofocus = form.find('[autofocus]');
if (formAutofocus.length < 1) {
formAutofocus = form.find('input[type=text],textarea,select');
}
formAutofocus.filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
});
}
/**
* redirect access denied / session timeouts to login page
*/
registerGlobalAjaxErrorHandler() {
const loginUrl = this.getConfiguration('login');
const alert = this.getContainer().getPlugin('alert');
const translation = this.getContainer().getTranslation().get('login.required');
jQuery(document).ajaxError(function(event, jqxhr, settings, thrownError) {
if (jqxhr.status !== undefined && jqxhr.status === 403) {
const loginRequired = jqxhr.getResponseHeader('login-required');
if (loginRequired !== null) {
alert.question(translation, function (result) {
if (result === true) {
window.location.replace(loginUrl);
}
});
}
}
});
}
/**

View File

@@ -14,12 +14,131 @@ import KimaiPlugin from "../KimaiPlugin";
export default class KimaiToolbar extends KimaiPlugin {
init() {
const datatable = this.getContainer().getPlugin('datatable');
constructor(formSelector, formSubmitActionClass) {
super();
this.formSelector = formSelector;
this.actionClass = formSubmitActionClass;
}
// This catches all clicks on the pagination and prevents the default action, as we want to relad the page via JS
getId() {
return 'toolbar';
}
init() {
const formSelector = this.getSelector();
const self = this;
const EVENT = self.getContainer().getPlugin('event');
this._registerPagination(formSelector, EVENT);
this._registerSortableTables(formSelector, EVENT);
this._registerAlternativeSubmitActions(formSelector, this.actionClass);
// Reset the page if filter values are changed, otherwise we might end up with a limited set of data,
// which does not support the given page - and it would be just wrong to stay in the same page
jQuery(formSelector +' input').change(function (event) {
switch (event.target.id) {
case 'order':
case 'orderBy':
case 'page':
break;
default:
jQuery(formSelector + ' input#page').val(1);
break;
}
self.triggerChange();
});
// when user selected a new customer or project, reset the pagination back to 1
// and then find out if the results should be reloaded
jQuery(formSelector + ' select').change(function (event) {
let reload = true;
switch (event.target.id) {
case 'customer':
if (jQuery(formSelector + ' select#project').length > 0) {
reload = false;
}
break;
case 'project':
if (jQuery(formSelector + ' select#activity').length > 0) {
reload = false;
}
break;
}
jQuery(formSelector + ' input#page').val(1);
if (reload) {
self.triggerChange();
}
});
}
/**
* Some actions utilize the filter from the search form and submit it to another URL.
* @private
*/
_registerAlternativeSubmitActions(toolbarSelector, actionBtnClass) {
document.addEventListener('click', function(event) {
let target = event.target;
while (target !== null && !target.matches('body')) {
if (target.classList.contains(actionBtnClass)) {
const form = document.querySelector(toolbarSelector);
if (form === null) {
return;
}
const prevAction = form.action;
const prevMethod = form.method;
form.target = '_blank';
form.action = target.href;
if (target.dataset.method !== undefined) {
form.method = target.dataset.method;
}
form.submit();
form.target = '';
form.action = prevAction;
form.method = prevMethod;
event.preventDefault();
event.stopPropagation();
}
target = target.parentNode;
}
});
}
/**
* Sortable datatables use hidden fields in the toolbar filter/search form
* @private
*/
_registerSortableTables(formSelector, EVENT) {
jQuery('body').on('click', 'th.sortable', function(event){
var $header = jQuery(event.target);
var order = 'DESC';
var orderBy = $header.data('order');
if ($header.hasClass('sorting_desc')) {
order = 'ASC';
}
jQuery(formSelector + ' #orderBy').val(orderBy);
jQuery(formSelector + ' #order').val(order);
// re-render the selectboxes
jQuery(formSelector + ' #orderBy').trigger('change');
jQuery(formSelector + ' #order').trigger('change');
// triggers the datatable reload - search for the event name
EVENT.trigger('filter-change');
});
}
/**
* This catches all clicks on the pagination and prevents the default action, as we want to reload the page via JS
* @private
*/
_registerPagination(formSelector, EVENT) {
jQuery('body').on('click', 'div.navigation ul.pagination li a', function(event) {
let pager = jQuery(".toolbar form input[name='page']");
let pager = jQuery(formSelector + " input#page");
if (pager.length === 0) {
return;
}
@@ -29,41 +148,26 @@ export default class KimaiToolbar extends KimaiPlugin {
let page = urlParts[urlParts.length-1];
pager.val(page);
pager.trigger('change');
EVENT.trigger('pagination-change');
return false;
});
// Reset the page if any other value is changed, otherwise we might end up with a limited set
// of data which does not support the given page - and it would be just wrong to stay in the same page
jQuery('.toolbar form input').change(function (event) {
switch (event.target.id) {
case 'page':
break;
default:
jQuery('.toolbar form input#page').val(1);
}
datatable.reloadDatatable();
});
}
/**
* Triggers an event, that everyone can listen for.
*/
triggerChange() {
this.getContainer().getPlugin('event').trigger('toolbar-change');
}
jQuery('.toolbar form select').change(function (event) {
let reload = true;
switch (event.target.id) {
case 'customer':
if (jQuery('.toolbar form select#project').length > 0) {
reload = false;
}
break;
case 'project':
if (jQuery('.toolbar form select#activity').length > 0) {
reload = false;
}
break;
}
jQuery('.toolbar form input#page').val(1);
if (reload) {
datatable.reloadDatatable();
}
});
/**
* Returns the CSS selector to target the toolbar form.
*
* @returns {string}
*/
getSelector() {
return this.formSelector;
}
}

View File

@@ -1,56 +0,0 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import KimaiPlugin from '../KimaiPlugin';
/**
* Needs to be initialized with a class name.
*
* A link like <a href=# class=remoteLink> can be activated with:
* new KimaiToolbarAction('remoteLink')
*
* @param selector
*/
export default class KimaiToolbarAction extends KimaiPlugin {
constructor(selector) {
super();
this.selector = selector;
}
init() {
const self = this;
document.addEventListener('click', function(event) {
let target = event.target;
while (target !== null && !target.matches('body')) {
if (target.classList.contains(self.selector)) {
const form = document.querySelector('div.toolbar form.navbar-form');
if (form === null) {
return;
}
const prevAction = form.action;
const prevMethod = form.method;
form.target = '_blank';
form.action = target.href;
if (target.dataset.method !== undefined) {
form.method = target.dataset.method;
}
form.submit();
form.target = '';
form.action = prevAction;
form.method = prevMethod;
event.preventDefault();
event.stopPropagation();
}
target = target.parentNode;
}
});
}
}

View File

@@ -0,0 +1,28 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiCookies: simple wrapper to handle cookies
*/
import Cookies from 'js-cookie';
export default class KimaiCookies {
static set(name, values, options) {
Cookies.set(name, values, options);
}
static get(name) {
return Cookies.getJSON(name);
}
static remove(name) {
Cookies.remove(name);
}
}

View File

@@ -0,0 +1,97 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiPaginatedBoxWidget: handles box widgets that have a pagination
*/
import jQuery from "jquery";
export default class KimaiPaginatedBoxWidget {
constructor(boxId) {
this.selector = boxId;
this.overlay = jQuery('<div class="overlay"><div class="fas fa-sync fa-spin"></div></div>');
this.widget = jQuery(this.selector);
this.href = this.widget.data('href');
this.events = this.widget.data('reload').split(' ');
const self = this;
const reloadPage = function (event) {
const page = jQuery(self.selector + ' .box-tools').data('page');
const url = self._buildUrl(page);
self.loadPage(url);
};
for (const eventName of this.events) {
document.addEventListener(eventName, reloadPage);
}
this.widget.on('click', '.box-tools ul.pagination a', function (event) {
event.preventDefault();
self.loadPage(jQuery(event.currentTarget).attr('href'));
});
}
static create(elementId) {
return new KimaiPaginatedBoxWidget(elementId);
}
_showOverlay() {
this.widget.append(this.overlay);
}
_hideOverlay() {
jQuery(this.overlay).remove();
}
loadPage(url) {
const self = this;
const selector = this.selector;
self._showOverlay();
jQuery.ajax({
url: url,
data: {},
success: function (response) {
const html = jQuery(response);
jQuery(selector + ' .box-tools').replaceWith(html.find('.box-tools'));
jQuery(selector + ' .box-body').replaceWith(html.find('.box-body'));
jQuery(selector + ' .box-title').replaceWith(html.find('.box-title'));
if (jQuery(selector + ' .box-footer').length > 0) {
jQuery(selector + ' .box-footer').replaceWith(html.find('.box-footer'));
}
jQuery(selector + ' .box-body [data-toggle="tooltip"]').tooltip();
self.widget.removeData('error-retry');
self._hideOverlay();
},
dataType: 'html',
error: function(jqXHR, textStatus, errorThrown) {
if (jqXHR.status !== undefined && jqXHR.status === 500) {
if (self.widget.data('error-retry') !== undefined) {
// TODO show error message ?
return;
}
const page = jQuery(selector + ' .box-tools').data('page');
if (page > 1) {
self.widget.data('error-retry', 1);
var url = self._buildUrl(page - 1);
self.loadPage(url);
}
}
self._hideOverlay();
}
});
}
_buildUrl(page) {
return this.href.replace('1', page);
}
}

View File

@@ -0,0 +1,73 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiReloadPageWidget: a simple helper to reload the page on events
*/
import jQuery from "jquery";
export default class KimaiReloadPageWidget {
constructor(events, fullReload) {
this.overlay = jQuery('<div class="overlay-wrapper"><div class="overlay"><div class="fas fa-sync fa-spin"></div></div></div>');
this.widget = jQuery('div.content-wrapper');
const self = this;
const reloadPage = function (event) {
if (fullReload) {
document.location.reload(true);
} else {
self.loadPage(document.location);
}
};
for (const eventName of events.split(' ')) {
document.addEventListener(eventName, reloadPage);
}
}
static create(events, fullReload) {
if (fullReload === undefined || fullReload === null) {
fullReload = false;
}
return new KimaiReloadPageWidget(events, fullReload);
}
_showOverlay() {
this.widget.append(this.overlay);
}
_hideOverlay() {
jQuery(this.overlay).remove();
}
loadPage(url) {
const self = this;
self._showOverlay();
jQuery.ajax({
url: url,
data: {},
success: function (response) {
jQuery('section.content').replaceWith(
jQuery(response).find('section.content')
);
document.dispatchEvent(new Event('kimai.reloadPage'));
self._hideOverlay();
},
dataType: 'html',
error: function(jqXHR, textStatus, errorThrown) {
self._hideOverlay();
document.location = url;
}
});
}
}

View File

@@ -0,0 +1,30 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiStorage: simple wrapper to handle localStorage access
*/
export default class KimaiStorage {
static set(name, values) {
window.localStorage.setItem(name, JSON.stringify(values));
}
static get(name) {
let value = window.localStorage.getItem(name);
if (value === undefined || value === null) {
return null;
}
return JSON.parse(value);
}
static remove(name) {
window.localStorage.removeItem(name);
}
}

View File

@@ -5,15 +5,16 @@
* file that was distributed with this source code.
*/
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
@import 'variables';
@import 'layout';
@import 'error-page';
@import 'print';
@import 'content';
@import 'box';
@import 'content-header';
@import 'toolbar';
@import 'sidebar';
@import 'footer';
@import 'invoice';
@import 'datatable';
@import 'datatable-extensions';
@import 'calendar';
@@ -23,3 +24,11 @@
@import 'skins';
@import 'sweetalert';
@import 'autocomplete';
@import 'selectpicker';
@import 'reporting';
@import 'forms';
@import 'modal';
@import 'progressbar';
@import 'avatar';
@import 'security';
@import 'tabs';

60
assets/sass/avatar.scss Normal file
View File

@@ -0,0 +1,60 @@
$avatar-base-size: 30;
.avatar {
text-align: center;
background-color: #d2d6de;
border-radius: 50%;
display: inline-block;
height: ($avatar-base-size)+px;
width: ($avatar-base-size)+px;
vertical-align: middle;
}
.initials {
line-height: 1;
position: relative;
font-weight: bold;
font-size: ceil($avatar-base-size / 2 - 2)+px;
top: floor($avatar-base-size / 4 - 1)+px;
}
$avatarSizes: "xs" .75, "sm" 1.25, "md" 1.5, "lg" 2;
@each $avatarSizeName, $avatarBaseSize in $avatarSizes {
.avatar-#{$avatarSizeName} {
width: ceil($avatar-base-size * $avatarBaseSize)+px;
height: ceil($avatar-base-size * $avatarBaseSize)+px;
.initials {
font-size: ceil($avatar-base-size * $avatarBaseSize / 2 - 2)+px;
top: ceil($avatar-base-size * $avatarBaseSize / 4 - 1)+px;
}
}
}
.widget-user-image {
border-radius: 50%;
border: 3px solid #fff;
.avatar {
width: ceil($avatar-base-size * 2.75)+px;
height: ceil($avatar-base-size * 2.75)+px;
.initials {
font-size: ceil($avatar-base-size * 2.75 / 2 - 2)+px;
top: ceil($avatar-base-size * 2.75 / 4 - 1)+px;
}
}
}
/* Detail tables like my teams (Dashboard) or */
table.dataTable.table > tbody > tr > td {
&.avatars {
.avatar {
margin: 1px;
}
}
}
.avatar.teamlead {
box-shadow: 0px 0px 5px 1px rgba(0,0,0,.4),0 0px 3px 0 rgba(0,0,0,0.3);
}

9
assets/sass/bootstrap.scss vendored Normal file
View File

@@ -0,0 +1,9 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
@import '~bootstrap-sass/assets/stylesheets/bootstrap';

22
assets/sass/box.scss Normal file
View File

@@ -0,0 +1,22 @@
.box {
/* used on detail pages for: customer, project and activity */
.box-body.no-padding div.comment,
.box-body.no-padding .box-padding {
padding: 10px;
}
.box-tools {
/* a button that is directly connected with a pagination element inside the box tools (eg. project list on customer detail page) */
.btn-pager {
float: left;
margin-right: 5px;
}
}
}
#customer_details_box,
#project_details_box,
#activity_details_box {
th {
width: 30%;
}
}

View File

@@ -6,3 +6,46 @@
}
}
}
/* Drag and Drop events with styling */
.external-events {
.external-event {
vertical-align: middle;
padding: 0 !important;
display: inline-block;
position: relative;
width: 100%;
}
.subtitle {
padding: 0 7px 4px 7px;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.7em;
}
.title {
padding: 3px 7px 0;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.fc-bootstrap3 {
.table-bordered > tbody > tr > td,
.table-bordered > thead > tr > td,
.table-bordered > thead > tr > th {
border: 1px solid #ddd;
}
.fc-head {
background-color: #fafafa;
}
td.fc-today {
background-color: #fcf8e3!important;
}
}

View File

@@ -0,0 +1,65 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
.content-header {
height: 50px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
background-color: #fff !important;
padding: 10px 0 0 10px;
h1 {
padding-top: 3px;
float: left;
small {
display: none;
}
}
}
/* Page based action buttons in the upper right corner of the content area */
.content-header>.breadcrumb {
position: absolute;
float: right;
background: transparent;
top: 0;
right: 0;
padding-left: 10px;
.btn {
span.label {
position: absolute;
top: 1px;
right: 1px;
text-align: center;
font-size: 9px;
padding: 2px 3px;
}
}
}
@media (max-width: $screen-md-max) {
.content-header h1 {
font-size: 1.5em;
}
.content-header>.breadcrumb {
margin-top: 0;
}
}
@media (min-width: $screen-sm-min) {
.content-header>.breadcrumb {
right: 10px;
}
.content-header {
h1 {
small {
display: inline-block;
}
}
}
}

View File

@@ -8,48 +8,19 @@
.content {
padding: 15px 0;
}
.content-header {
border-top: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
background-color: #fff !important;
padding: 15px 0 15px 10px;
h1 {
small {
display: none;
}
}
}
.content-header>.breadcrumb {
position: relative;
margin-top: 5px;
top: 0;
right: 0;
float: none;
background: #d2d6de;
padding-left: 10px;
}
@media (min-width: $screen-sm-min) {
.content-header>.breadcrumb {
right: 10px;
}
}
@media (min-width: $screen-sm-min) {
.content-header {
h1 {
small {
display: inline-block;
}
}
}
.content {
padding: 15px;
}
}
@media (min-width: $screen-lg-min) {
.content {
padding: 20px;
}
}
td {
&.timesheet-description {
ul {
@@ -69,4 +40,39 @@ td {
text-align: center;
line-height: 35px;
margin-right: 15px;
}
.label-gray {
background-color: $gray-lte;
color: #666;
}
/* Delete link in action dropdowns */
.dropdown-menu > li.delete > a {
color: #dd4b39;
}
.open-edit {
cursor: pointer;
}
/* allow minimized table columns, eg. customer team & visibility */
th.w-min,
td.w-min {
width: 1%;
white-space: nowrap;
}
table.dataTable thead > tr > th.hw-min {
width: 1%;
white-space: normal;
}
.badge {
font-weight: normal;
color: #000;
background-color: $kimai-default;
}
.label {
font-weight: normal;
}

View File

@@ -13,4 +13,8 @@ section {
}
}
}
.box-body-scrollable {
overflow: auto;
max-height: 300px;
}
}

View File

@@ -5,35 +5,61 @@
* file that was distributed with this source code.
*/
@import "variables";
table.dataTable.table > tbody > tr > td {
vertical-align: middle;
.img-circle {
width: 30px;
height: 30px;
margin-right: 0;
&.teamlead {
box-shadow: 0 0 5px 3px $gray-lte;
}
}
}
span.label-color,
span.label-meta,
span.label-tag,
span.label-user,
span.label-activity,
span.label-project,
span.label-customer {
display: inline-block;
.name {
white-space: nowrap;
.name-inner {
white-space: normal;
}
}
.dot {
height: 10px;
width: 10px;
background-color: $gray-lte;
border-radius: 50%;
display: inline-block;
font-size: 9px;
margin-right: 3px;
color: $gray-lte;
}
}
table.dataTable {
/* action column */
.actions {
width: 90px;
li.delete a {
color: #dd4b39;
}
width: 40px;
}
tr {
/* summary row - provided in the users own timesheet table */
&.summary td {
font-weight: bold;
border-bottom: 1px solid #ccc;
background-color: #eaeaea;
}
&.open-edit {
cursor: pointer;
th.multiCheckbox {
width: 15px;
}
&.overlapping {
border-top: 2px dotted red;
}
&.exported {
opacity: 0.7;
}
}
td {
@@ -46,8 +72,8 @@ table.dataTable {
}
.label {
font-size: 85%;
font-weight: 500;
padding: .3em .6em .3em;
line-height: 24px;
}
span.label-activity,
span.label-project,
@@ -65,4 +91,45 @@ table.dataTable {
opacity: 0.8;
}
}
tfoot {
tr {
/* overwritten if used in tfoot, to switch the border from below to top */
&.summary td {
border-top: 1px solid #ccc;
border-bottom: 0;
}
}
}
th.weekend,
td.weekend {
background-color: #f9f9f9;
}
/* order is important, "today” should overwrite "weekend" therefor later in the file */
th.today,
td.today {
background-color: $highlight-today;
}
th.total,
td.total {
font-weight: bold;
}
}
/* Quick entry form */
.form-dataTable {
table.dataTable {
.form-group {
margin-bottom: 0;
}
}
}
table.table-hover {
tr {
&.exported {
&:hover {
opacity: 1.0;
}
}
}
}

View File

@@ -23,7 +23,7 @@ section.content .overlay > .fas,
section.content > .overlay,
.box > .loading-img,
.overlay-wrapper > .loading-img {
position: absolute;
position: fixed; /* see #1330 */
top: 0;
left: 0;
width: 100%;
@@ -125,46 +125,45 @@ table.dataTable thead .sorting_desc_disabled {
position: relative;
}
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc {
font-weight: bold;
}
table.dataTable thead .sorting:after,
table.dataTable thead .sorting_asc:after,
table.dataTable thead .sorting_desc:after {
position: absolute;
top: 8px;
right: 8px;
display: block;
font-family: 'Glyphicons Halflings';
padding-left: 5px;
font-family: 'Font Awesome 5 Free';
opacity: 0.5;
font-size: 12px;
}
table.dataTable thead .sorting:after {
opacity: 0.2;
content: "\e150"; /* sort */
content: "\f0dc"; /* sort */
}
table.dataTable thead .sorting_asc:after {
content: "\e155"; /* sort-by-attributes */
content: "\f062";
}
table.dataTable thead .sorting_desc:after {
content: "\e156"; /* sort-by-attributes-alt */
content: "\f063";
}
div.dataTables_scrollBody table.dataTable thead .sorting:after,
div.dataTables_scrollBody table.dataTable thead .sorting_asc:after,
div.dataTables_scrollBody table.dataTable thead .sorting_desc:after {
div.dataTables_scrollBody table.dataTable thead .sorting:before,
div.dataTables_scrollBody table.dataTable thead .sorting_asc:before,
div.dataTables_scrollBody table.dataTable thead .sorting_desc:before {
display: none;
}
table.dataTable thead .sorting_asc_disabled:after,
table.dataTable thead .sorting_desc_disabled:after {
table.dataTable thead .sorting_asc_disabled:before,
table.dataTable thead .sorting_desc_disabled:before {
color: #eee;
}
table.dataTable thead > tr > th {
vertical-align: top;
white-space: nowrap;
}
/*
table.dataTable thead > tr > th {
padding-right: 30px;
}
*/
table.dataTable th:active {
outline: none;
}
@@ -175,9 +174,9 @@ table.dataTable.table-condensed thead > tr > th {
padding-right: 20px;
}
table.dataTable.table-condensed thead .sorting:after,
table.dataTable.table-condensed thead .sorting_asc:after,
table.dataTable.table-condensed thead .sorting_desc:after {
table.dataTable.table-condensed thead .sorting:before,
table.dataTable.table-condensed thead .sorting_asc:before,
table.dataTable.table-condensed thead .sorting_desc:before {
top: 6px;
right: 6px;
}

View File

@@ -1,3 +1,4 @@
@import '~daterangepicker/daterangepicker.css';
.drp-calendar {
.calendar-table {

12
assets/sass/fontawesome.scss vendored Normal file
View File

@@ -0,0 +1,12 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts/";
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import '~@fortawesome/fontawesome-free/scss/regular';
@import '~@fortawesome/fontawesome-free/scss/solid';
@import '~@fortawesome/fontawesome-free/scss/brands';

60
assets/sass/forms.scss Normal file
View File

@@ -0,0 +1,60 @@
/* Currently only used in batch-update form for timesheets, to highlight sections of combined fields */
form .box-body fieldset {
border-radius: $box-border-radius;
padding: 10px;
&:hover {
background-color: $highlight-bg;
}
}
/* To be used with .form-horizontal - to reduce the space between rows, eg. used in the search modals */
form.form-narrow {
.form-group {
margin: 0 0 5px 0;
}
}
/* bootstrap3 hack because the filter look plain ugly without it (see report: project month) */
.checkbox-menu li label {
display: block;
padding: 5px 15px 5px 10px !important;
clear: both;
font-weight: normal;
line-height: 1.42857143;
color: #333;
white-space: nowrap;
margin:0;
transition: background-color .4s ease;
}
.checkbox-menu li div.checkbox {
display: block;
width: 100%;
}
.checkbox-menu li div.radio {
display: block;
width: 100%;
}
.checkbox-menu li input {
margin: 0 5px !important;
position: relative;
}
.checkbox-menu li.active label {
background-color: #cbcbff;
font-weight:bold;
}
.checkbox-menu li label:hover,
.checkbox-menu li label:focus {
background-color: #f5f5f5;
}
.checkbox-menu li.active label:hover,
.checkbox-menu li.active label:focus {
background-color: #b8b8ff;
}
.duration-input {
min-width:50px;
padding: 6px;
}

View File

@@ -0,0 +1,119 @@
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
body {
font-family: sans-serif;
font-size: 10pt;
margin: 0;
padding: 0;
}
table, tr, td, th, p, h1, h2, h3, h4, h5 {
padding: 0;
margin: 0;
}
th {
border: none;
text-align: left;
}
td {
vertical-align: top;
}
p {
margin-bottom: 12px;
}
.text-small {
font-size: 7pt;
}
.padding-left {
padding-left: 15px;
}
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.text-nowrap {
white-space: nowrap;
}
.date {
color: #666;
font-size: 80%;
text-align: right;
padding-top: 6px;
}
.wrapper {
margin: 4px;
}
table {
width: 100%;
border-spacing: 0;
}
table.addresses, p {
line-height: 14pt;
}
/* The address table */
table.addresses {
margin-bottom: 30px;
}
/* The PDF header */
table.header {
padding-bottom: 4px;
border-bottom: 1px solid #999;
}
table.header .title {
font-weight: normal;
font-size: 140%;
}
/* The PDF footer */
table.footer {
border-top: 1px solid #999;
font-size: 80%;
padding-top: 8px;
line-height: 14px;
}
table.footer td {
vertical-align: top;
}
/* The invoice items list */
table.items {
border-collapse: collapse;
margin-top: 50px;
margin-bottom: 60px;
}
table.items thead th,
table.items tbody td {
border-bottom: 1px solid #ccc;
}
table.items thead th.first,
table.items tbody td.first {
padding-left: 0;
}
table.items thead th.last,
table.items tbody td.last,
table.items tfoot td.last {
padding-right: 0;
}
table.items td,
table.items th {
padding: 8px 6px;
}
table.items tfoot td,
table.items tfoot th {
padding: 20px 0 0 0;
line-height: 1px;
}
table.items tfoot td.last {
padding-left: 10px;
}
table.items tr.odd {
background-color: #f5f5f5;
}

View File

@@ -5,301 +5,301 @@
* file that was distributed with this source code.
*/
.invoice {
margin: 0;
border: none;
}
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/print";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/scaffolding";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/type";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/grid";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/tables";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/list-group";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/panels";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/wells";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/utilities";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities";
@media (min-width: $screen-sm-min) {
.invoice {
margin: 25px;
}
}
body {
font-family: $font-family-sans-serif;
&.invoice_print {
background-color: #eee;
.page-header-small{
font-size: 14px;
}
div.invoice-address {
margin-top: 30px;
margin-bottom: 30px;
}
table.invoice-records {}
table.invoice-meta th {
padding-right: 40px;
}
table.invoice-sum th {
width: 70%;
}
table.invoice-sum th, table.invoice-sum td {
text-align: right;
}
/* ================================ FREELANCER INVOICE ================================ */
#freelancer-invoice {
font: 15px/1 $font-family-sans-serif;
box-sizing: border-box;
h1 {
font: bold 100% sans-serif;
letter-spacing: 0.5em;
text-align: center;
text-transform: uppercase;
}
h2 {
font: bold 1.5em sans-serif;
margin-bottom: 1em;
}
/* table */
table {
table-layout: fixed;
width: 100%;
}
table {
border-collapse: separate;
border-spacing: 0;
}
/* header */
header {
}
header:after {
clear: both;
content: "";
display: table;
font-size: 1em;
}
header address {
float: left;
font-style: normal;
line-height: 1.25;
margin: 0 1em 1em 0;
font-weight: bold;
font-size: 0.7em;
}
article address {
font-size: 1em;
float: left;
margin: 1em 0;
}
article.address {
margin-bottom: 1.5em;
}
article.footer {
margin-top: 60px;
}
/* article */
article, table.meta, table.inventory {
margin: 1em 0;
}
article:after {
clear: both;
content: "";
display: table;
}
article h1 {
clip: rect(0 0 0 0);
position: absolute;
}
article p {
margin-bottom: 20px;
}
/* table meta & balance */
table.meta:after, table.balance:after {
clear: both;
content: "";
display: table;
}
/* table meta */
table.meta {
float: right;
width: 50%;
font-size: 85%;
th {
text-align: right;
font-weight: normal;
.table.no-border, .table.no-border td, .table.no-border th {
border: 0;
}
td {
width: 120px;
text-align: right;
}
}
/* table balance */
table.balance {
float: right;
th {
text-align: right;
font-weight: normal;
h1, h2, h3 {
font-family: $font-family-sans-serif;
}
td {
width: 140px;
.invoice {
margin: 105px auto 30px auto;
padding: 50px 65px;
min-height: 297mm;
position: relative;
max-width: 210mm;
box-shadow: 0 0 20px rgba(80, 80, 80, 0.7);
background: #fff;
border: none;
}
.page-header {
margin: 10px 0 20px 0;
font-size: 22px;
> small {
color: #666;
display: block;
margin-top: 5px;
}
h3 {
border-color: #ddd;
}
}
div.invoice-address {
margin-top: 30px;
margin-bottom: 30px;
}
table.invoice-meta th {
padding-right: 40px;
}
table.invoice-sum th {
width: 70%;
}
table.invoice-sum th, table.invoice-sum td {
text-align: right;
}
.total {
font-weight: bold;
}
}
.invoice-items {
margin-top: 2em;
margin-bottom: 3em;
/* table items */
.table {
thead th {
font-weight: bold;
border-bottom: 1px solid #ddd;
padding-bottom: 15px
}
table.inventory {
clear: both;
width: 100%;
margin-top: 2em;
border-bottom: 1px solid #000;
}
tfoot {
border-top: 1px solid #ddd;
table.inventory th {
font-weight: bold;
border-width: 0 0 1px 0;
border-bottom: 1px solid #000;
}
table.inventory th,
table.inventory td {
padding: 5px;
}
tr:first-child th,
tr:first-child td {
padding-top: 15px
}
table.inventory td:nth-child(1),
table.inventory th:nth-child(1) {
text-align: left;
}
tr {
th, td {
border: none;
padding: 4px 8px;
}
}
}
}
table.inventory td:nth-child(2),
table.inventory th:nth-child(2) {
text-align: center;
width: 20%;
}
table.inventory td:nth-child(3),
table.inventory th:nth-child(3) {
text-align: center;
width: 20%;
}
table.inventory td:nth-child(4),
table.inventory th:nth-child(4) {
text-align: right;
width: 20%;
}
/* footer */
footer {
margin-top: 50px;
}
footer table {
border-top: 1px solid #000;
font-size: 0.8em;
padding-top: 5px;
}
footer th {
text-align: left;
font-weight: bold;
margin-bottom: 10px;
}
footer table th:nth-child(1) {
width: 32%;
}
footer table th:nth-child(3) {
width: 28%;
}
footer td {
text-align: left;
vertical-align: bottom;
}
/* aside */
aside {
position: absolute;
top: 10in;
width: 7.5in;
}
aside h1 {
border: none;
border-width: 0 0 1px;
margin: 0 0 1em;
}
aside h1 {
border-color: #999;
border-bottom-style: solid;
}
aside p {
font-size: 85%;
}
aside div {
margin-top: 15px;
}
aside div p {
line-height: 1.4;
}
aside p span {
width: 130px;
font-weight: bold;
display: inline-block;
}
}
@media print {
* {
-webkit-print-color-adjust: exact;
}
body, .wrapper, #freelancer-invoice {
margin: 0;
padding: 0;
}
#freelancer-invoice {
margin: 1em 2em;
span:empty {
display: none;
}
.add, .cut {
display: none;
.footer {
border-top: 1px solid #ccc;
margin-top: 50px;
font-size: 0.8em;
padding-top: 10px;
width: 100%;
}
/* ================================ FREELANCER INVOICE ================================ */
#freelancer-invoice {
box-sizing: border-box;
h1 {
font: bold 100% sans-serif;
letter-spacing: 0.5em;
text-align: center;
text-transform: uppercase;
}
h2 {
font: bold 1.5em sans-serif;
margin-bottom: 1em;
}
/* table */
table {
width: 100%;
}
table {
border-collapse: separate;
border-spacing: 0;
}
/* header */
header {
&:after {
clear: both;
content: "";
display: table;
font-size: 1em;
}
address {
float: left;
font-style: normal;
line-height: 1.25;
margin: 0 1em 1em 0;
font-size: 0.7em;
}
}
article address {
font-size: 1em;
float: left;
}
article.address {
margin: 1em 0 1.5em 0;
}
/* article */
article:after {
clear: both;
content: "";
display: table;
}
article h1 {
clip: rect(0 0 0 0);
position: absolute;
}
article p {
margin-bottom: 20px;
}
/* table meta & balance */
table.meta:after, table.balance:after {
clear: both;
content: "";
display: table;
}
/* table meta */
table.meta {
float: right;
width: 50%;
font-size: 85%;
th {
text-align: right;
font-weight: normal;
}
td {
width: 120px;
text-align: right;
}
}
/* table balance */
table.balance {
float: right;
margin-top: 1em;
th {
text-align: right;
font-weight: normal;
}
td {
width: 140px;
text-align: right;
line-height: 22px;
}
.total {
font-weight: bold;
}
}
/* table items */
table.inventory {
clear: both;
width: 100%;
margin-top: 2em;
border-bottom: 1px solid #000;
}
table.inventory th,
table.inventory td {
padding: 5px;
}
table.inventory thead th {
font-weight: bold;
border-width: 0 0 1px 0;
border-bottom: 1px solid #000;
padding-bottom: 15px
}
table.inventory tbody tr:first-child td {
padding-top: 15px
}
table.inventory tbody tr:last-child td {
padding-bottom: 15px
}
.footer {
border-color: #000;
}
}
/* ================================ PRINTING RULES ================================ */
@media print {
* {
-webkit-print-color-adjust: exact;
}
body, .wrapper {
margin: 0;
padding: 0;
background-color: white;
}
#freelancer-invoice {
span:empty {
display: none;
}
.add, .cut {
display: none;
}
}
.invoice {
margin: 1em 2em;
font-size: 90%;
width: unset;
padding: 0;
min-height: unset;
position: unset;
max-width: unset;
box-shadow: unset;
}
}
}
.invoice {
width: unset;
}
}
}

8
assets/sass/layout.scss Normal file
View File

@@ -0,0 +1,8 @@
@media (max-width: $screen-sm-max) {
.main-header .logo {
display: none;
}
.fixed .content-wrapper, .fixed .right-side, .control-sidebar, .main-sidebar {
padding-top: 50px;
}
}

9
assets/sass/modal.scss Normal file
View File

@@ -0,0 +1,9 @@
.modal-header,
.modal-footer {
padding-top: 10px;
padding-bottom: 10px;
}
.modal-content {
border-radius: 3px;
box-shadow: 0 10px 80px rgba(0, 0, 0, 0.6);
}

View File

@@ -5,10 +5,30 @@
* file that was distributed with this source code.
*/
.navbar-custom-menu {
.navbar-nav {
li>.ddt-small {
padding-top: 10px;
padding-bottom: 10px;
}
}
}
.navbar-nav {
li>a.ddt-large {
padding: 15px 12px 11px 10px;
font-size: 10px;
.avatar {
max-height: 30px;
max-width: 30px;
height: 30px;
width: 30px;
margin-top: 0;
.initials {
top: 5px;
left: 0;
}
}
li>.ddt-small {
padding-top: 10px;
padding-bottom: 10px;
}
li.messages-menu ul.menu li {
a:hover .pull-left i {
@@ -31,17 +51,13 @@
width: unset !important;
}
.user-menu {
margin-right: 10px;
ul {
li {
a {
color: #444;
&:hover {
background-color: #f4f4f4;
i {
color: $blue;
}
}
color: #444;
i {
padding-right: 10px;
}
@@ -52,41 +68,86 @@
}
}
}
.notifications-menu>.dropdown-menu>li .menu>li>a:hover i {
color: $green;
}
@keyframes ticktac-blink {
0% { opacity: 1; }
5% { opacity: 0.95; }
10% { opacity: 0.9; }
15% { opacity: 0.85; }
20% { opacity: 0.8; }
25% { opacity: 0.75; }
30% { opacity: 0.7; }
35% { opacity: 0.65; }
40% { opacity: 0.6; }
45% { opacity: 0.65; }
50% { opacity: 0.7; }
55% { opacity: 0.75; }
60% { opacity: 0.8; }
65% { opacity: 0.85; }
70% { opacity: 0.9; }
75% { opacity: 0.95; }
100% { opacity: 1; }
}
.ticktac-single {
display: block;
position: relative;
padding-left: 15px;
padding-right: 15px;
i {
animation: ticktac-blink 2s step-end infinite;
}
span {
font-size: 1.8em;
line-height: 1em;
padding-left: 5px;
padding-right: 0;
}
&:hover {
background: rgba(0,0,0,.3);
}
}
body.fixed {
.navbar-nav {
.user-menu {
margin-right: 10px;
}
}
}
@media (max-width: $screen-sm-min) {
@media (max-width: $screen-sm-max) {
.navbar-custom-menu>.navbar-nav>li>.dropdown-menu {
right: 0;
}
.navbar-nav .user-menu {
margin-right: 0;
body.fixed {
.navbar-nav {
.user-menu {
margin-right: 0;
}
}
}
.navbar-nav .user-menu.open .dropdown-menu > li > a {
padding: 5px 25px 5px 25px
}
}
/*
.ticktac:hover i.running{
color: #4ff131;
@media (max-width: $screen-sm-max) {
.ticktac-single {
padding-left: 5px;
padding-right: 5px;
}
.navbar-custom-menu .navbar-nav li>.ddt-small {
padding-left: 5px;
padding-right: 5px;
}
}
li.open .ticktac i.running{
color: #4ff131;
@media (max-width: $screen-md-max) {
.ticktac-single {
padding-left: 10px;
padding-right: 10px;
}
.navbar-custom-menu .navbar-nav li>.ddt-small {
padding-left: 10px;
padding-right: 10px;
}
}
.ticktac i.stopped{
color: #fff;
}
.ticktac:hover i.stopped{
color: #4ff131;
}
.user-panel>.info {
left: 40px;
padding-top: 0px;
}
*/

View File

@@ -6,5 +6,21 @@
*/
@media print{
.sf-toolbar, .control-sidebar {display: none !important;}
.sf-toolbar, .control-sidebar {
display: none !important;
}
.col-print-12{
width:100%;
}
.sidebar-mini.sidebar-collapse .content-wrapper, .sidebar-mini.sidebar-collapse .main-footer, .sidebar-mini.sidebar-collapse .right-side {
margin: 0!important;
}
section.content {
margin: 0;
padding: 0;
}
span i.dot {
display: none;
margin-right: 0!important;
}
}

View File

@@ -0,0 +1,20 @@
/*
make sure that the progressbar title does not wrap and does not need to be embedded in a container with white-space: nowrap
used for example in the project-overview report
*/
.progress-group {
.progress-title {
width: 100%;
white-space: nowrap;
.progress-text {
display: inline-block;
width: 1%;
}
.progress-number {
display: inline-block;
width: 98%;
text-align: right;
float: none;
}
}
}

View File

@@ -0,0 +1,7 @@
.form-reporting .box-header .form-group {
margin-right: 10px;
label {
font-size: $font-size-base;
}
}

35
assets/sass/security.scss Normal file
View File

@@ -0,0 +1,35 @@
body.login-page {
.alert {
margin-bottom: 0;
}
.login-box-body {
text-align: center;
padding: 30px 35px;
box-shadow: 0 10px 30px 0 rgba(172, 168, 168, 0.43);
.btn {
padding: 7px;
font-size: 115%;
}
}
.login-head {
margin-top: 0;
}
.login-actions a {
display: block;
}
.form-group {
input[type=password], input[type=text] {
padding: 20px 15px;
}
}
span.glyphicon {
display: none;
}
}
@media (max-width: $screen-sm-max) {
body.login-page {
.login-box-body {
padding: 20px 27px;
}
}
}

View File

@@ -0,0 +1,29 @@
@import '~select2/dist/css/select2.min.css';
@import '~select2-bootstrap-theme/dist/select2-bootstrap.min.css';
/* some labels were placed incorrect due to a margin-top: -4px */
.select2-container .select2-selection--single .select2-selection__rendered {
margin-top: 0;
}
/* make sure the border looks like all other form elements */
.select2-container--bootstrap .select2-selection {
border-radius: 0;
}
/* placeholder in multi-selects were cut off */
.select2-search.select2-search--inline .select2-search__field {
width: 100% !important;
}
/* make sure that <optgroup> elements are padded, to allow visual difference to global activities */
.select2-container--bootstrap .select2-results__options.select2-results__options--nested li.select2-results__option {
padding-left: 30px;
}
/* empty <option> were too small to be noticed as clickable element */
.select2-container--bootstrap .select2-results__option {
min-height: 30px;
}
/* Fixes the size on quick-entry form */
.select2-container--bootstrap {
display: table;
table-layout: fixed;
}

View File

@@ -5,28 +5,6 @@
* file that was distributed with this source code.
*/
/* UserPanel is deactivated right now */
/*
.user-panel>.info {
left: 80px;
}
*/
/* Right sidebar with additional tabs for personal settings and "About Kimai" section */
/*
.control-sidebar {
select {
color: $input-color;
background-color: $input-bg;
}
div.image {
img.img-circle {
max-width: 34px;
}
}
}
*/
@media (min-width: $screen-sm-min) {
.sidebar-mini.sidebar-collapse .sidebar-menu li.header {
border-top-width: 1px;

View File

@@ -1,26 +1,69 @@
$skins: blue, blue, black, green, purple, red, yellow;
$skins:
"blue" $yellow #fff $yellow $red,
"black" $yellow #000 $green $red,
"green" $yellow #fff $yellow $red,
"purple" $yellow #fff $yellow $red,
"red" $yellow #fff $yellow $yellow,
"yellow" $blue #fff $green $red
;
@each $skinColor in $skins {
@media (max-width: $screen-sm-min) {
.skin-#{$skinColor} .main-header .navbar .dropdown-menu li.divider {
background-color: #ccc;
}
.skin-#{$skinColor} .main-header .navbar .dropdown-menu li a {
color: #444;
}
.skin-#{$skinColor} .main-header .navbar .dropdown-menu li a:hover {
background: #fff;
}
@each $skinColor, $navHighlight, $tickTacColor, $startButton, $stopButton in $skins {
@each $prefix in (".skin-#{$skinColor}", ".skin-#{$skinColor}-light") {
@media (max-width: $screen-sm-max) {
#{$prefix} {
.main-header .navbar .dropdown-menu li.divider {
background-color: #ccc;
}
.skin-#{$skinColor}-light .main-header .navbar .dropdown-menu li.divider {
background-color: #ccc;
.main-header .navbar .dropdown-menu li a {
color: #444;
}
.main-header .navbar .dropdown-menu li a:hover {
background: #fff;
}
}
}
.skin-#{$skinColor}-light .main-header .navbar .dropdown-menu li a {
color: #444;
}
.skin-#{$skinColor}-light .main-header .navbar .dropdown-menu li a:hover {
background: #fff;
#{$prefix} {
.navbar-nav {
.user-menu {
ul {
li {
a {
&:hover {
i {
color: $navHighlight;
}
}
}
}
}
}
.notifications-menu > .dropdown-menu > li .menu > li > a:hover i {
color: $navHighlight;
}
}
.ticktac-single {
i {
color: $tickTacColor;
}
span {
color: $tickTacColor;
}
&:hover {
.ticktac-stop {
i {
color: $stopButton;
}
}
.ticktac-start {
i {
color: $startButton;
}
}
}
}
}
}
}

View File

@@ -1,3 +1,16 @@
.swal2-popup {
font-size: unset;
}
}
body.swal2-toast-shown .swal2-container {
// never block the top two bars double-bars display (smaller displays)
@media only screen and (max-width: 768px) {
top: 95px !important;
width: 80%;
}
// never block the top bar on single-bar display (larger devices)
@media only screen and (min-width: 768px) {
top: 60px !important;
}
}

3
assets/sass/tabs.scss Normal file
View File

@@ -0,0 +1,3 @@
.tab-content {
margin-top: 20px;
}

View File

@@ -5,20 +5,9 @@
* file that was distributed with this source code.
*/
/* Page based action buttons in the upper right corner of the content area */
.content-header > .breadcrumb {
position: absolute;
float: right;
background: transparent;
}
.toolbar-pad {
padding: 10px;
}
/* The filter form is available on most pages above the datatable */
@media (min-width: $screen-sm-min) {
.toolbar {
// @deprecated since 1.2 - using the navbar-form is deprecated and will be removed with 2.0
form.navbar-form {
font-size: $font-size-base;
.form-control {
@@ -34,8 +23,4 @@
}
}
}
.toolbar-pad {
padding: 10px 0;
}
}

View File

@@ -5,8 +5,14 @@
* file that was distributed with this source code.
*/
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
/* For highlighting rows, fieldsets and so on */
$highlight-bg: #f5f5f5;
$highlight-today: #fcf8e3;
/**
* A simple one to one clone of /node_modules/admin-lte/build/less/variables.less
* BELOW: A simple one to one clone of /node_modules/admin-lte/build/less/variables.less
*/
// COLORS
@@ -20,7 +26,7 @@ $green: #00a65a;
// Info
//$aqua: #00c0ef;
// Warning
//$yellow: #f39c12;
$yellow: #f0ad4e;
$blue: #0073b7;
//$navy: #001F3F;
//$teal: #39CCCC;
@@ -32,6 +38,7 @@ $blue: #0073b7;
//$maroon: #D81B60;
//$black: #111;
$gray-lte: #d2d6de;
$kimai-default: $gray-lte;
// LAYOUT
// --------------------------------------------------------

View File

@@ -2,38 +2,28 @@
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use App\ConsoleApplication;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Debug\Debug;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\Dotenv\Dotenv;
set_time_limit(0);
require __DIR__.'/../vendor/autoload.php';
require dirname(__DIR__) . '/vendor/autoload.php';
if (!class_exists(Application::class)) {
throw new \RuntimeException('You need to add "symfony/framework-bundle" as a Composer dependency.');
}
if (!isset($_SERVER['APP_ENV'])) {
if (!class_exists(Dotenv::class)) {
throw new \RuntimeException('APP_ENV environment variable is not defined. You need to define environment variables for configuration or add "symfony/dotenv" as a Composer dependency to load variables from a .env file.');
}
(new Dotenv(true))->load(__DIR__.'/../.env');
}
// do NOT rely on calls to getenv() as it is not thread safe
(new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env');
$input = new ArgvInput();
$env = $input->getParameterOption(['--env', '-e'], $_SERVER['APP_ENV'] ?? 'dev');
$debug = ($_SERVER['APP_DEBUG'] ?? ('prod' !== $env)) && !$input->hasParameterOption(['--no-debug', '']);
$env = $input->getParameterOption(['--env', '-e'], $_SERVER['APP_ENV'] ?? 'prod');
$debug = (bool) ($_SERVER['APP_DEBUG'] ?? (in_array($env, ['dev', 'test']))) && !$input->hasParameterOption(['--no-debug', '']);
if ($debug) {
umask(0000);
if (class_exists(Debug::class)) {
Debug::enable();
}
Debug::enable();
}
$kernel = new Kernel($env, $debug);
$application = new Application($kernel);
$application = new ConsoleApplication($kernel);
$application->run($input);

View File

@@ -2,7 +2,7 @@
"name": "kevinpapst/kimai2",
"license": "MIT",
"type": "project",
"description": "Kimai Time-Tracking 2",
"description": "Kimai - Time Tracking",
"authors": [
{
"name": "Kevin Papst",
@@ -10,79 +10,108 @@
}
],
"require": {
"php": "^7.2",
"php": "7.3.*||7.4.*||8.0.*",
"ext-gd": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pdo": "*",
"ext-xsl": "*",
"ext-zip": "*",
"beberlei/doctrineextensions": "^1.2",
"composer/package-versions-deprecated": "^1.8",
"doctrine/dbal": "^2.1",
"doctrine/doctrine-bundle": "^2.0",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.8",
"erusev/parsedown": "^1.6",
"friendsofsymfony/rest-bundle": "^2.5",
"friendsofsymfony/user-bundle": "dev-master",
"gedmo/doctrine-extensions": "^2.4",
"friendsofsymfony/rest-bundle": "^3.0",
"gedmo/doctrine-extensions": "^3.0",
"handcraftedinthealps/rest-routing-bundle": "^1.0",
"jms/metadata": "^2.0",
"jms/serializer-bundle": "^3.2",
"kevinpapst/adminlte-bundle": "~2.6",
"kimai/kimai2-composer": "^0.1",
"mpdf/mpdf": "^7.1",
"jms/serializer-bundle": "^3.9",
"kevinpapst/adminlte-bundle": "^3.5",
"league/csv": "^9.4",
"league/html-to-markdown": "^5.0",
"mpdf/mpdf": "^8.0",
"nelmio/api-doc-bundle": "^3.2",
"nelmio/cors-bundle": "^1.5",
"ocramius/proxy-manager": "2.1.1",
"phpoffice/phpspreadsheet": "^1.4",
"phpoffice/phpword": "^0.15.0",
"nelmio/cors-bundle": "^2.0",
"onelogin/php-saml": "^3.4",
"pagerfanta/pagerfanta": "^2.1",
"phpoffice/phpspreadsheet": "^1.16",
"phpoffice/phpword": "^0.18",
"psr/log": "^1.1",
"sensio/framework-extra-bundle": "^5.2",
"symfony/asset": "^4.0",
"symfony/console": "^4.0",
"symfony/debug-pack": "^1.0",
"symfony/dotenv": "^4.0",
"symfony/expression-language": "^4.0",
"sensio/framework-extra-bundle": "^6.0",
"symfony/amazon-mailer": "^4.4",
"symfony/asset": "^4.4",
"symfony/console": "^4.4",
"symfony/dotenv": "^4.4",
"symfony/expression-language": "^4.4",
"symfony/flex": "^1.0",
"symfony/form": "^4.0",
"symfony/framework-bundle": "^4.0",
"symfony/intl": "^4.0",
"symfony/form": "^4.4",
"symfony/framework-bundle": "^4.4",
"symfony/google-mailer": "^4.4",
"symfony/http-client": "^4.4",
"symfony/intl": "^4.4",
"symfony/mailchimp-mailer": "^4.4",
"symfony/mailer": "^4.4",
"symfony/mailgun-mailer": "^4.4",
"symfony/monolog-bundle": "^3.4",
"symfony/orm-pack": "^1.0",
"symfony/security-bundle": "^4.0",
"symfony/security-csrf": "^4.0",
"symfony/serializer": "^4.0",
"symfony/swiftmailer-bundle": "^3.2",
"symfony/translation": "^4.0",
"symfony/twig-bundle": "^4.0",
"symfony/validator": "^4.0",
"symfony/postmark-mailer": "^4.4",
"symfony/security-bundle": "^4.4",
"symfony/security-csrf": "^4.4",
"symfony/sendgrid-mailer": "^4.4",
"symfony/serializer": "^4.4",
"symfony/translation": "^4.4",
"symfony/twig-bundle": "^4.4",
"symfony/uid": "^5.0",
"symfony/validator": "^4.4",
"symfony/webpack-encore-bundle": "^1.5",
"symfony/yaml": "^4.0",
"twig/extensions": "^1.5",
"white-october/pagerfanta-bundle": "^1.1"
"symfony/yaml": "^4.4",
"twig/cssinliner-extra": "^3.0",
"twig/extra-bundle": "^3.0",
"twig/inky-extra": "^3.0",
"twig/intl-extra": "^3.0",
"twig/string-extra": "^3.0"
},
"require-dev": {
"dama/doctrine-test-bundle": "^5.0",
"dama/doctrine-test-bundle": "^6.0",
"doctrine/doctrine-fixtures-bundle": "^3.2",
"friendsofphp/php-cs-fixer": "^2.10",
"fzaninotto/faker": "^1.8",
"phpstan/phpstan": "^0.11.7",
"phpstan/phpstan-doctrine": "^0.11.4",
"phpstan/phpstan-phpunit": "^0.11.2",
"phpstan/phpstan-symfony": "^0.11.6",
"phpunit/phpunit": "^7.0",
"symfony/browser-kit": "^4.0",
"symfony/css-selector": "^4.0",
"fakerphp/faker": "^1.15",
"friendsofphp/php-cs-fixer": "^3.2",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-doctrine": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-symfony": "^1.0",
"phpunit/phpunit": "^9.0",
"symfony/browser-kit": "^4.4",
"symfony/css-selector": "^4.4",
"symfony/debug-bundle": "^4.4",
"symfony/dom-crawler": "4.4.4",
"symfony/maker-bundle": "^1.12",
"symfony/phpunit-bridge": "^4.0",
"symfony/profiler-pack": "^1.0",
"symfony/thanks": "^1.0",
"symfony/web-server-bundle": "~4.2.0"
"symfony/phpunit-bridge": "^4.4",
"symfony/stopwatch": "^4.4",
"symfony/var-dumper": "^4.4",
"symfony/web-profiler-bundle": "^4.4"
},
"repositories": [
{
"type": "artifact",
"url": "var/packages/"
}
],
"config": {
"platform": {
"php": "7.2"
"php": "7.3"
},
"preferred-install": {
"*": "dist"
},
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"composer/package-versions-deprecated": true,
"symfony/flex": true
}
},
"autoload": {
"psr-4": {
@@ -96,14 +125,18 @@
}
},
"replace": {
"symfony/polyfill-mbstring": "*",
"symfony/polyfill-intl": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
"symfony/polyfill-php56": "*",
"symfony/polyfill-php55": "*",
"symfony/polyfill-php54": "*"
},
"suggest": {
"ext-mbstring": "If ext-mbstring is not available you MUST install symfony/polyfill-mbstring",
"zendframework/zend-ldap": "For LDAP authentication with Kimai"
"laminas/laminas-ldap": "For LDAP authentication with Kimai"
},
"scripts": {
"auto-scripts": {
@@ -116,20 +149,39 @@
"post-update-cmd": [
"@auto-scripts"
],
"kimai:code-check": [
"kimai:pre-commit": [
"@kimai:codestyle",
"@kimai:phpstan",
"@kimai:tests"
"@linting",
"@kimai:tests-unit"
],
"kimai:tests": "vendor/bin/phpunit tests/",
"kimai:code-check": "@code-check",
"code-check": [
"@kimai:pre-commit",
"@kimai:tests-integration"
],
"kimai:code-lint": "@linting",
"linting": [
"composer validate --strict",
"bin/console lint:container",
"bin/console lint:yaml config --parse-tags",
"bin/console lint:twig templates --show-deprecations",
"bin/console lint:xliff translations",
"bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction"
],
"tests": "vendor/bin/phpunit tests/",
"kimai:tests": "@tests",
"kimai:tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
"kimai:tests-integration": "vendor/bin/phpunit --group integration tests/",
"kimai:phpstan": [
"vendor/bin/phpstan analyse src -c phpstan.neon --level=3",
"vendor/bin/phpstan analyse tests -c tests/phpstan.neon --level=4"
"kimai:phpstan": "@phpstan",
"phpstan": [
"vendor/bin/phpstan analyse src -c phpstan.neon --level=5",
"vendor/bin/phpstan analyse tests -c tests/phpstan.neon --level=5"
],
"kimai:codestyle": "vendor/bin/php-cs-fixer fix --dry-run --verbose --show-progress=none",
"kimai:codestyle-fix": "vendor/bin/php-cs-fixer fix"
"kimai:codestyle": "@codestyle",
"codestyle": "vendor/bin/php-cs-fixer fix --dry-run --verbose --show-progress=none",
"kimai:codestyle-fix": "@codestyle-fix",
"codestyle-fix": "vendor/bin/php-cs-fixer fix"
},
"conflict": {
"symfony/symfony": "*"
@@ -138,7 +190,7 @@
"symfony": {
"id": "01C3FWRDJJEX9K6Y3A4XDFXPBR",
"allow-contrib": true,
"require": "4.3.*"
"require": "4.4.*"
}
}
}

12864
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,6 @@
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],
Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
@@ -12,16 +10,15 @@ return [
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
KevinPapst\AdminLTEBundle\AdminLTEBundle::class => ['all' => true],
JMS\SerializerBundle\JMSSerializerBundle::class => ['all' => true],
FOS\UserBundle\FOSUserBundle::class => ['all' => true],
FOS\RestBundle\FOSRestBundle::class => ['all' => true],
Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
HandcraftedInTheAlps\RestRoutingBundle\RestRoutingBundle::class => ['all' => true],
];

View File

@@ -7,7 +7,7 @@ admin_lte:
# default image for missing user avatar images
default_avatar: build/images/default_avatar.png
# the color skin
skin: skin-green
skin: skin-blue
# if activated, the boxed_layout will be deactivated
fixed_layout: true
# boxed layout (true) or full-screen (false)

View File

@@ -1,3 +1,6 @@
framework:
assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
packages:
avatars:
base_path: 'avatars'

View File

@@ -1,16 +0,0 @@
services:
EasyCorp\EasyLog\EasyLogHandler:
public: false
arguments: ['%kernel.logs_dir%/%kernel.environment%.log']
#// FIXME: How to add this configuration automatically without messing up with the monolog configuration?
#monolog:
# handlers:
# buffered:
# type: buffer
# handler: easylog
# channels: ['!event']
# level: debug
# easylog:
# type: service
# id: EasyCorp\EasyLog\EasyLogHandler

View File

@@ -1,7 +0,0 @@
jms_serializer:
visitors:
json_serialization:
options:
- JSON_PRETTY_PRINT
- JSON_UNESCAPED_SLASHES
- JSON_PRESERVE_ZERO_FRACTION

Some files were not shown because too many files have changed in this diff Show More