diff --git a/api/child_api.py b/api/child_api.py index 9fe4ce6..334dfe7 100644 --- a/api/child_api.py +++ b/api/child_api.py @@ -285,7 +285,7 @@ def list_all_tasks(id): tasks.append(task_dict) tasks.sort(key=lambda t: (not t['assigned'], t['name'].lower())) - return jsonify({ 'tasks': tasks }), 200 + return jsonify({ 'tasks': tasks, 'count': len(tasks), 'list_type': 'task' }), 200 @child_api.route('/child//trigger-task', methods=['POST']) @@ -373,7 +373,8 @@ def list_all_rewards(id): return jsonify({ 'rewards': rewards, - 'rewards_count': len(rewards) + 'rewards_count': len(rewards), + 'list_type': 'reward' }), 200 @@ -680,5 +681,5 @@ def list_pending_rewards(): ) reward_responses.append(response.to_dict()) - return jsonify({'rewards': reward_responses}), 200 + return jsonify({'rewards': reward_responses, 'count': len(reward_responses), 'list_type': 'notification'}), 200 diff --git a/web/vue-app/src/common/models.ts b/web/vue-app/src/common/models.ts index 482a946..b6c2645 100644 --- a/web/vue-app/src/common/models.ts +++ b/web/vue-app/src/common/models.ts @@ -6,6 +6,7 @@ export interface Task { image_id: string | null image_url?: string | null // optional, for resolved URLs } +export const TASK_FIELDS = ['id', 'name', 'points', 'is_good', 'image_id'] as const export interface Child { id: string @@ -17,6 +18,7 @@ export interface Child { image_id: string | null image_url?: string | null // optional, for resolved URLs } +export const CHILD_FIELDS = ['id', 'name', 'age', 'tasks', 'rewards', 'points', 'image_id'] as const export interface Reward { id: string @@ -26,6 +28,7 @@ export interface Reward { image_id: string | null image_url?: string | null // optional, for resolved URLs } +export const REWARD_FIELDS = ['id', 'name', 'cost', 'points_needed', 'image_id'] as const export interface RewardStatus { id: string @@ -36,6 +39,14 @@ export interface RewardStatus { image_id: string | null image_url?: string | null // optional, for resolved URLs } +export const REWARD_STATUS_FIELDS = [ + 'id', + 'name', + 'points_needed', + 'cost', + 'redeeming', + 'image_id', +] as const export interface PendingReward { id: string @@ -48,6 +59,15 @@ export interface PendingReward { reward_image_id: string | null reward_image_url?: string | null // optional, for resolved URLs } +export const PENDING_REWARD_FIELDS = [ + 'id', + 'child_id', + 'child_name', + 'child_image_id', + 'reward_id', + 'reward_name', + 'reward_image_id', +] as const export interface Event { type: string diff --git a/web/vue-app/src/components/child/RewardAssignView.vue b/web/vue-app/src/components/child/RewardAssignView.vue index 1280553..6abb9ef 100644 --- a/web/vue-app/src/components/child/RewardAssignView.vue +++ b/web/vue-app/src/components/child/RewardAssignView.vue @@ -10,11 +10,18 @@ ref="rewardListRef" :fetchUrl="`/api/child/${childId}/list-all-rewards`" itemKey="rewards" - :itemFields="['id', 'name', 'cost', 'image_id']" + :itemFields="REWARD_FIELDS" imageField="image_id" selectable @loading-complete="(count) => (rewardCountRef = count)" - /> + :getItemClass="(item) => `reward`" + > + +
@@ -29,6 +36,7 @@ import { useRoute, useRouter } from 'vue-router' import ItemList from '../shared/ItemList.vue' import MessageBlock from '../shared/MessageBlock.vue' import '@/assets/actions-shared.css' +import { REWARD_FIELDS } from '@/common/models' const route = useRoute() const router = useRouter() @@ -90,4 +98,21 @@ function onCancel() { padding: 0; min-height: 0; } + +.name { + flex: 1; + text-align: left; + font-weight: 600; +} + +.value { + min-width: 60px; + text-align: right; + font-weight: 600; +} + +:deep(.reward) { + border-color: var(--list-item-border-reward); + background: var(--list-item-bg-reward); +} diff --git a/web/vue-app/src/components/child/TaskAssignView.vue b/web/vue-app/src/components/child/TaskAssignView.vue index a5d9976..b167276 100644 --- a/web/vue-app/src/components/child/TaskAssignView.vue +++ b/web/vue-app/src/components/child/TaskAssignView.vue @@ -10,11 +10,18 @@ ref="taskListRef" :fetchUrl="`/api/child/${childId}/list-all-tasks?type=${typeFilter}`" itemKey="tasks" - :itemFields="['id', 'name', 'points', 'is_good', 'image_id']" + :itemFields="TASK_FIELDS" imageField="image_id" selectable @loading-complete="(count) => (taskCountRef = count)" - /> + :getItemClass="(item) => ({ bad: !item.is_good, good: item.is_good })" + > + +
@@ -29,6 +36,7 @@ import { useRoute, useRouter } from 'vue-router' import ItemList from '../shared/ItemList.vue' import MessageBlock from '../shared/MessageBlock.vue' import '@/assets/actions-shared.css' +import { TASK_FIELDS } from '@/common/models' const route = useRoute() const router = useRouter() @@ -96,4 +104,25 @@ function onCancel() { padding: 0; min-height: 0; } + +:deep(.good) { + border-color: var(--list-item-border-good); + background: var(--list-item-bg-good); +} +:deep(.bad) { + border-color: var(--list-item-border-bad); + background: var(--list-item-bg-bad); +} + +.name { + flex: 1; + text-align: left; + font-weight: 600; +} + +.value { + min-width: 60px; + text-align: right; + font-weight: 600; +} diff --git a/web/vue-app/src/components/notification/NotificationList.vue b/web/vue-app/src/components/notification/NotificationList.vue deleted file mode 100644 index fb8ec62..0000000 --- a/web/vue-app/src/components/notification/NotificationList.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - - - diff --git a/web/vue-app/src/components/notification/NotificationView.vue b/web/vue-app/src/components/notification/NotificationView.vue index cfaa5fb..7abd7ae 100644 --- a/web/vue-app/src/components/notification/NotificationView.vue +++ b/web/vue-app/src/components/notification/NotificationView.vue @@ -1,20 +1,43 @@ @@ -29,4 +52,34 @@ function handleNotificationClick(item: PendingReward) { padding: 0; min-height: 0; } +.notification-centered { + display: flex; + justify-content: center; + margin-inline: auto; + align-items: center; +} +.child-info { + display: flex; + align-items: center; + gap: 0.4rem; + font-weight: 600; + color: var(--dialog-child-name); +} + +.reward-info { + display: flex; + align-items: center; + gap: 0.4rem; + margin-bottom: 0rem; + font-weight: 600; + color: var(--notification-reward-name); +} + +.requested-text { + margin: 0 0.7rem; + font-weight: 500; + color: var(--dialog-message); + font-size: 1.05rem; + white-space: nowrap; +} diff --git a/web/vue-app/src/components/reward/RewardView.vue b/web/vue-app/src/components/reward/RewardView.vue index 0b7ca47..cbbad5d 100644 --- a/web/vue-app/src/components/reward/RewardView.vue +++ b/web/vue-app/src/components/reward/RewardView.vue @@ -8,13 +8,20 @@ v-else fetchUrl="/api/reward/list" itemKey="rewards" - :itemFields="['id', 'name', 'cost', 'description', 'image_id']" + :itemFields="REWARD_FIELDS" imageField="image_id" deletable - @edit="(rewardId) => $router.push({ name: 'EditReward', params: { id: rewardId } })" + @clicked="(reward: Reward) => $router.push({ name: 'EditReward', params: { id: reward.id } })" @delete="confirmDeleteReward" @loading-complete="(count) => (rewardCountRef = count)" - /> + :getItemClass="(item) => `reward`" + > + + @@ -35,6 +42,8 @@ import MessageBlock from '@/components/shared/MessageBlock.vue' import '@/assets/button-shared.css' import FloatingActionButton from '../shared/FloatingActionButton.vue' import DeleteModal from '../shared/DeleteModal.vue' +import type { Reward } from '@/common/models' +import { REWARD_FIELDS } from '@/common/models' import '@/assets/view-shared.css' @@ -83,4 +92,21 @@ const createReward = () => { padding: 0; min-height: 0; } + +.name { + flex: 1; + text-align: left; + font-weight: 600; +} + +.value { + min-width: 60px; + text-align: right; + font-weight: 600; +} + +:deep(.reward) { + border-color: var(--list-item-border-reward); + background: var(--list-item-bg-reward); +} diff --git a/web/vue-app/src/components/shared/ItemList.vue b/web/vue-app/src/components/shared/ItemList.vue index 0f8305c..9331bc5 100644 --- a/web/vue-app/src/components/shared/ItemList.vue +++ b/web/vue-app/src/components/shared/ItemList.vue @@ -1,20 +1,21 @@ @@ -92,44 +95,49 @@ const handleDelete = (id: string) => {
{{ error }}
No items found.
-
-
- Item - {{ item.name }} - {{ item.points }} pts - {{ item.cost }} pts - - +
+
+ + + + List Item + 1 + +
+ + +
+
-
+