Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Archive projects #236

Merged
merged 11 commits into from
Dec 3, 2023
4 changes: 2 additions & 2 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -847,11 +847,11 @@ public function webEditBills(
*/
public function webEditProject(string $projectid, ?string $name = null, ?string $contact_email = null, ?string $password = null,
?string $autoexport = null, ?string $currencyname = null, ?bool $deletion_disabled = null,
?string $categorysort = null, ?string $paymentmodesort = null): DataResponse {
?string $categorysort = null, ?string $paymentmodesort = null, ?int $archived_ts = null): DataResponse {
if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['admin']) {
$result = $this->projectService->editProject(
$projectid, $name, $contact_email, $password, $autoexport,
$currencyname, $deletion_disabled, $categorysort, $paymentmodesort
$currencyname, $deletion_disabled, $categorysort, $paymentmodesort, $archived_ts
);
if (isset($result['success'])) {
return new DataResponse('UPDATED');
Expand Down
5 changes: 5 additions & 0 deletions lib/Db/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
* @method void setPaymentmodesort(string $paymentmodesort)
* @method string getCurrencyname()
* @method void setCurrencyname(string $currencyname)
* @method int getArchivedTs()
* @method void setArchivedTs(int $archivedTs)
*/
class Project extends Entity implements \JsonSerializable {

Expand All @@ -53,6 +55,7 @@ class Project extends Entity implements \JsonSerializable {
protected $categorysort;
protected $paymentmodesort;
protected $currencyname;
protected $archivedTs;

public function __construct() {
$this->addType('id', 'string');
Expand All @@ -67,6 +70,7 @@ public function __construct() {
$this->addType('categorysort', 'string');
$this->addType('paymentmodesort', 'string');
$this->addType('currencyname', 'string');
$this->addType('archived_ts', 'integer');
}

#[\ReturnTypeWillChange]
Expand All @@ -84,6 +88,7 @@ public function jsonSerialize() {
'categorysort' => $this->categorysort,
'paymentmodesort' => $this->paymentmodesort,
'currencyname' => $this->currencyname,
'archived_ts' => $this->archivedTs,
];
}
}
3 changes: 3 additions & 0 deletions lib/Db/ProjectMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
class ProjectMapper extends QBMapper {
const TABLENAME = 'cospend_projects';

public const ARCHIVED_TS_UNSET = -1;
public const ARCHIVED_TS_NOW = 0;

public function __construct(
IDBConnection $db,
private IL10N $l10n,
Expand Down
40 changes: 40 additions & 0 deletions lib/Migration/Version010512Date20231201151136.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace OCA\Cospend\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;

class Version010512Date20231201151136 extends SimpleMigrationStep {

public function __construct() {
}

/**
* @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 {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if ($schema->hasTable('cospend_projects')) {
$table = $schema->getTable('cospend_projects');
if (!$table->hasColumn('archived_ts')) {
$table->addColumn('archived_ts', Types::BIGINT, [
'notnull' => false,
'default' => null,
'unsigned' => true,
]);
return $schema;
}
}
return null;
}
}
13 changes: 12 additions & 1 deletion lib/Service/ProjectService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1572,14 +1572,25 @@ public function editMember(string $projectid, int $memberid, ?string $name = nul
* @param bool|null $deletion_disabled
* @param string|null $categorysort
* @param string|null $paymentmodesort
* @param int|null $archivedTs
* @return array
* @throws \OCP\DB\Exception
*/
public function editProject(string $projectid, ?string $name = null, ?string $contact_email = null, ?string $password = null,
?string $autoexport = null, ?string $currencyname = null, ?bool $deletion_disabled = null,
?string $categorysort = null, ?string $paymentmodesort = null): array {
?string $categorysort = null, ?string $paymentmodesort = null, ?int $archivedTs = null): array {
$qb = $this->db->getQueryBuilder();
$qb->update('cospend_projects');
if ($archivedTs !== null) {
if ($archivedTs === ProjectMapper::ARCHIVED_TS_NOW) {
$dbTs = (new DateTime())->getTimestamp();
} elseif ($archivedTs === ProjectMapper::ARCHIVED_TS_UNSET) {
$dbTs = null;
} else {
$dbTs = $archivedTs;
}
$qb->set('archived_ts', $qb->createNamedParameter($dbTs, IQueryBuilder::PARAM_STR));
}

if ($name !== null) {
if ($name === '') {
Expand Down
21 changes: 17 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ export default {

subscribe('project-clicked', this.onProjectClicked)
subscribe('delete-project', this.onDeleteProject)
subscribe('archive-project', this.archiveProject)
subscribe('deselect-project', this.deselectProject)
subscribe('project-imported', this.onProjectImported)
subscribe('stats-clicked', this.onStatsClicked)
subscribe('settle-clicked', this.onSettleClicked)
Expand All @@ -365,6 +367,8 @@ export default {

unsubscribe('project-clicked', this.onProjectClicked)
unsubscribe('delete-project', this.onDeleteProject)
unsubscribe('archive-project', this.archiveProject)
unsubscribe('deselect-project', this.deselectProject)
unsubscribe('project-imported', this.onProjectImported)
unsubscribe('stats-clicked', this.onStatsClicked)
unsubscribe('settle-clicked', this.onSettleClicked)
Expand Down Expand Up @@ -1021,6 +1025,10 @@ export default {
)
})
},
archiveProject(projectId) {
this.$set(this.projects[projectId], 'archived_ts', this.projects[projectId].archived_ts ? constants.PROJECT_ARCHIVED_TS_UNSET : constants.PROJECT_ARCHIVED_TS_NOW)
this.editProject(projectId, null, true)
},
updateProjectInfo(projectid) {
return network.updateProjectInfo(projectid).then((response) => {
this.projects[projectid].balance = response.data.balance
Expand All @@ -1037,6 +1045,7 @@ export default {
this.projects[projectid].lastchanged = response.data.lastchanged
this.projects[projectid].categories = response.data.categories
this.projects[projectid].paymentmodes = response.data.paymentmodes
this.projects[projectid].archived_ts = response.data.archived_ts
}).catch((error) => {
showError(
t('cospend', 'Failed to update balances')
Expand Down Expand Up @@ -1144,14 +1153,18 @@ export default {
}
}
},
editProject(projectid, password = null) {
const project = this.projects[projectid]
editProject(projectId, password = null, deselectProject = false) {
const project = this.projects[projectId]
network.editProject(project, password).then((response) => {
if (password && cospend.pageIsPublic) {
cospend.password = password
}
this.updateProjectInfo(cospend.currentProjectId)
showSuccess(t('cospend', 'Project saved'))
this.updateProjectInfo(projectId).then(() => {
showSuccess(t('cospend', 'Project saved'))
if (deselectProject) {
this.deselectProject()
}
})
}).catch((error) => {
showError(
t('cospend', 'Failed to edit project')
Expand Down
35 changes: 33 additions & 2 deletions src/components/AppNavigationProjectItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@
@update:menuOpen="onUpdateMenuOpen"
@click="onProjectClick">
<template #icon>
<FolderIcon v-if="selected"
<FolderIcon v-if="selected && !project.archived_ts"
class="icon folder-icon-primary"
:size="20" />
<FolderOutlineIcon v-else
<FolderOutlineIcon v-if="!selected && !project.archived_ts"
class="icon folder-icon"
:size="20" />
<ArchiveIcon v-if="selected && project.archived_ts"
class="icon folder-icon-primary"
:size="20" />
<ArchiveOutlineIcon v-if="!selected && project.archived_ts"
class="icon folder-icon"
:size="20" />
</template>
Expand Down Expand Up @@ -87,6 +93,22 @@
</template>
{{ t('cospend', 'Project settlement') }}
</NcActionButton>
<NcActionButton v-if="adminAccess && !project.archived_ts"
:close-after-click="true"
@click="onArchiveProjectClick">
<template #icon>
<ArchiveIcon :size="20" />
</template>
{{ t('cospend', 'Archive') }}
</NcActionButton>
<NcActionButton v-if="adminAccess && project.archived_ts"
:close-after-click="true"
@click="onArchiveProjectClick">
<template #icon>
<ArchiveCancelIcon :size="20" />
</template>
{{ t('cospend', 'Unarchive') }}
</NcActionButton>
<NcActionButton v-if="adminAccess"
:close-after-click="true"
@click="onDeleteProjectClick">
Expand Down Expand Up @@ -127,6 +149,9 @@ import AccountIcon from 'vue-material-design-icons/Account.vue'
import FolderIcon from 'vue-material-design-icons/Folder.vue'
import FolderOutlineIcon from 'vue-material-design-icons/FolderOutline.vue'
import ChartLineIcon from 'vue-material-design-icons/ChartLine.vue'
import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import ArchiveCancelIcon from 'vue-material-design-icons/ArchiveCancel.vue'
import ArchiveOutlineIcon from 'vue-material-design-icons/ArchiveOutline.vue'

import ReimburseIcon from './icons/ReimburseIcon.vue'

Expand Down Expand Up @@ -157,6 +182,9 @@ export default {
PlusIcon,
DeleteIcon,
DeleteVariantIcon,
ArchiveIcon,
ArchiveCancelIcon,
ArchiveOutlineIcon,
},
directives: {
ClickOutside,
Expand Down Expand Up @@ -226,6 +254,9 @@ export default {
onMemberClick(memberId) {
emit('member-click', { projectId: this.project.id, memberId })
},
onArchiveProjectClick() {
emit('archive-project', this.project.id)
},
onDeleteProjectClick() {
this.deleting = true
this.deletionTimer = new Timer(() => {
Expand Down
34 changes: 30 additions & 4 deletions src/components/CospendNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
</template>
</NcEmptyContent>
<AppNavigationProjectItem
v-for="id in sortedProjectIds"
v-for="id in filteredProjectIds"
:key="id"
:project="projects[id]"
:members="projects[id].members"
Expand All @@ -76,9 +76,19 @@
<template #footer>
<div id="app-settings">
<div id="app-settings-header">
<!--button class="settings-button" @click="showSettings">
{{ t('cospend', 'Cospend settings') }}
</button-->
<NcAppNavigationItem
:name="showArchivedProjects ? t('cospend', 'Show active projects') : t('cospend', 'Show archived projects')"
@click="toggleArchivedProjects">
<template #icon>
<CalendarIcon v-if="showArchivedProjects" />
<ArchiveLockIcon v-else />
</template>
<template #counter>
<NcCounterBubble>
{{ sortedProjectIds.length - filteredProjectIds.length }}
</NcCounterBubble>
</template>
</NcAppNavigationItem>
<NcAppNavigationItem
:name="t('cospend', 'Cospend settings')"
@click="showSettings">
Expand All @@ -99,6 +109,8 @@ import FolderIcon from 'vue-material-design-icons/Folder.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import FileImportIcon from 'vue-material-design-icons/FileImport.vue'
import CogIcon from 'vue-material-design-icons/Cog.vue'
import ArchiveLockIcon from 'vue-material-design-icons/ArchiveLock.vue'
import CalendarIcon from 'vue-material-design-icons/Calendar.vue'

import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
Expand All @@ -107,6 +119,7 @@ import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js'

import AppNavigationProjectItem from './AppNavigationProjectItem.vue'

Expand All @@ -129,12 +142,15 @@ export default {
NcButton,
NcModal,
NcTextField,
NcCounterBubble,
CogIcon,
FileImportIcon,
PlusIcon,
FolderIcon,
FolderPlusIcon,
ArrowRightIcon,
ArchiveLockIcon,
CalendarIcon,
},
directives: {
ClickOutside,
Expand Down Expand Up @@ -171,9 +187,15 @@ export default {
importingProject: false,
showCreationModal: false,
newProjectName: '',
showArchivedProjects: false,
}
},
computed: {
filteredProjectIds(opposite = false) {
return this.showArchivedProjects
? this.sortedProjectIds.filter(id => this.projects[id].archived_ts !== null)
: this.sortedProjectIds.filter(id => this.projects[id].archived_ts === null)
},
sortedProjectIds() {
if (this.cospend.sortOrder === 'name') {
return Object.keys(this.projects).sort((a, b) => {
Expand All @@ -194,6 +216,10 @@ export default {
beforeMount() {
},
methods: {
toggleArchivedProjects() {
this.showArchivedProjects = !this.showArchivedProjects
emit('deselect-project')
},
showSettings() {
emit('show-settings')
},
Expand Down
3 changes: 3 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export const MEMBER_WEIGHT_EDITION = 2
export const PROJECT_NAME_EDITION = 1
export const PROJECT_PASSWORD_EDITION = 2

export const PROJECT_ARCHIVED_TS_UNSET = -1
export const PROJECT_ARCHIVED_TS_NOW = 0

export const SHARE_TYPE = {
PUBLIC_LINK: 'l',
USER: 'u',
Expand Down
1 change: 1 addition & 0 deletions src/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export function editProject(project, password) {
deletion_disabled: project.deletiondisabled,
categorysort: project.categorysort,
paymentmodesort: project.paymentmodesort,
archived_ts: project.archived_ts,
}
const url = cospend.pageIsPublic
? generateUrl('/apps/cospend/api/projects/' + cospend.projectid + '/' + cospend.password)
Expand Down
Loading