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

Add component to show submission review state #1071

Merged
merged 4 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions src/components/entity/feed-entry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@ except according to the terms contained in the LICENSE file.
<template v-else-if="entry.action === 'submission.update'">
<i18n-t keypath="title.submission.approval.full">
<template #reviewState>
<span class="approval">
<span :class="reviewStateIcon('approved')"></span>
<span>{{ $t('title.submission.approval.reviewState') }}</span>
</span>
<submission-review-state value="approved" color-text>
{{ $t('title.submission.approval.reviewState') }}
</submission-review-state>
</template>
<template #name><actor-link :actor="entry.actor"/></template>
</i18n-t>
Expand Down Expand Up @@ -146,8 +145,8 @@ import { useI18n } from 'vue-i18n';
import ActorLink from '../actor-link.vue';
import EntityDiff from './diff.vue';
import FeedEntry from '../feed-entry.vue';
import SubmissionReviewState from '../submission/review-state.vue';

import useReviewState from '../../composables/review-state';
import useRoutes from '../../composables/routes';
import { useRequestData } from '../../request-data';

Expand Down Expand Up @@ -189,9 +188,6 @@ const sourceSubmissionPath = computed(() => submissionPath(
const { t } = useI18n();
const deletedSubmission = (key) => t(key, { id: props.submission.instanceId });

// submission.update
const { reviewStateIcon } = useReviewState();

// entity.create, entity.update.version
const versionAnchor = (v) => `#v${v}`;
const showBranchData = () => {
Expand All @@ -214,7 +210,6 @@ const showBranchData = () => {

.deleted-submission, .entity-label, .source-name { font-weight: normal; }
.deleted-submission { color: $color-danger; }
.approval { color: $color-success; }

.entity-version-tag, .feed-entry-title .offline-update {
font-size: 12px;
Expand Down
29 changes: 11 additions & 18 deletions src/components/submission/basic-details.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ except according to the terms contained in the LICENSE file.
</div>
<div>
<dt>{{ $t('reviewState') }}</dt>
<dd id="submission-basic-details-review-state">
<span :class="reviewStateIcon(submission.__system.reviewState)"></span>
<span>{{ $t(`reviewState.${submission.__system.reviewState}`) }}</span>
<dd>
<submission-review-state :value="submission.__system.reviewState"/>
</dd>
</div>
<div>
Expand Down Expand Up @@ -74,20 +73,24 @@ import DateTime from '../date-time.vue';
import FormVersionString from '../form-version/string.vue';
import Loading from '../loading.vue';
import PageSection from '../page/section.vue';
import SubmissionReviewState from './review-state.vue';

import useReviewState from '../../composables/review-state';
import { useRequestData } from '../../request-data';

export default {
name: 'SubmissionBasicDetails',
components: { DateTime, FormVersionString, Loading, PageSection },
components: {
DateTime,
FormVersionString,
Loading,
PageSection,
SubmissionReviewState
},
setup() {
const { submission, submissionVersion, resourceStates } = useRequestData();
const { reviewStateIcon } = useReviewState();
return {
submission, submissionVersion,
...resourceStates([submission, submissionVersion]),
reviewStateIcon
...resourceStates([submission, submissionVersion])
};
},
computed: {
Expand Down Expand Up @@ -121,16 +124,6 @@ export default {
margin-right: $margin-right-icon;
}
}

#submission-basic-details-review-state {
[class^="icon-"] { margin-right: $margin-right-icon; }

.icon-dot-circle-o { color: #999; }
.icon-comments { color: $color-warning; }
.icon-pencil { color: #666; }
.icon-check-circle { color: $color-success; }
.icon-times-circle { color: $color-danger; }
}
</style>

<i18n lang="json5">
Expand Down
27 changes: 12 additions & 15 deletions src/components/submission/feed-entry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ except according to the terms contained in the LICENSE file.
<template v-else-if="updateOrEdit">
<i18n-t :keypath="`title.updateReviewState.${reviewState}.full`">
<template #reviewState>
<span class="review-state" :class="reviewState">
<span :class="reviewStateIcon(reviewState)"></span>
<span>{{ $t(`title.updateReviewState.${reviewState}.reviewState`) }}</span>
</span>
<submission-review-state :value="reviewState" color-text>
{{ $t(`title.updateReviewState.${reviewState}.reviewState`) }}
</submission-review-state>
</template>
<template #name><actor-link :actor="entry.actor"/></template>
</i18n-t>
Expand Down Expand Up @@ -109,14 +108,20 @@ import ActorLink from '../actor-link.vue';
import FeedEntry from '../feed-entry.vue';
import MarkdownView from '../markdown/view.vue';
import SubmissionDiffItem from './diff-item.vue';
import SubmissionReviewState from './review-state.vue';

import useReviewState from '../../composables/review-state';
import useRoutes from '../../composables/routes';
import { useRequestData } from '../../request-data';

export default {
name: 'SubmissionFeedEntry',
components: { ActorLink, FeedEntry, MarkdownView, SubmissionDiffItem },
components: {
ActorLink,
FeedEntry,
MarkdownView,
SubmissionDiffItem,
SubmissionReviewState
},
props: {
projectId: {
type: String,
Expand All @@ -137,9 +142,8 @@ export default {
},
setup() {
const { diffs } = useRequestData();
const { reviewStateIcon } = useReviewState();
const { datasetPath, entityPath } = useRoutes();
return { diffs, reviewStateIcon, datasetPath, entityPath };
return { diffs, datasetPath, entityPath };
},
computed: {
updateOrEdit() {
Expand Down Expand Up @@ -204,13 +208,6 @@ export default {
.icon-cloud-upload, .icon-comment, .icon-trash, .icon-recycle { color: #bbb; }
.entity-icon { color: $color-action-foreground; }
.icon-warning { color: $color-danger; }
.review-state {
color: #999;
&.hasIssues { color: $color-warning; }
&.edited { color: #666; }
&.approved { color: $color-success; }
&.rejected { color: $color-danger; }
}
.entity-label { font-weight: normal; }
}
</style>
Expand Down
36 changes: 14 additions & 22 deletions src/components/submission/metadata-row.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ except according to the terms contained in the LICENSE file.
<td><date-time :iso="submission.__system.submissionDate"/></td>
<td v-if="!draft && !deleted" class="state-and-actions">
<div class="col-content">
<span class="state"><span :class="stateIcon"></span>{{ stateText }}</span>
<span class="state">
<template v-if="missingAttachment">
<span class="icon-circle-o"></span>
<span>{{ $t('submission.missingAttachment') }}</span>
</template>
<submission-review-state v-else
:value="submission.__system.reviewState" align/>
</span>
<span class="edits">
<template v-if="submission.__system.edits !== 0">
<span class="icon-pencil"></span>
Expand Down Expand Up @@ -76,15 +83,15 @@ except according to the terms contained in the LICENSE file.

<script>
import DateTime from '../date-time.vue';
import Spinner from '../spinner.vue';
import SubmissionReviewState from './review-state.vue';

import useReviewState from '../../composables/review-state';
import useRoutes from '../../composables/routes';
import { apiPaths } from '../../util/request';
import Spinner from '../spinner.vue';

export default {
name: 'SubmissionMetadataRow',
components: { DateTime, Spinner },
components: { DateTime, Spinner, SubmissionReviewState },
props: {
projectId: {
type: String,
Expand Down Expand Up @@ -114,26 +121,15 @@ export default {
awaitingResponse: Boolean
},
setup() {
const { reviewStateIcon } = useReviewState();
const { submissionPath } = useRoutes();
return { reviewStateIcon, submissionPath };
return { submissionPath };
},
computed: {
missingAttachment() {
const { __system } = this.submission;
return __system.reviewState == null &&
__system.attachmentsPresent !== __system.attachmentsExpected;
},
stateIcon() {
return this.missingAttachment
? 'icon-circle-o'
: this.reviewStateIcon(this.submission.__system.reviewState);
},
stateText() {
return this.missingAttachment
? this.$t('submission.missingAttachment')
: this.$t(`reviewState.${this.submission.__system.reviewState}`);
},
editPath() {
return apiPaths.editSubmission(
this.projectId,
Expand Down Expand Up @@ -171,17 +167,13 @@ export default {
.state {
margin-right: 15px;

.icon-comments { margin-right: $margin-right-icon; }
.icon-circle-o, .icon-dot-circle-o, .icon-pencil, .icon-check-circle, .icon-times-circle {
.icon-circle-o {
color: #999;
matthew-white marked this conversation as resolved.
Show resolved Hide resolved
margin-left: 1px;
margin-right: #{$margin-right-icon + 1px};
}

.icon-circle-o, .icon-comments { color: $color-warning; }
.icon-dot-circle-o { color: #999; }
.icon-pencil { color: #777; }
matthew-white marked this conversation as resolved.
Show resolved Hide resolved
.icon-check-circle { color: $color-success; }
.icon-times-circle { color: $color-danger; }
}
.edits {
color: #777;
Expand Down
70 changes: 70 additions & 0 deletions src/components/submission/review-state.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!--
Copyright 2024 ODK Central Developers
See the NOTICE file at the top-level directory of this distribution and at
https://github.com/getodk/central-frontend/blob/master/NOTICE.

This file is part of ODK Central. It is subject to the license terms in
the LICENSE file found in the top-level directory of this distribution and at
https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central,
including this file, may be copied, modified, propagated, or distributed
except according to the terms contained in the LICENSE file.
-->
<template>
<span :class="htmlClass">
<span :class="reviewStateIcon(value)"></span>
<span><slot>{{ $t(`reviewState.${value}`) }}</slot></span>
</span>
</template>

<script setup>
import { computed } from 'vue';

import useReviewState from '../../composables/review-state';

defineOptions({
name: 'SubmissionReviewState'
});
const props = defineProps({
value: String,
colorText: Boolean,
// Useful when there is a list or column of review states, with a review state
// on each row. In that case, specifying `true` for `align` will align the
// review state icons with each other. This is needed because different icons
// have different widths.
align: Boolean
});

const htmlClass = computed(() => {
const result = ['submission-review-state'];
if (props.value != null) result.push(props.value);
if (props.colorText) result.push('color-text');
if (props.align) result.push('align');
return result;
});

const { reviewStateIcon } = useReviewState();
</script>

<style lang="scss">
@import '../../assets/scss/variables';

.submission-review-state {
@mixin reviewStateColor($color) {
& span:first-child, &.color-text span:last-child { color: $color; }
}
@include reviewStateColor(#999);
&.hasIssues { @include reviewStateColor($color-warning); }
&.edited { @include reviewStateColor(#666); }
&.approved { @include reviewStateColor($color-success); }
&.rejected { @include reviewStateColor($color-danger); }

span:first-child { margin-right: $margin-right-icon; }

&.align {
.icon-dot-circle-o, .icon-pencil, .icon-check-circle, .icon-times-circle {
padding-left: 1px;
padding-right: 1px;
}
}
}
</style>
27 changes: 5 additions & 22 deletions src/components/submission/update-review-state.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ except according to the terms contained in the LICENSE file.
<label>
<input v-model="selectedState" type="radio"
:value="reviewState">
<span :class="reviewStateIcon(reviewState)"></span>
<span>{{ $t(`reviewState.${reviewState}`) }}</span>
<submission-review-state :value="reviewState" align/>
</label>
</div>
</div>
Expand Down Expand Up @@ -53,17 +52,17 @@ except according to the terms contained in the LICENSE file.
import Modal from '../modal.vue';
import Spinner from '../spinner.vue';
import MarkdownTextarea from '../markdown/textarea.vue';
import SubmissionReviewState from './review-state.vue';

import useRequest from '../../composables/request';
import useReviewState from '../../composables/review-state';
import { apiPaths } from '../../util/request';
import { noop } from '../../util/util';

const selectableStates = ['approved', 'hasIssues', 'rejected'];

export default {
name: 'SubmissionUpdateReviewState',
components: { Modal, Spinner, MarkdownTextarea },
components: { Modal, Spinner, MarkdownTextarea, SubmissionReviewState },
props: {
state: Boolean,
projectId: {
Expand All @@ -79,8 +78,7 @@ export default {
emits: ['hide', 'success'],
setup() {
const { request, awaitingResponse } = useRequest();
const { reviewStateIcon } = useReviewState();
return { request, awaitingResponse, reviewStateIcon };
return { request, awaitingResponse };
},
data() {
return {
Expand Down Expand Up @@ -135,24 +133,9 @@ export default {
</script>

<style lang="scss">
@import '../../assets/scss/variables';

#submission-update-review-state {
.form-group { margin-bottom: 0; }

$margin-left-icon: 2px;
.icon-comments {
margin-left: $margin-left-icon;
margin-right: $margin-right-icon;
}
.icon-check-circle, .icon-times-circle {
margin-left: #{$margin-left-icon + 1px};
margin-right: #{$margin-right-icon + 1px};
}

.icon-check-circle { color: $color-success; }
.icon-comments { color: $color-warning; }
.icon-times-circle { color: $color-danger; }
.submission-review-state { margin-left: 2px; }
}
</style>

Expand Down
4 changes: 4 additions & 0 deletions src/composables/review-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,9 @@ icons.set('received', icons.get(null));

export default always({
reviewStates,
// Most components should use the SubmissionReviewState component instead of
// this function. This function returns the icon class for the review state,
// but it doesn't style the icon. For example, it doesn't specify a color for
// the icon.
reviewStateIcon: (reviewState) => icons.get(reviewState)
});
Loading