" );
@@ -314,6 +321,27 @@ function() {
html: days.join( "" )
}));
+ $(".cost").focusout(function(e) {
+ e.preventDefault();
+ var input = $(this);
+ var cost = $(this).val();
+ var id = $(e.target).data('myid');
+ var baseUrl = OC.generateUrl('/apps/timetracker/ajax/add-cost/' + id);
+ if (cost !== undefined) {
+ $.post(baseUrl, {cost: cost}, 'json').done(function (e) {
+ input.css('border', 'solid 1px green');
+ setTimeout(function () {
+ input.css('border', '');
+ }, 3000);
+ }).fail(function (xhr, status, error) {
+ var errorMessage = JSON.parse(xhr.responseText);
+ if (errorMessage.error !== undefined) {
+ alert(errorMessage.error);
+ }
+ input.css('border', 'solid 1px red');
+ });
+ }
+ });
$(".wi-child-hours").each(function(){
$(this).daterangepicker({
@@ -348,7 +376,7 @@ function() {
$('.wi-child-name').click(function(e) {
e.preventDefault();
dialogWorkItemEditForm.target = e.target;
-
+
var form = dialogWorkItemEditForm.find( "form" )
form.find("#name").val($(e.target).data("name"));
form.find("#details").val($(e.target).data("details"));
@@ -358,10 +386,13 @@ function() {
})
$('.wi-play').click(function(e) {
e.preventDefault();
- $('#work-input').val($(this).data('work-name'));
- startTimer($(this).data('projectid'), $(this).data('tagids'));
- return false;
- })
+ createWorkItem($(this));
+ });
+
+ $('.wi-resume').click(function(e) {
+ e.preventDefault();
+ resumeWorkItem($(this));
+ });
$('.wi-trash').click(function(e) {
$("#dialog-confirm").dialog({
buttons : {
@@ -425,11 +456,11 @@ function() {
},
ajax: {
url: projectsAjaxUrl,
-
+
dataType: 'json',
delay: 250,
processResults: function (data) { //json parse
- return {
+ return {
results: $.map(data.Projects,function(val, i){
return { id: val.id, text:val.name, color: val.color};
}),
@@ -439,35 +470,35 @@ function() {
};
},
cache: false,
-
+
},
});
$(this).data('myid',id);
-
+
$(this).val(projectId).trigger('change');
//clearInterval(interval);
//}.bind(this),0);
});
-
+
var tagsAjaxUrl = OC.generateUrl('/apps/timetracker/ajax/tags');
$(".set-tag").each(function(){
//var interval = setInterval( function() {
-
+
$(this).select2({
tags: true,
containerCssClass:'tags-select',
placeholder: "Select tags...",
allowClear: true,
-
- ajax: {
+
+ ajax: {
url: function () { return tagsAjaxUrl+'?workItem='+$(this).data('myid');},
data: function (params){
var query = {
q: params.term,
type: 'public'
}
-
+
// Query parameters will be ?search=[term]&type=public
return query;
},
@@ -478,7 +509,7 @@ function() {
dataType: 'json',
delay: 250,
processResults: function (data) { //json parse
- return {
+ return {
results: $.map(data.Tags,function(val, i){
return { id: val.id, text:val.name};
}),
@@ -501,8 +532,8 @@ function() {
var myid = $(e.target).data('myid');
var selectedId = $(e.target).val();
var jqxhr = $.post( "ajax/update-work-interval/"+myid,{projectId:selectedId}, function() {
-
-
+
+
})
.done(function(data, status, jqXHR) {
var response = data;
@@ -524,7 +555,7 @@ function() {
}, 0);
});
- $(".set-tag").on("change", function (e) {
+ $(".set-tag").on("change", function (e) {
var myid = $(e.target).data('myid');
var selectedTag = $(e.target).val();
var jqxhr = $.post( "ajax/update-work-interval/"+myid,{tagId:selectedTag.join(",")}, function() {
@@ -553,19 +584,31 @@ function() {
});
}
- function startTimer(projectId = null, tags = ""){
+ function createWorkItem(wiPlay) {
+ var workName = $('#work-input').val();
+ $('#work-input').val(wiPlay.data('work-name'));
+ startTimer(wiPlay.data('projectid'), wiPlay.data('tagids'), workName);
+ return false;
+ }
+
+ function resumeWorkItem(wiResume) {
+ startTimer(wiResume.data('projectid'), wiResume.data('tagids'), wiResume.data('work-name'));
+ return false;
+ }
+
+ function startTimer(projectId = null, tags = "", inputWorkName = null){
if(localStorage.getItem('isTimerStarted') === 'true'){
- stopTimer(startTimer, [projectId, tags]);
+ stopTimer(startTimer, [projectId, tags, inputWorkName]);
return;
}
- var baseUrl = OC.generateUrl('/apps/timetracker/ajax/start-timer');
- var workName = $('#work-input').val();
+ var workName = inputWorkName ?? $('#work-input').val();
if (workName == ''){
workName = 'no description';
}
- var jqxhr = $.post( "ajax/start-timer/"+encodeURIComponent(encodeURIComponent(workName)), { projectId: projectId, tags: tags}, function() {
+ var baseUrl = OC.generateUrl('/apps/timetracker/ajax/start-timer/'+encodeURIComponent(encodeURIComponent(workName)));
+ var jqxhr = $.post(baseUrl, { projectId: projectId, tags: tags}, function() {
})
- .done(function(data, status, jqXHR) {
+ .done(function(data, status, jqxhr) {
var response = data;
if ('Error' in response){
alert(response.Error);
@@ -579,15 +622,16 @@ function() {
}).always(function() {
getWorkItems();
});
-
+
}
function stopTimer(onStopped = null, args = []){
-
+
var workName = $('#work-input').val();
if (workName == ''){
workName = 'no description';
}
- var jqxhr = $.post( "ajax/stop-timer/"+encodeURIComponent(encodeURIComponent(workName)), function() { // encode twice so we can pass / character
+ var baseUrl = OC.generateUrl('/apps/timetracker/ajax/stop-timer/'+encodeURIComponent(encodeURIComponent(workName)));
+ var jqxhr = $.post(baseUrl, function() { // encode twice so we can pass / character
})
.done(function(data, status, jqXHR) {
var response = data;
@@ -597,7 +641,7 @@ function() {
localStorage.setItem('isTimerStarted', false);
$('#start-tracking > span').addClass("play-button").removeClass("stop-button");
if (onStopped != null){
- onStopped(args[0], args[1]);
+ onStopped(...args);
} else {
getWorkItems();
}
@@ -607,7 +651,7 @@ function() {
alert( "error" );
})
.always(function() {
-
+
});
}
@@ -615,7 +659,7 @@ function() {
$( "#datepicker-from" ).datepicker();
$( "#datepicker-to" ).datepicker();
-
+
if(localStorage.getItem('isTimerStarted') === 'true'){
$('#start-tracking > span').addClass("stop-button").removeClass("play-button");
} else {
@@ -630,5 +674,5 @@ function() {
return false;
});
} );
-
+
}());
diff --git a/lib/AppFramework/Db/OldNextcloudMapper.php b/lib/AppFramework/Db/OldNextcloudMapper.php
new file mode 100644
index 0000000..ee45fb5
--- /dev/null
+++ b/lib/AppFramework/Db/OldNextcloudMapper.php
@@ -0,0 +1,373 @@
+
+ * @author Christoph Wurst
+ * @author Joas Schilling
+ * @author Lukas Reschke
+ * @author Morris Jobke
+ * @author Roeland Jago Douma
+ * @author Thomas Müller
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see
+ *
+ */
+namespace OCA\TimeTracker\AppFramework\Db;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\Entity;
+use OCP\AppFramework\Db\MultipleObjectsReturnedException;
+use OCP\IDBConnection;
+
+/**
+ * Simple parent class for inheriting your data access layer from. This class
+ * may be subject to change in the future
+ * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+abstract class OldNextcloudMapper {
+ protected $tableName;
+ protected $entityClass;
+ protected $db;
+
+ /**
+ * @param IDBConnection $db Instance of the Db abstraction layer
+ * @param string $tableName the name of the table. set this to allow entity
+ * @param string $entityClass the name of the entity that the sql should be
+ * mapped to queries without using sql
+ * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ public function __construct(IDBConnection $db, $tableName, $entityClass = null) {
+ $this->db = $db;
+ $this->tableName = '*PREFIX*' . $tableName;
+
+ // if not given set the entity name to the class without the mapper part
+ // cache it here for later use since reflection is slow
+ if ($entityClass === null) {
+ $this->entityClass = str_replace('Mapper', '', get_class($this));
+ } else {
+ $this->entityClass = $entityClass;
+ }
+ }
+
+
+ /**
+ * @return string the table name
+ * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ public function getTableName() {
+ return $this->tableName;
+ }
+
+
+ /**
+ * Deletes an entity from the table
+ * @param Entity $entity the entity that should be deleted
+ * @return Entity the deleted entity
+ * @since 7.0.0 - return value added in 8.1.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ public function delete(Entity $entity) {
+ $sql = 'DELETE FROM `' . $this->tableName . '` WHERE `id` = ?';
+ $stmt = $this->execute($sql, [$entity->getId()]);
+ $stmt->closeCursor();
+ return $entity;
+ }
+
+
+ /**
+ * Creates a new entry in the db from an entity
+ * @param Entity $entity the entity that should be created
+ * @return Entity the saved entity with the set id
+ * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ public function insert(Entity $entity) {
+ // get updated fields to save, fields have to be set using a setter to
+ // be saved
+ $properties = $entity->getUpdatedFields();
+ $values = '';
+ $columns = '';
+ $params = [];
+
+ // build the fields
+ $i = 0;
+ foreach ($properties as $property => $updated) {
+ $column = $entity->propertyToColumn($property);
+ $getter = 'get' . ucfirst($property);
+
+ $columns .= '`' . $column . '`';
+ $values .= '?';
+
+ // only append colon if there are more entries
+ if ($i < count($properties) - 1) {
+ $columns .= ',';
+ $values .= ',';
+ }
+
+ $params[] = $entity->$getter();
+ $i++;
+ }
+
+ $sql = 'INSERT INTO `' . $this->tableName . '`(' .
+ $columns . ') VALUES(' . $values . ')';
+
+ $stmt = $this->execute($sql, $params);
+
+ $entity->setId((int) $this->db->lastInsertId($this->tableName));
+
+ $stmt->closeCursor();
+
+ return $entity;
+ }
+
+
+
+ /**
+ * Updates an entry in the db from an entity
+ * @throws \InvalidArgumentException if entity has no id
+ * @param Entity $entity the entity that should be created
+ * @return Entity the saved entity with the set id
+ * @since 7.0.0 - return value was added in 8.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ public function update(Entity $entity) {
+ // if entity wasn't changed it makes no sense to run a db query
+ $properties = $entity->getUpdatedFields();
+ if (count($properties) === 0) {
+ return $entity;
+ }
+
+ // entity needs an id
+ $id = $entity->getId();
+ if ($id === null) {
+ throw new \InvalidArgumentException(
+ 'Entity which should be updated has no id');
+ }
+
+ // get updated fields to save, fields have to be set using a setter to
+ // be saved
+ // do not update the id field
+ unset($properties['id']);
+
+ $columns = '';
+ $params = [];
+
+ // build the fields
+ $i = 0;
+ foreach ($properties as $property => $updated) {
+ $column = $entity->propertyToColumn($property);
+ $getter = 'get' . ucfirst($property);
+
+ $columns .= '`' . $column . '` = ?';
+
+ // only append colon if there are more entries
+ if ($i < count($properties) - 1) {
+ $columns .= ',';
+ }
+
+ $params[] = $entity->$getter();
+ $i++;
+ }
+
+ $sql = 'UPDATE `' . $this->tableName . '` SET ' .
+ $columns . ' WHERE `id` = ?';
+ $params[] = $id;
+
+ $stmt = $this->execute($sql, $params);
+ $stmt->closeCursor();
+
+ return $entity;
+ }
+
+ /**
+ * Checks if an array is associative
+ * @param array $array
+ * @return bool true if associative
+ * @since 8.1.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ private function isAssocArray(array $array) {
+ return array_values($array) !== $array;
+ }
+
+ /**
+ * Returns the correct PDO constant based on the value type
+ * @param $value
+ * @return int PDO constant
+ * @since 8.1.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ private function getPDOType($value) {
+ switch (gettype($value)) {
+ case 'integer':
+ return \PDO::PARAM_INT;
+ case 'boolean':
+ return \PDO::PARAM_BOOL;
+ default:
+ return \PDO::PARAM_STR;
+ }
+ }
+
+
+ /**
+ * Runs an sql query
+ * @param string $sql the prepare string
+ * @param array $params the params which should replace the ? in the sql query
+ * @param int $limit the maximum number of rows
+ * @param int $offset from which row we want to start
+ * @return \PDOStatement the database query result
+ * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ protected function execute($sql, array $params = [], $limit = null, $offset = null) {
+ $query = $this->db->prepare($sql, $limit, $offset);
+
+ if ($this->isAssocArray($params)) {
+ foreach ($params as $key => $param) {
+ $pdoConstant = $this->getPDOType($param);
+ $query->bindValue($key, $param, $pdoConstant);
+ }
+ } else {
+ $index = 1; // bindParam is 1 indexed
+ foreach ($params as $param) {
+ $pdoConstant = $this->getPDOType($param);
+ $query->bindValue($index, $param, $pdoConstant);
+ $index++;
+ }
+ }
+
+ $query->execute();
+
+ return $query;
+ }
+
+ /**
+ * Returns an db result and throws exceptions when there are more or less
+ * results
+ * @see findEntity
+ * @param string $sql the sql query
+ * @param array $params the parameters of the sql query
+ * @param int $limit the maximum number of rows
+ * @param int $offset from which row we want to start
+ * @throws DoesNotExistException if the item does not exist
+ * @throws MultipleObjectsReturnedException if more than one item exist
+ * @return array the result as row
+ * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ protected function findOneQuery($sql, array $params = [], $limit = null, $offset = null) {
+ $stmt = $this->execute($sql, $params, $limit, $offset);
+ $row = $stmt->fetch();
+
+ if ($row === false || $row === null) {
+ $stmt->closeCursor();
+ $msg = $this->buildDebugMessage(
+ 'Did expect one result but found none when executing', $sql, $params, $limit, $offset
+ );
+ throw new DoesNotExistException($msg);
+ }
+ $row2 = $stmt->fetch();
+ $stmt->closeCursor();
+ //MDB2 returns null, PDO and doctrine false when no row is available
+ if (! ($row2 === false || $row2 === null)) {
+ $msg = $this->buildDebugMessage(
+ 'Did not expect more than one result when executing', $sql, $params, $limit, $offset
+ );
+ throw new MultipleObjectsReturnedException($msg);
+ } else {
+ return $row;
+ }
+ }
+
+ /**
+ * Builds an error message by prepending the $msg to an error message which
+ * has the parameters
+ * @see findEntity
+ * @param string $sql the sql query
+ * @param array $params the parameters of the sql query
+ * @param int $limit the maximum number of rows
+ * @param int $offset from which row we want to start
+ * @return string formatted error message string
+ * @since 9.1.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ private function buildDebugMessage($msg, $sql, array $params = [], $limit = null, $offset = null) {
+ return $msg .
+ ': query "' . $sql . '"; ' .
+ 'parameters ' . print_r($params, true) . '; ' .
+ 'limit "' . $limit . '"; '.
+ 'offset "' . $offset . '"';
+ }
+
+
+ /**
+ * Creates an entity from a row. Automatically determines the entity class
+ * from the current mapper name (MyEntityMapper -> MyEntity)
+ * @param array $row the row which should be converted to an entity
+ * @return Entity the entity
+ * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ protected function mapRowToEntity($row) {
+ return call_user_func($this->entityClass .'::fromRow', $row);
+ }
+
+
+ /**
+ * Runs a sql query and returns an array of entities
+ * @param string $sql the prepare string
+ * @param array $params the params which should replace the ? in the sql query
+ * @param int $limit the maximum number of rows
+ * @param int $offset from which row we want to start
+ * @return array all fetched entities
+ * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ protected function findEntities($sql, array $params = [], $limit = null, $offset = null) {
+ $stmt = $this->execute($sql, $params, $limit, $offset);
+
+ $entities = [];
+
+ while ($row = $stmt->fetch()) {
+ $entities[] = $this->mapRowToEntity($row);
+ }
+
+ $stmt->closeCursor();
+
+ return $entities;
+ }
+
+
+ /**
+ * Returns an db result and throws exceptions when there are more or less
+ * results
+ * @param string $sql the sql query
+ * @param array $params the parameters of the sql query
+ * @param int $limit the maximum number of rows
+ * @param int $offset from which row we want to start
+ * @throws DoesNotExistException if the item does not exist
+ * @throws MultipleObjectsReturnedException if more than one item exist
+ * @return Entity the entity
+ * @since 7.0.0
+ * @deprecated 14.0.0 Move over to QBMapper
+ */
+ protected function findEntity($sql, array $params = [], $limit = null, $offset = null) {
+ return $this->mapRowToEntity($this->findOneQuery($sql, $params, $limit, $offset));
+ }
+}
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index b91ee9e..de801d7 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -25,6 +25,15 @@ class Application extends App {
*/
public function __construct(array $urlParams=array()){
parent::__construct('timetracker', $urlParams);
+
+ if (!\class_exists('\OCA\TimeTracker\AppFramework\Db\CompatibleMapper')) {
+ if (\class_exists(\OCP\AppFramework\Db\Mapper::class)) {
+ \class_alias(\OCP\AppFramework\Db\Mapper::class, 'OCA\TimeTracker\AppFramework\Db\CompatibleMapper');
+ } else {
+ \class_alias(\OCA\TimeTracker\AppFramework\Db\OldNextcloudMapper::class, 'OCA\TimeTracker\AppFramework\Db\CompatibleMapper');
+ }
+ }
+
$container = $this->getContainer();
/**
* Controllers
@@ -44,4 +53,4 @@ public function __construct(array $urlParams=array()){
});
}
-}
\ No newline at end of file
+}
diff --git a/lib/Controller/AjaxController.php b/lib/Controller/AjaxController.php
index f5dbd38..96e12d2 100644
--- a/lib/Controller/AjaxController.php
+++ b/lib/Controller/AjaxController.php
@@ -1,5 +1,6 @@
userId = $UserId;
@@ -102,7 +103,7 @@ public function workIntervals() {
if ($wi->projectId != null){
$project = $this->projectMapper->find($wi->projectId);
}
-
+
$tags = [];
$wiToTags = $this->workIntervalToTagMapper->findAllForWorkInterval($wi->id);
foreach($wiToTags as $wiToTag){
@@ -110,7 +111,7 @@ public function workIntervals() {
if ($t != null)
$tags[] = $t;
}
-
+
$wa = ['duration' => $wi->duration,
'id' => $wi->id,
'name' => $wi->name,
@@ -120,19 +121,20 @@ public function workIntervals() {
'start' => $wi->start,
'tags' => $tags,
'userUid' => $wi->userUid,
+ 'cost' => $wi->cost,
'projectName' => ($project === null)?null:$project->name,
'projectColor' => ($project === null)?null:$project->color,
];
$days[$dt][$wi->name]['children'][] = $wa;
$days[$dt][$wi->name]['totalTime'] += $wa['duration'];
}
-
+
$running = $this->workIntervalMapper->findAllRunning($this->userId);
return new JSONResponse(["WorkIntervals" => $l, "running" => $running, 'days' => $days, 'now' => time()]);
}
-
+
public function isThisAdminUser(){
return \OC_User::isAdminUser(\OC_User::getUser());
}
@@ -147,9 +149,35 @@ public function isUserAdmin($user){
* @NoCSRFRequired
*/
public function index() {
-
+
}
+ /**
+ * @NoAdminRequired
+ */
+ public function addCost($id)
+ {
+ $wi = $this->workIntervalMapper->find($id);
+ $cost = $this->request->cost;
+ $cost = str_replace(',', '.', $cost);
+
+ if (!is_numeric($cost)) {
+ return new JSONResponse(['error' => 'Non numeric value'], Http::STATUS_BAD_REQUEST);
+ }
+
+ $costInCents = $cost * 100;
+
+ $wi->setCost($costInCents);
+
+ try {
+ $this->workIntervalMapper->update($wi);
+ } catch (\Exception $e) {
+ return new JSONResponse(['error' => true, 'message' => $e->getMessage()]);
+ }
+
+ return new JSONResponse(['success' => true, 'test' => $cost ?? 't']);
+ }
+
/**
*
* @NoAdminRequired
@@ -176,11 +204,11 @@ public function startTimer($name) {
$winterval->setRunning(1);
$winterval->setName($name);
$winterval->setUserUid($this->userId);
-
+
// first get tags and project ids from the last work item with the same name
$lwinterval = $this->workIntervalMapper->findLatestByName($this->userId, $name);
if ($projectId == null && $lwinterval != null){
-
+
$winterval->setProjectId($lwinterval->projectId);
}
@@ -200,7 +228,7 @@ public function startTimer($name) {
}
}
-
+
if ($tags != null){
$tagsArray = explode(",", $tags);
foreach($tagsArray as $t){
@@ -213,12 +241,12 @@ public function startTimer($name) {
}
-
-
-
+
+
+
//echo json_encode((array)$winterval);
return new JSONResponse(["WorkIntervals" => $winterval, "running" => 1]);
-
+
}
@@ -237,7 +265,7 @@ public function stopTimer($name) {
}
$running = $this->workIntervalMapper->findAllRunning($this->userId);
-
+
$now = time();
foreach($running as $r){
$r->setRunning(0);
@@ -258,7 +286,9 @@ public function stopTimer($name) {
public function deleteWorkInterval($id) {
$wi = $this->workIntervalMapper->find($id);
$this->workIntervalMapper->delete($wi);
-
+
+ $running = $this->workIntervalMapper->findAllRunning($this->userId);
+
return new JSONResponse(["WorkIntervals" => json_decode(json_encode($running), true)]);
}
@@ -268,9 +298,9 @@ public function deleteWorkInterval($id) {
*/
public function updateWorkInterval($id) {
-
+
$wi = $this->workIntervalMapper->find($id);
-
+
if (isset($this->request->name)) {
if (strlen($this->request->name) > 255){
return new JSONResponse(["Error" => "Name too long"]);
@@ -304,14 +334,14 @@ public function updateWorkInterval($id) {
$newWiToTag->setTagId($tag);
$newWiToTag->setCreatedAt(time());
$this->workIntervalToTagMapper->insert($newWiToTag);
-
+
}
-
+
}
}
}
-
+
if (isset($this->request->tagId)) {
if (is_array($this->request->tagId)){
$tags = $this->request->tagId;
@@ -322,7 +352,7 @@ public function updateWorkInterval($id) {
$this->workIntervalToTagMapper->deleteAllForWorkInterval($id);
$project = null;
$locked = 0;
-
+
foreach($tags as $tag){
if (empty($tag))
@@ -363,10 +393,10 @@ public function updateWorkInterval($id) {
$de->setTimeZone(new \DateTimeZone('UTC'));
$wi->setDuration($de->getTimestamp() - $dt->getTimestamp());
}
-
+
$this->workIntervalMapper->update($wi);
$running = $this->workIntervalMapper->findAllRunning($this->userId);
-
+
return new JSONResponse(["WorkIntervals" => json_decode(json_encode($running), true)]);
}
@@ -377,11 +407,11 @@ public function updateWorkInterval($id) {
*/
public function addWorkInterval() {
-
+
$wi = new WorkInterval();
$wi->setUserUid($this->userId);
$wi->setRunning(0);
-
+
if (isset($this->request->name)) {
$wi->setName(urldecode($this->request->name));
}
@@ -412,9 +442,9 @@ public function addWorkInterval() {
$newWiToTag->setTagId($tag);
$newWiToTag->setCreatedAt(time());
$this->workIntervalToTagMapper->insert($newWiToTag);
-
+
}
-
+
}
}
@@ -424,7 +454,7 @@ public function addWorkInterval() {
$this->workIntervalToTagMapper->deleteAllForWorkInterval($id);
$project = null;
$locked = 0;
-
+
foreach($tags as $tag){
if (empty($tag))
@@ -454,7 +484,9 @@ public function addWorkInterval() {
}
$this->workIntervalMapper->insert($wi);
-
+
+ $running = $this->workIntervalMapper->findAllRunning($this->userId);
+
return new JSONResponse(["WorkIntervals" => json_decode(json_encode($running), true)]);
}
@@ -522,7 +554,7 @@ public function deleteClient($id) {
$utoc = $this->userToClientMapper->findForUserAndClient($this->userId, $c);
if ($utoc != null){
-
+
$this->userToClientMapper->delete($utoc);
}
return $this->getClients();
@@ -535,12 +567,16 @@ public function deleteClient($id) {
* @NoCSRFRequired
*/
public function getClients(){
- $clients = $this->clientMapper->findAll($this->userId);
- return new JSONResponse(["Clients" => json_decode(json_encode($clients), true)]);
- }
+ $clientName = $this->request->term ?? null;
-
+ if ($clientName) {
+ $clients = $this->clientMapper->searchByName($this->userId, $clientName);
+ } else {
+ $clients = $this->clientMapper->findAll($this->userId);
+ }
+ return new JSONResponse(["Clients" => json_decode(json_encode($clients), true)]);
+ }
/**
*
@@ -637,10 +673,10 @@ public function editProject($id) {
$this->userToProjectMapper->insert($up);
}
}
-
+
if (isset($this->request->archived) && $p->getArchived() != $this->request->archived){
if (($this->isThisAdminUser() || $p->createdByUserUid == $this->userId) ){
-
+
$archived = $this->request->archived;
$p->setArchived($archived);
} else {
@@ -649,7 +685,7 @@ public function editProject($id) {
}
$this->projectMapper->update($p);
-
+
return $this->getProjects();
}
/**
@@ -663,7 +699,7 @@ public function deleteProject($id) {
$utop = $this->userToProjectMapper->findForUserAndProject($this->userId, $p);
if ($utop != null){
-
+
$this->userToProjectMapper->delete($utop);
}
return $this->getProjects();
@@ -684,17 +720,23 @@ public function deleteProjectWithData($id) {
$this->tagMapper->allowedTags($id,[]);
$this->projectMapper->delete($id);
-
+
return $this->getProjects();
}
-
+
/**
*
* @NoAdminRequired
*/
public function getProjects(){
- $projects = $this->projectMapper->findAll($this->userId);
+ $projectName = $this->request->term ?? null;
+
+ if ($projectName) {
+ $projects = $this->projectMapper->searchByName($this->userId, $projectName);
+ } else {
+ $projects = $this->projectMapper->findAll($this->userId);
+ }
$parray = json_decode(json_encode($projects), true);
foreach($parray as $pi => $pv){
if (isset($pv->id)) {
@@ -742,7 +784,7 @@ public function getProjectsTable(){
$out['clientId'] = $client->id;
}
}
-
+
$outProjects[] = $out;
}
@@ -768,7 +810,7 @@ public function addTag($name) {
return new JSONResponse(["Error" => "This tag name already exists"]);
}
-
+
return $this->getTags();
}
/**
@@ -791,7 +833,7 @@ public function editTag($id) {
}
$c->setName($name);
$this->tagMapper->update($c);
-
+
return $this->getTags();
}
/**
@@ -821,7 +863,7 @@ public function getTags(){
$wi = $this->workIntervalMapper->find($workItem);
if ($wi->projectId != null){
$project = $this->projectMapper->find($wi->projectId);
-
+
}
}
if($project != null && $project->locked){
@@ -867,7 +909,7 @@ public function getReport(){
$name = $this->userId;
}
-
+
if(!$this->isThisAdminUser()){
$allowedClients = $this->clientMapper->findAll($this->userId);
$allowedClientsId = array_map(function($client){ return $client->id;}, $allowedClients );
@@ -887,7 +929,7 @@ public function getReport(){
}
}
-
+
$filterTagId = [];
$groupOn1 = $this->request->group1;
$groupOn2 = $this->request->group2;
@@ -921,7 +963,7 @@ public function postTimeline(){
$name = $this->userId;
}
-
+
if(!$this->isThisAdminUser()){
$allowedClients = $this->clientMapper->findAll($this->userId);
$allowedClientsId = array_map(function($client){ return $client->id;}, $allowedClients );
@@ -941,12 +983,12 @@ public function postTimeline(){
}
}
-
+
$filterTagId = [];
$groupOn1 = $this->request->group1;
$groupOn2 = $this->request->group2;
$items = $this->reportItemMapper->report($name, $from, $to, $filterProjectId, $filterClientId, $filterTagId, $timegroup, $groupOn1, $groupOn2, $this->isThisAdminUser(), 0, 1000);
-
+
$timeline = new Timeline();
$timeline->setUserUid($this->userId);
$timeline->setGroup1($this->request->group1);
@@ -974,6 +1016,7 @@ public function postTimeline(){
$te->setTimeInterval($i->time);
$te->setTotalDuration($i->totalDuration);
$te->setCreatedAt(time());
+ $te->setCost($i->cost);
$this->timelineEntryMapper->insert($te);
}
@@ -1013,7 +1056,7 @@ public function secondsToTime($seconds){
$timeFormat = sprintf('%02d:%02d:%02d', $hours, $mins, $secs);
return $timeFormat;
}
-
+
/**
*
* @NoAdminRequired
@@ -1038,7 +1081,7 @@ public function downloadTimeline($id){
fputcsv($output, array('id', 'User Uid', 'Name', 'Project Name', 'Client Name', 'Time Interval', 'Total Duration'));
$totalDuration = 0;
foreach($te as $t){
-
+
fputcsv($output, [$t->id, $t->userUid, $t->name, $t->projectName, $t->clientName, $t->timeInterval, $this->secondsToTime($t->totalDuration)]);
$totalDuration += $t->totalDuration;
}
@@ -1090,7 +1133,7 @@ public function emailTimeline($id) {
$emails = explode(';',$email);
$subject = $this->request->subject;
$content = $this->request->content;
-
+
// output headers so that the file is downloaded rather than displayed
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=timeline-'.$user.'-'.$id.'.csv');
@@ -1104,14 +1147,14 @@ public function emailTimeline($id) {
fputcsv($output, array('id', 'User Uid', 'Name', 'Project Name', 'Client Name', 'Time Interval', 'Total Duration'));
$totalDuration = 0;
foreach($te as $t){
-
+
fputcsv($output, [$t->id, $t->userUid, $t->name, $t->projectName, $t->clientName, $t->timeInterval, $this->secondsToTime($t->totalDuration)]);
$totalDuration += $t->totalDuration;
}
fputcsv($output, ['TOTAL', '', '', '', '', '', $this->secondsToTime($totalDuration)]);
-
-
-
+
+
+
$mailer = \OC::$server->getMailer();
$message = $mailer->createMessage();
$attach = $mailer->createAttachmentFromPath($path);
@@ -1122,7 +1165,7 @@ public function emailTimeline($id) {
//$message->setHtmlBody($content);
$message->attach($attach);
$mailer->send($message);
-
+
fclose($output);
unlink($path);
return new JSONResponse([]);
@@ -1155,10 +1198,10 @@ public function addGoal() {
return new JSONResponse(["Error" => "There can be only one goal per project"]);
}
-
+
return $this->getGoals();
}
-
+
/**
*
* @NoAdminRequired
@@ -1180,6 +1223,7 @@ public function getStartOfWeek($timestamp){
$weeknumber = $date->format("W");
$year = $date->format("Y");
$weekstartdt = new \DateTime();
+ $weekstartdt->setTime(0, 0, 0, 0);
$weekstartdt->setISODate($year, $weeknumber);
return $weekstartdt;
@@ -1187,6 +1231,7 @@ public function getStartOfWeek($timestamp){
public function getStartOfMonth($timestamp){
$date = new \DateTime('@'.$timestamp);
$date->modify('first day of this month');
+ $date->setTime(0, 0, 0, 0);
return $date;
}
@@ -1225,7 +1270,7 @@ public function getGoals(){
$goals = $this->goalMapper->findAll($this->userId);
$weekStart = $this->getStartOfWeek(time())->format('Y-m-d');
$monthStart = $this->getStartOfMonth(time())->format('Y-m');
-
+
$ret = [];
foreach($goals as $goal){
$rgoal = [];
@@ -1241,22 +1286,27 @@ public function getGoals(){
}
$workedSecondsCurrentPeriod = 0;
$debtSeconds = 0;
- array_pop($intervals);
foreach($intervals as $interval){
$workedInInterval = 0;
foreach($repItems as $repItem) {
- if ($interval == $repItem->time) {
- $workedInInterval = $repItem->totalDuration;
- break;
- }
+
+ if ($goal->interval == 'Weekly'){
+ if ($interval == $this->getStartOfWeek($repItem->time)->format('Y-m-d')) {
+ $workedInInterval += $repItem->totalDuration;
+ }
+ } elseif ($goal->interval == 'Monthly'){
+ if ($interval == $this->getStartOfMonth($repItem->time)->format('Y-m')) {
+ $workedInInterval += $repItem->totalDuration;
+ }
+ }
}
$debtSeconds += ($goal->hours*3600 - $workedInInterval);
}
foreach($repItems as $period){
- if ($goal->interval == 'Weekly' && $period->time == $weekStart){
+ if ($goal->interval == 'Weekly' && $this->getStartOfWeek($period->time)->format('Y-m-d') == $weekStart){
$workedSecondsCurrentPeriod += $period->totalDuration;
- } elseif ($goal->interval == 'Monthly' && $period->time == $monthStart){
+ } elseif ($goal->interval == 'Monthly' && $this->getStartOfMonth($period->time)->format('Y-m') == $monthStart){
$workedSecondsCurrentPeriod += $period->totalDuration;
}
}
diff --git a/lib/Db/ClientMapper.php b/lib/Db/ClientMapper.php
index 77e6f11..1cee7ba 100644
--- a/lib/Db/ClientMapper.php
+++ b/lib/Db/ClientMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class ClientMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class ClientMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'timetracker_client');
@@ -16,14 +17,14 @@ public function __construct(IDBConnection $db) {
public function findByName($name) {
$sql = 'SELECT * FROM `*PREFIX*timetracker_client` ' .
'WHERE upper(`name`) = ?';
-
+
try {
$e = $this->findEntity($sql, [strtoupper($name)]);
return $e;
} catch (\OCP\AppFramework\Db\DoesNotExistException $e){
return null;
}
-
+
}
/**
@@ -36,11 +37,14 @@ public function find($id) {
return $this->findEntity($sql, [$id]);
}
-
public function findAll($user){
$sql = 'SELECT tc.* FROM `*PREFIX*timetracker_client` tc left join `*PREFIX*timetracker_user_to_client` uc on uc.client_id = tc.id where uc.user_uid = ? order by tc.name asc';
return $this->findEntities($sql, [$user]);
}
-
-}
\ No newline at end of file
+ public function searchByName($user, $name){
+ $name = strtoupper($name);
+ $sql = 'SELECT tc.* FROM `*PREFIX*timetracker_client` tc left join `*PREFIX*timetracker_user_to_client` uc on uc.client_id = tc.id where uc.user_uid = ? and upper(tc.name) LIKE ? order by tc.name asc';
+ return $this->findEntities($sql, [$user, "%" . $name . "%"]);
+ }
+}
diff --git a/lib/Db/GoalMapper.php b/lib/Db/GoalMapper.php
index 50004d3..a3ccf76 100644
--- a/lib/Db/GoalMapper.php
+++ b/lib/Db/GoalMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class GoalMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class GoalMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'timetracker_goal');
@@ -43,4 +44,4 @@ public function findAll($user){
}
-}
\ No newline at end of file
+}
diff --git a/lib/Db/ProjectMapper.php b/lib/Db/ProjectMapper.php
index aa41da4..8c23921 100644
--- a/lib/Db/ProjectMapper.php
+++ b/lib/Db/ProjectMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class ProjectMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class ProjectMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'timetracker_project');
@@ -16,14 +17,22 @@ public function __construct(IDBConnection $db) {
public function findByName($name) {
$sql = 'SELECT * FROM `*PREFIX*timetracker_project` ' .
'WHERE upper(`name`) = ?';
-
+
try {
$e = $this->findEntity($sql, [strtoupper($name)]);
return $e;
} catch (\OCP\AppFramework\Db\DoesNotExistException $e){
return null;
}
-
+
+ }
+
+ public function searchByName($user, string $name) {
+ $name = strtoupper($name);
+ $sql = 'SELECT tp.* FROM `*PREFIX*timetracker_project` tp LEFT JOIN `*PREFIX*timetracker_user_to_project` up ON up.project_id = tp.id WHERE up.`user_uid` = ? AND upper(tp.`name`) LIKE ? ORDER BY tp.`name`';
+
+ return $this->findEntities($sql, [$user,"%" . $name ."%"]);
+
}
/**
@@ -59,17 +68,13 @@ public function findAllAdmin($getArchived = 0){
public function delete($project_id) {
$sql = 'delete FROM `*PREFIX*timetracker_project` ' .
' where id = ?';
-
+
try {
$this->execute($sql, [$project_id]);
return;
} catch (\OCP\AppFramework\Db\DoesNotExistException $e){
return;
}
-
- }
-
-
-
-}
\ No newline at end of file
+ }
+}
diff --git a/lib/Db/ReportItem.php b/lib/Db/ReportItem.php
index c7cb0eb..1388980 100644
--- a/lib/Db/ReportItem.php
+++ b/lib/Db/ReportItem.php
@@ -17,12 +17,13 @@ class ReportItem extends Entity {
public $project;
public $clientId;
public $client;
+ public $cost;
public function __construct() {
// add types in constructor
-
+
$this->addType('id', 'integer');
$this->addType('name', 'string');
$this->addType('details', 'string');
@@ -32,5 +33,6 @@ public function __construct() {
$this->addType('ftime', 'string');
$this->addType('totalDuration', 'integer');
$this->addType('project', 'string');
+ $this->addType('cost', 'integer');
}
}
diff --git a/lib/Db/ReportItemMapper.php b/lib/Db/ReportItemMapper.php
index 16be6e6..6728175 100644
--- a/lib/Db/ReportItemMapper.php
+++ b/lib/Db/ReportItemMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class ReportItemMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class ReportItemMapper extends CompatibleMapper {
private $dbengine;
@@ -32,11 +33,17 @@ public function __construct(IDBConnection $db) {
public $client;
*/
-
+
public function report($user, $from, $to, $filterProjectId, $filterClientId, $filterTagId, $timegroup, $groupOn1, $groupOn2, $admin, $start, $limit ){
-
+
$selectFields = ['min(wi.id) as id', 'sum(duration) as "totalDuration"'];
-
+
+ if ($timegroup !== null) {
+ $selectFields[]= "SUM(wi.cost) as cost";
+ } else {
+ $selectFields[] = 'wi.cost as cost';
+ }
+
$aggregation = true;
if(empty($groupOn1) && empty($groupOn2) && empty($timegroup)) {
$selectFields[] = 'min(wi.details) as "details"';
@@ -103,7 +110,7 @@ public function report($user, $from, $to, $filterProjectId, $filterClientId, $fi
}
}
-
+
if ($aggregation){
if(($groupOn1 != 'project') && ($groupOn2 != 'project')){
$selectFields[] = '\'*\' as "projectId"';
@@ -113,12 +120,12 @@ public function report($user, $from, $to, $filterProjectId, $filterClientId, $fi
$selectFields[] = 'group_concat(distinct p.name) as project';
}
} else {
-
+
$selectFields[] = '\'*\' as "projectId"';
$selectFields[] = 'p.name as project';
}
-
-
+
+
if(($groupOn1 != 'client') && ($groupOn2 != 'client')){
$selectFields[] = '\'*\' as "clientId"';
if ($this->dbengine == 'POSTGRES') {
@@ -126,23 +133,23 @@ public function report($user, $from, $to, $filterProjectId, $filterClientId, $fi
} else {
$selectFields[] = 'group_concat(distinct c.name) as client';
}
-
+
} else {
$selectFields[] = '\'*\' as "clientId"';
$selectFields[] = 'c.name as client';
}
-
+
if(($groupOn1 != 'userUid') && ($groupOn2 != 'userUid') && $aggregation){
if ($this->dbengine == 'POSTGRES') {
$selectFields[] = 'string_agg(distinct user_uid, \',\') as "userUid"';
} else {
$selectFields[] = 'group_concat(distinct user_uid) as "userUid"';
}
-
+
} else {
$selectFields[] = 'user_uid as "userUid"';
}
-
+
}
$selectItems = implode(", ",$selectFields).
@@ -167,7 +174,7 @@ public function report($user, $from, $to, $filterProjectId, $filterClientId, $fi
foreach($filterProjectId as $f){
$qm[] = '?';
$params[] = $f;
-
+
if($f == null) {
$append = ' or wi.project_id is null ';
}
@@ -205,7 +212,7 @@ public function report($user, $from, $to, $filterProjectId, $filterClientId, $fi
// }
$groups[] = 'ftime';
}
-
+
if (!empty($groupOn1)){
if ($groupOn1 == "project" || $groupOn1 == "client" || $groupOn1 == "name" || $groupOn1 == "userUid")
// $groups[] = $groupOn1;
@@ -250,6 +257,6 @@ public function report($user, $from, $to, $filterProjectId, $filterClientId, $fi
return $this->findEntities($sql, $params, $limit, $start);
}
-
+
}
diff --git a/lib/Db/TagMapper.php b/lib/Db/TagMapper.php
index ce29fbd..187252f 100644
--- a/lib/Db/TagMapper.php
+++ b/lib/Db/TagMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class TagMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class TagMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
$this->dbengine = 'MYSQL';
diff --git a/lib/Db/TimelineEntryMapper.php b/lib/Db/TimelineEntryMapper.php
index 0c66a31..c0216c4 100644
--- a/lib/Db/TimelineEntryMapper.php
+++ b/lib/Db/TimelineEntryMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class TimelineEntryMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class TimelineEntryMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'timetracker_timeline_entry');
@@ -31,4 +32,4 @@ public function findTimelineEntries($tid) {
return $this->findEntities($sql, [$tid]);
}
-}
\ No newline at end of file
+}
diff --git a/lib/Db/TimelineMapper.php b/lib/Db/TimelineMapper.php
index 3cbea06..4f434fb 100644
--- a/lib/Db/TimelineMapper.php
+++ b/lib/Db/TimelineMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class TimelineMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class TimelineMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'timetracker_timeline');
@@ -63,4 +64,4 @@ public function findByStatus($status) {
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/Db/UserToClientMapper.php b/lib/Db/UserToClientMapper.php
index 7884697..816598f 100644
--- a/lib/Db/UserToClientMapper.php
+++ b/lib/Db/UserToClientMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class UserToClientMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class UserToClientMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'timetracker_user_to_client');
@@ -54,4 +55,4 @@ public function findForUserAndClient($uid, $client) {
-}
\ No newline at end of file
+}
diff --git a/lib/Db/UserToProjectMapper.php b/lib/Db/UserToProjectMapper.php
index f88eb2e..6337603 100644
--- a/lib/Db/UserToProjectMapper.php
+++ b/lib/Db/UserToProjectMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class UserToProjectMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class UserToProjectMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'timetracker_user_to_project');
@@ -77,4 +78,4 @@ public function deleteAllForProject($project_id) {
-}
\ No newline at end of file
+}
diff --git a/lib/Db/WorkInterval.php b/lib/Db/WorkInterval.php
index 998036d..d546f16 100644
--- a/lib/Db/WorkInterval.php
+++ b/lib/Db/WorkInterval.php
@@ -14,11 +14,12 @@ class WorkInterval extends Entity {
public $start;
public $duration;
public $running;
+ public $cost;
public function __construct() {
// add types in constructor
-
+
$this->addType('id', 'integer');
$this->addType('name', 'string');
$this->addType('details', 'string');
@@ -27,5 +28,6 @@ public function __construct() {
$this->addType('start', 'integer');
$this->addType('duration', 'integer');
$this->addType('running', 'integer');
+ $this->addType('cost', 'integer');
}
}
diff --git a/lib/Db/WorkIntervalMapper.php b/lib/Db/WorkIntervalMapper.php
index a494ea6..4a94ec4 100644
--- a/lib/Db/WorkIntervalMapper.php
+++ b/lib/Db/WorkIntervalMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class WorkIntervalMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class WorkIntervalMapper extends CompatibleMapper {
private $dbengine;
public function __construct(IDBConnection $db) {
diff --git a/lib/Db/WorkIntervalToTagMapper.php b/lib/Db/WorkIntervalToTagMapper.php
index 37d2083..d7bd2a5 100644
--- a/lib/Db/WorkIntervalToTagMapper.php
+++ b/lib/Db/WorkIntervalToTagMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class WorkIntervalToTagMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class WorkIntervalToTagMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'timetracker_workint_to_tag');
@@ -69,4 +70,4 @@ public function deleteAllForTag($tagId) {
-}
\ No newline at end of file
+}
diff --git a/lib/Db/WorkItemMapper.php b/lib/Db/WorkItemMapper.php
index 0a8da3e..8517fc6 100644
--- a/lib/Db/WorkItemMapper.php
+++ b/lib/Db/WorkItemMapper.php
@@ -4,9 +4,10 @@
namespace OCA\TimeTracker\Db;
use OCP\IDBConnection;
-use OCP\AppFramework\Db\Mapper;
-class WorkItemMapper extends Mapper {
+use OCA\TimeTracker\AppFramework\Db\CompatibleMapper;
+
+class WorkItemMapper extends CompatibleMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'timetracker_work_item');
@@ -50,4 +51,4 @@ public function findAll($limit=null, $offset=null) {
return $this->findEntities($sql, $limit, $offset);
}
-}
\ No newline at end of file
+}
diff --git a/lib/Migration/Version000020Date20220528101009.php b/lib/Migration/Version000020Date20220528101009.php
new file mode 100644
index 0000000..6e2c6bf
--- /dev/null
+++ b/lib/Migration/Version000020Date20220528101009.php
@@ -0,0 +1,75 @@
+
+ *
+ * @author Harm Akkerman
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\TimeTracker\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+/**
+ * Auto-generated migration step: Please modify to your needs!
+ */
+class Version000020Date20220528101009 extends SimpleMigrationStep {
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ */
+ public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ * @return null|ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ $schema = $schemaClosure();
+ if ($schema->hasTable('timetracker_work_interval')) {
+ $table = $schema->getTable('timetracker_work_interval');
+ if (!$table->hasColumn('cost')) {
+ $table->addColumn('cost', 'integer', [
+ 'notnull' => false,
+ 'length' => 10,
+ ]);
+ }
+ }
+
+ return $schema;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ */
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ }
+}
diff --git a/templates/content/clients.php b/templates/content/clients.php
index d4687f7..e0f12ac 100644
--- a/templates/content/clients.php
+++ b/templates/content/clients.php
@@ -1,28 +1,28 @@
-
Clients
-
+
Clients
+
-
-
- Are you sure you want to delete this client?
-
-
-
All form fields are required.
-
-
-
-
-
-
+
+
+ Are you sure you want to delete this client?
+
+
+
All form fields are required.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/content/dashboard.php b/templates/content/dashboard.php
index a8fd72e..66ac410 100644
--- a/templates/content/dashboard.php
+++ b/templates/content/dashboard.php
@@ -1,22 +1,22 @@
-
-
Dashboard
+
Dashboard
-
-
-
-
Time (in minutes) allocated to each client in the last days
-
-
-
-
-
+
+
+
Time (in minutes) allocated to each client in the last days
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
\ No newline at end of file
diff --git a/templates/content/goals.php b/templates/content/goals.php
index d1a5cac..b0f4159 100644
--- a/templates/content/goals.php
+++ b/templates/content/goals.php
@@ -1,26 +1,26 @@
-
Goals
-
+
Goals
+
-
-
- Are you sure you want to delete this goal?
-
-
-
-
-
+
+
+ Are you sure you want to delete this goal?
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/content/index.php b/templates/content/index.php
index c5b5f2b..d3ba8e6 100644
--- a/templates/content/index.php
+++ b/templates/content/index.php
@@ -1,71 +1,69 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
Manual entry
+
+ Are you sure you want to delete this work item?
+
+
+
All form fields are required.
+
-
-
-
-
-
-
-
Manual entry
-
- Are you sure you want to delete this work item?
-
\ No newline at end of file
diff --git a/templates/content/reports.php b/templates/content/reports.php
index 8dd0c47..f4005a9 100644
--- a/templates/content/reports.php
+++ b/templates/content/reports.php
@@ -1,80 +1,79 @@
-
-
Reports
+
Reports
+
+
+ Are you sure you want to delete this report?
+
+
+
All form fields are required.
-
- Are you sure you want to delete this report?
-
-
-
All form fields are required.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
-
-
-
+
+
+
\ No newline at end of file
diff --git a/templates/content/tags.php b/templates/content/tags.php
index 5fcf6df..10e6823 100644
--- a/templates/content/tags.php
+++ b/templates/content/tags.php
@@ -1,28 +1,28 @@
-
Tags
-
+
Tags
+
-
-
- Are you sure you want to delete this tag?
-
-
-
All form fields are required.
-
-
-
-
-
-
+
+
+ Are you sure you want to delete this tag?
+
+
+
All form fields are required.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/content/timelines-admin.php b/templates/content/timelines-admin.php
index 625d618..e16f4ca 100644
--- a/templates/content/timelines-admin.php
+++ b/templates/content/timelines-admin.php
@@ -1,44 +1,43 @@
-
-
Timelines Admin
+
Timelines Admin
+
+
+ Are you sure you want to delete this timeline?
+
+
+
All form fields are required.
+
+
+
+
+
+
+
All form fields are required.
-
- Are you sure you want to delete this timeline?
-
-
-
All form fields are required.
-
-
-
-
-
-
-
All form fields are required.
-
-
-
+
+
-
-
-
+
+
+
\ No newline at end of file
diff --git a/templates/content/timelines.php b/templates/content/timelines.php
index 7d68bb8..70c5a8f 100644
--- a/templates/content/timelines.php
+++ b/templates/content/timelines.php
@@ -1,102 +1,102 @@
-
Timelines
+
Timelines
-
- Are you sure you want to delete this timeline?
-
-
-
All form fields are required.
-
-
-
-
-
All form fields are required. Please make sure that your email settings are correct in your Nextcloud configuration.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ Are you sure you want to delete this timeline?
+
+
+
All form fields are required.
+
+
+
+
+
All form fields are required. Please make sure that your email settings are correct in your Nextcloud configuration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
Exported Timelines Statuses
-
-
-
-
+
+
+
+
Exported Timelines Statuses
+
+
+
+
\ No newline at end of file
diff --git a/templates/index.php b/templates/index.php
index c458d69..d9144c1 100644
--- a/templates/index.php
+++ b/templates/index.php
@@ -1,36 +1,24 @@
= 28) {
+ style('timetracker', 'style-compat');
+}
+
script('timetracker', $script);
?>
-