@cavulsqa/create 2.9.2 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/bin/create.mjs +18 -25
- package/lib/templates.mjs +56 -0
- package/package.json +13 -5
- package/templates/f7-app/CLAUDE.md +3 -0
- package/templates/f7-app/auto-imports.d.ts +15 -0
- package/templates/f7-app/package.json +6 -3
- package/templates/f7-app/src/App.vue +1 -0
- package/templates/f7-app/src/env.d.ts +1 -0
- package/templates/f7-app/src/main.ts +3 -0
- package/templates/f7-app/src/modules/demo/router/routes/demo.routes.ts +4 -16
- package/templates/f7-app/src/modules/home/router/routes/home.routes.ts +3 -11
- package/templates/f7-app/src/modules/settings/router/routes/settings.routes.ts +2 -6
- package/templates/f7-app/src/plugins/bootstrapError.ts +2 -0
- package/templates/f7-app/src/plugins/recorder.plugin.ts +155 -0
- package/templates/f7-app/src/router/global/global.routes.ts +2 -6
- package/templates/f7-app/src/shared/composables/useNavigationGuard.ts +48 -0
- package/templates/f7-app/src/shared/recorder/capacitorSink.ts +124 -0
- package/templates/f7-app/src/shared/utils/lazyRoute.ts +25 -0
- package/templates/f7-app/tests/lazyRoute.test.ts +30 -0
- package/templates/f7-app/tests/navigationGuard.test.ts +82 -0
- package/templates/f7-app/vite.config.ts +1 -0
- package/templates/m3e-app/.claude/rules/data-fetching.md +68 -0
- package/templates/m3e-app/.claude/rules/database.md +106 -0
- package/templates/m3e-app/.claude/rules/m3e-ui.md +48 -0
- package/templates/m3e-app/.claude/rules/modules.md +43 -0
- package/templates/m3e-app/.claude/rules/native.md +60 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/SKILL.md +68 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/color.md +44 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/components.md +325 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/layout.md +42 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/motion.md +51 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/shapes-type.md +48 -0
- package/templates/m3e-app/.claude/skills/m3-expressive/sources.md +53 -0
- package/templates/m3e-app/.claude/skills/module-architecture/SKILL.md +65 -0
- package/templates/m3e-app/.claude/skills/module-architecture/file-templates.md +178 -0
- package/templates/m3e-app/.claude/skills/reactive-data/SKILL.md +90 -0
- package/templates/m3e-app/.claude/skills/reactive-data/testing.md +55 -0
- package/templates/m3e-app/.env.example +32 -0
- package/templates/m3e-app/CLAUDE.md +93 -0
- package/templates/m3e-app/auto-imports.d.ts +851 -0
- package/templates/m3e-app/capacitor.config.ts +44 -0
- package/templates/m3e-app/components.d.ts +252 -0
- package/templates/m3e-app/index.html +16 -0
- package/templates/m3e-app/package.json +107 -0
- package/templates/m3e-app/src/App.vue +129 -0
- package/templates/m3e-app/src/app/pragmas.config.ts +35 -0
- package/templates/m3e-app/src/app/scroll.config.ts +20 -0
- package/templates/m3e-app/src/app/storage.config.ts +65 -0
- package/templates/m3e-app/src/app/tabs.ts +33 -0
- package/templates/m3e-app/src/app/theme.config.ts +51 -0
- package/templates/m3e-app/src/assets/css/app.css +18 -0
- package/templates/m3e-app/src/assets/css/base.css +53 -0
- package/templates/m3e-app/src/assets/css/layout/container-transform.css +113 -0
- package/templates/m3e-app/src/assets/css/layout/shell.css +69 -0
- package/templates/m3e-app/src/assets/css/layout/transitions.css +121 -0
- package/templates/m3e-app/src/assets/css/m3e.css +6 -0
- package/templates/m3e-app/src/assets/css/theme/framework7.css +18 -0
- package/templates/m3e-app/src/assets/css/theme/tailwind.css +365 -0
- package/templates/m3e-app/src/domains/benchmark/benchmark.dataset.ts +209 -0
- package/templates/m3e-app/src/domains/benchmark/benchmark.suite.ts +662 -0
- package/templates/m3e-app/src/domains/sales/sales.repository.ts +458 -0
- package/templates/m3e-app/src/env.d.ts +40 -0
- package/templates/m3e-app/src/locales/ar.json +1260 -0
- package/templates/m3e-app/src/locales/en.json +1260 -0
- package/templates/m3e-app/src/locales/fr.json +1260 -0
- package/templates/m3e-app/src/main.ts +41 -0
- package/templates/m3e-app/src/modules/demo/components/DemoBenchmark.vue +119 -0
- package/templates/m3e-app/src/modules/demo/components/DemoBusLog.vue +63 -0
- package/templates/m3e-app/src/modules/demo/components/DemoCreateOrderSheet.vue +203 -0
- package/templates/m3e-app/src/modules/demo/components/DemoMetricsSheet.vue +75 -0
- package/templates/m3e-app/src/modules/demo/components/DemoOrderList.vue +75 -0
- package/templates/m3e-app/src/modules/demo/components/DemoPipelineBenchmark.vue +65 -0
- package/templates/m3e-app/src/modules/demo/components/DemoStatCards.vue +71 -0
- package/templates/m3e-app/src/modules/demo/composables/useBenchmark.ts +139 -0
- package/templates/m3e-app/src/modules/demo/composables/useOrderStatus.ts +64 -0
- package/templates/m3e-app/src/modules/demo/composables/useReactiveDemo.ts +171 -0
- package/templates/m3e-app/src/modules/demo/router/routes/demo.routes.ts +22 -0
- package/templates/m3e-app/src/modules/demo/views/DemoView.vue +210 -0
- package/templates/m3e-app/src/modules/demo/views/OrderDetailView.vue +136 -0
- package/templates/m3e-app/src/modules/demo/views/OrderSearchView.vue +74 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryBlock.vue +16 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryCarouselTile.vue +71 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryCustomerForm.vue +76 -0
- package/templates/m3e-app/src/modules/gallery/components/GalleryProofOfDelivery.vue +69 -0
- package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaDay.vue +106 -0
- package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaMonth.vue +40 -0
- package/templates/m3e-app/src/modules/gallery/components/agenda/AgendaVisitList.vue +45 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatAttachSheet.vue +106 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatContactPicker.vue +85 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatInviteComposer.vue +131 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatPollComposer.vue +148 -0
- package/templates/m3e-app/src/modules/gallery/components/chat/ChatRecentPhotos.vue +62 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/InputsAccount.vue +79 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/InputsTags.vue +53 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/InputsVerification.vue +73 -0
- package/templates/m3e-app/src/modules/gallery/components/inputs/PasswordStrength.vue +37 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryButtons.vue +128 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryCarousels.vue +236 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryCharts.vue +84 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryFabs.vue +70 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryInputs.vue +219 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryNavigation.vue +141 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryOverlays.vue +368 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryPickers.vue +175 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryProgress.vue +118 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryScale.vue +103 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GallerySelection.vue +219 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryShapes.vue +48 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GallerySurfaces.vue +213 -0
- package/templates/m3e-app/src/modules/gallery/components/sections/GalleryTables.vue +185 -0
- package/templates/m3e-app/src/modules/gallery/components/surfaces/SurfacesContainerTransform.vue +46 -0
- package/templates/m3e-app/src/modules/gallery/composables/chatTeam.ts +18 -0
- package/templates/m3e-app/src/modules/gallery/composables/composeKind.ts +2 -0
- package/templates/m3e-app/src/modules/gallery/composables/currentPosition.ts +35 -0
- package/templates/m3e-app/src/modules/gallery/composables/demoOrders.ts +29 -0
- package/templates/m3e-app/src/modules/gallery/composables/featuredStories.ts +73 -0
- package/templates/m3e-app/src/modules/gallery/composables/fieldFormats.ts +36 -0
- package/templates/m3e-app/src/modules/gallery/composables/openExternal.ts +11 -0
- package/templates/m3e-app/src/modules/gallery/composables/routePlan.ts +90 -0
- package/templates/m3e-app/src/modules/gallery/composables/useAgenda.ts +61 -0
- package/templates/m3e-app/src/modules/gallery/composables/useChatCustomers.ts +31 -0
- package/templates/m3e-app/src/modules/gallery/composables/useChatDemo.ts +299 -0
- package/templates/m3e-app/src/modules/gallery/composables/useGallerySections.ts +230 -0
- package/templates/m3e-app/src/modules/gallery/composables/useLocalAttachments.ts +71 -0
- package/templates/m3e-app/src/modules/gallery/composables/useLocationShare.ts +38 -0
- package/templates/m3e-app/src/modules/gallery/composables/usePhotoScenes.ts +82 -0
- package/templates/m3e-app/src/modules/gallery/composables/useTreeDemo.ts +67 -0
- package/templates/m3e-app/src/modules/gallery/composables/wilayas.ts +67 -0
- package/templates/m3e-app/src/modules/gallery/router/routes/gallery.routes.ts +52 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryAgendaView.vue +117 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryChatView.vue +292 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryContactsView.vue +77 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryFeaturedView.vue +60 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryLoginView.vue +135 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryOnboardingView.vue +116 -0
- package/templates/m3e-app/src/modules/gallery/views/GallerySectionView.vue +27 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryTabsView.vue +135 -0
- package/templates/m3e-app/src/modules/gallery/views/GalleryView.vue +50 -0
- package/templates/m3e-app/src/modules/home/components/HomeHero.vue +57 -0
- package/templates/m3e-app/src/modules/home/composables/useHomeFeatures.ts +128 -0
- package/templates/m3e-app/src/modules/home/composables/useOpenFeature.ts +22 -0
- package/templates/m3e-app/src/modules/home/router/routes/home.routes.ts +21 -0
- package/templates/m3e-app/src/modules/home/views/FeatureDetailView.vue +66 -0
- package/templates/m3e-app/src/modules/home/views/HomeView.vue +47 -0
- package/templates/m3e-app/src/modules/settings/components/RolePalette.vue +38 -0
- package/templates/m3e-app/src/modules/settings/components/SeedSwatches.vue +41 -0
- package/templates/m3e-app/src/modules/settings/components/SettingsChoice.vue +43 -0
- package/templates/m3e-app/src/modules/settings/components/StudioPreview.vue +52 -0
- package/templates/m3e-app/src/modules/settings/router/routes/settings.routes.ts +17 -0
- package/templates/m3e-app/src/modules/settings/types.ts +7 -0
- package/templates/m3e-app/src/modules/settings/views/ColorStudioView.vue +166 -0
- package/templates/m3e-app/src/modules/settings/views/SettingsView.vue +157 -0
- package/templates/m3e-app/src/plugins/bootstrapError.ts +62 -0
- package/templates/m3e-app/src/plugins/capacitor/index.ts +14 -0
- package/templates/m3e-app/src/plugins/capacitor/useAndroidBackButton.ts +36 -0
- package/templates/m3e-app/src/plugins/capacitor/useKeyboard.ts +48 -0
- package/templates/m3e-app/src/plugins/capacitor/useSplashScreen.ts +43 -0
- package/templates/m3e-app/src/plugins/capacitor/useStatusBar.ts +16 -0
- package/templates/m3e-app/src/plugins/framework7.plugin.ts +36 -0
- package/templates/m3e-app/src/plugins/i18n.plugin.ts +87 -0
- package/templates/m3e-app/src/plugins/m3e.plugin.ts +22 -0
- package/templates/m3e-app/src/plugins/seed.plugin.ts +7 -0
- package/templates/m3e-app/src/plugins/sqlite.plugin.ts +32 -0
- package/templates/m3e-app/src/router/global/global.routes.ts +12 -0
- package/templates/m3e-app/src/router/index.ts +20 -0
- package/templates/m3e-app/src/shared/components/error/404.vue +13 -0
- package/templates/m3e-app/src/shared/components/layout/EmptyState.vue +30 -0
- package/templates/m3e-app/src/shared/components/layout/SectionHeader.vue +10 -0
- package/templates/m3e-app/src/shared/components/page/AppPage.vue +104 -0
- package/templates/m3e-app/src/shared/composables/navigation/useActiveTab.ts +39 -0
- package/templates/m3e-app/src/shared/composables/navigation/useContainerTransform.ts +117 -0
- package/templates/m3e-app/src/shared/composables/navigation/useNavigationGuard.ts +48 -0
- package/templates/m3e-app/src/shared/composables/navigation/useNavigationVisibility.ts +64 -0
- package/templates/m3e-app/src/shared/composables/navigation/useViewRouter.ts +25 -0
- package/templates/m3e-app/src/shared/composables/navigation/useWindowClass.ts +22 -0
- package/templates/m3e-app/src/shared/composables/theme/useThemeSettings.ts +155 -0
- package/templates/m3e-app/src/shared/database/candidates/capacitorSqlite.ts +36 -0
- package/templates/m3e-app/src/shared/database/candidates/index.ts +4 -0
- package/templates/m3e-app/src/shared/database/candidates/opfsSahPool.ts +25 -0
- package/templates/m3e-app/src/shared/database/candidates/types.ts +56 -0
- package/templates/m3e-app/src/shared/database/candidates/waSqlite.ts +56 -0
- package/templates/m3e-app/src/shared/database/database.ts +219 -0
- package/templates/m3e-app/src/shared/database/index.ts +3 -0
- package/templates/m3e-app/src/shared/database/migrations.ts +223 -0
- package/templates/m3e-app/src/shared/database/opfs.worker.ts +5 -0
- package/templates/m3e-app/src/shared/database/queries.ts +26 -0
- package/templates/m3e-app/src/shared/database/schema.ts +153 -0
- package/templates/m3e-app/src/shared/database/storage.ts +64 -0
- package/templates/m3e-app/src/shared/database/wa.worker.ts +5 -0
- package/templates/m3e-app/src/shared/utils/lazyRoute.ts +41 -0
- package/templates/m3e-app/src/shared/utils/resolvers/resolvers.ts +42 -0
- package/templates/m3e-app/src/shared/utils/textDirection.ts +22 -0
- package/templates/m3e-app/src/shared/utils/theme/themeSettings.ts +59 -0
- package/templates/m3e-app/src/shared/utils/tone.ts +8 -0
- package/templates/m3e-app/tests/benchmark.suite.test.ts +104 -0
- package/templates/m3e-app/tests/containerTransform.test.ts +58 -0
- package/templates/m3e-app/tests/fieldFormats.test.ts +25 -0
- package/templates/m3e-app/tests/lazyRoute.test.ts +30 -0
- package/templates/m3e-app/tests/locales.test.ts +73 -0
- package/templates/m3e-app/tests/migrations.test.ts +74 -0
- package/templates/m3e-app/tests/navigationGuard.test.ts +82 -0
- package/templates/m3e-app/tests/openDatabase.test.ts +54 -0
- package/templates/m3e-app/tests/sales.repository.test.ts +329 -0
- package/templates/m3e-app/tests/storage.test.ts +117 -0
- package/templates/m3e-app/tests/textDirection.test.ts +11 -0
- package/templates/m3e-app/tooling/linkedPackages.ts +112 -0
- package/templates/m3e-app/tsconfig.json +21 -0
- package/templates/m3e-app/vite.config.ts +144 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<AppPage :title="order?.reference ?? t('demo.order')" :subtitle="order?.customerName" back>
|
|
3
|
+
<template v-if="order" #actions>
|
|
4
|
+
<M3IconButton :label="t('demo.delete')" @click="confirmDelete">
|
|
5
|
+
<i-ms-delete-outline-rounded />
|
|
6
|
+
</M3IconButton>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<template v-if="order">
|
|
10
|
+
<section class="mx-4 mt-2 flex items-center gap-4 rounded-xl bg-surface-container-low p-5">
|
|
11
|
+
<M3ShapeMorph :shape="look.shape" class="size-16 transition-colors" :class="look.fill">
|
|
12
|
+
<component :is="look.icon" class="size-7" :class="look.ink" />
|
|
13
|
+
</M3ShapeMorph>
|
|
14
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
15
|
+
<span class="type-label-large text-on-surface-variant"
|
|
16
|
+
>{{ t(look.labelKey) }} · {{ order.city }}</span
|
|
17
|
+
>
|
|
18
|
+
<span class="type-headline-medium-emphasized font-rounded tabular-nums">{{
|
|
19
|
+
formatMoney(order.totalCents)
|
|
20
|
+
}}</span>
|
|
21
|
+
</div>
|
|
22
|
+
</section>
|
|
23
|
+
|
|
24
|
+
<div v-if="order.tags.length" class="flex flex-wrap gap-2 px-4 pt-3">
|
|
25
|
+
<M3Chip v-for="tag in order.tags" :key="tag" :label="tag" />
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<SectionHeader :title="t('demo.lineItems')" />
|
|
29
|
+
<M3List variant="segmented" inset>
|
|
30
|
+
<M3ListItem
|
|
31
|
+
v-for="line in order.lines"
|
|
32
|
+
:key="line.id"
|
|
33
|
+
:headline="line.productName"
|
|
34
|
+
:supporting="`${line.quantity} × ${formatMoney(line.unitPriceCents)}`"
|
|
35
|
+
:trailing-text="formatMoney(line.lineTotalCents)"
|
|
36
|
+
/>
|
|
37
|
+
</M3List>
|
|
38
|
+
<div class="h-16" aria-hidden="true" />
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<EmptyState v-else shape="ghostish" :headline="t('demo.order')" :text="t('demo.orderGone')" />
|
|
42
|
+
|
|
43
|
+
<template v-if="order" #fixed>
|
|
44
|
+
<M3DockedToolbar class="app-detail-toolbar" :label="t('demo.status')">
|
|
45
|
+
<M3ButtonGroup variant="connected" size="s" :label="t('demo.status')">
|
|
46
|
+
<M3Button
|
|
47
|
+
v-for="status in ORDER_STATUSES"
|
|
48
|
+
:key="status"
|
|
49
|
+
variant="tonal"
|
|
50
|
+
toggle
|
|
51
|
+
:selected="order.status === status"
|
|
52
|
+
@update:selected="apply(status)"
|
|
53
|
+
>
|
|
54
|
+
{{ t(STATUS_LOOK[status].labelKey) }}
|
|
55
|
+
</M3Button>
|
|
56
|
+
</M3ButtonGroup>
|
|
57
|
+
</M3DockedToolbar>
|
|
58
|
+
</template>
|
|
59
|
+
</AppPage>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
import type { Router } from "framework7/types";
|
|
64
|
+
import {
|
|
65
|
+
deleteOrder,
|
|
66
|
+
loadOrderDetail,
|
|
67
|
+
setOrderStatus,
|
|
68
|
+
type OrderDetail,
|
|
69
|
+
} from "@/domains/sales/sales.repository";
|
|
70
|
+
import {
|
|
71
|
+
ORDER_STATUSES,
|
|
72
|
+
STATUS_LOOK,
|
|
73
|
+
formatMoney,
|
|
74
|
+
statusLook,
|
|
75
|
+
type OrderStatus,
|
|
76
|
+
} from "@/modules/demo/composables/useOrderStatus";
|
|
77
|
+
import { getDatabase, rdb } from "@/shared/database/database";
|
|
78
|
+
import { useReactiveQuery } from "@/shared/database/queries";
|
|
79
|
+
|
|
80
|
+
const { t } = useI18n();
|
|
81
|
+
const props = defineProps<{ f7route: Router.Route; f7router: Router.Router }>();
|
|
82
|
+
const dialog = useDialog();
|
|
83
|
+
const snackbar = useSnackbar();
|
|
84
|
+
|
|
85
|
+
const orderId = Number(props.f7route.params.id ?? 0);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reads five tables, so any write in that set refreshes this page - including the status changes
|
|
89
|
+
* made from the toolbar below, which is why nothing here calls refetch by hand.
|
|
90
|
+
*/
|
|
91
|
+
const query = useReactiveQuery(() => loadOrderDetail(getDatabase().db, orderId), {
|
|
92
|
+
tables: ["sales_order", "order_line", "customer", "product", "customer_tag"],
|
|
93
|
+
queryKey: ["demo:order", orderId],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const order = computed<OrderDetail | null>(() => query.data.value ?? null);
|
|
97
|
+
const look = computed(() => statusLook(order.value?.status ?? "draft"));
|
|
98
|
+
|
|
99
|
+
async function apply(status: OrderStatus) {
|
|
100
|
+
if (!order.value || order.value.status === status) return;
|
|
101
|
+
try {
|
|
102
|
+
await setOrderStatus(rdb, orderId, status);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error("[demo] status change failed", error);
|
|
105
|
+
void snackbar.show({ message: t("demo.actionFailed"), duration: "long" });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function confirmDelete() {
|
|
110
|
+
const confirmed = await dialog.confirm({
|
|
111
|
+
headline: t("demo.delete"),
|
|
112
|
+
text: t("demo.deleteConfirm"),
|
|
113
|
+
confirmLabel: t("demo.delete"),
|
|
114
|
+
dismissLabel: t("demo.cancel"),
|
|
115
|
+
destructive: true,
|
|
116
|
+
});
|
|
117
|
+
if (!confirmed || !order.value) return;
|
|
118
|
+
const reference = order.value.reference;
|
|
119
|
+
try {
|
|
120
|
+
await deleteOrder(rdb, orderId);
|
|
121
|
+
props.f7router.back();
|
|
122
|
+
void snackbar.show(t("demo.deleted", { reference }));
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error("[demo] delete failed", error);
|
|
125
|
+
void snackbar.show({ message: t("demo.actionFailed"), duration: "long" });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
<style scoped>
|
|
131
|
+
.app-detail-toolbar {
|
|
132
|
+
position: absolute;
|
|
133
|
+
inset: auto 0 0;
|
|
134
|
+
z-index: 10;
|
|
135
|
+
}
|
|
136
|
+
</style>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<AppPage :title="t('demo.searchTitle')" back>
|
|
3
|
+
<div class="sticky top-[calc(64px+env(safe-area-inset-top))] z-[2] bg-surface px-4 pt-1 pb-3">
|
|
4
|
+
<M3SearchBar
|
|
5
|
+
v-model="term"
|
|
6
|
+
:placeholder="t('demo.searchPlaceholder')"
|
|
7
|
+
:clear-label="t('gallery.inputs.clear')"
|
|
8
|
+
:autofocus="!term"
|
|
9
|
+
/>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<SectionHeader
|
|
13
|
+
:title="
|
|
14
|
+
term ? t('demo.matches', { count: results.length }, results.length) : t('demo.allOrders')
|
|
15
|
+
"
|
|
16
|
+
/>
|
|
17
|
+
|
|
18
|
+
<M3List v-if="results.length" variant="segmented" inset>
|
|
19
|
+
<M3ListItem
|
|
20
|
+
v-for="order in results"
|
|
21
|
+
:key="order.id"
|
|
22
|
+
clickable
|
|
23
|
+
:headline="order.reference"
|
|
24
|
+
:supporting="`${order.customerName} · ${order.city}`"
|
|
25
|
+
:trailing-text="formatMoney(order.totalCents)"
|
|
26
|
+
@click="open(f7router, $event, `/demo/order/${String(order.id)}/`)"
|
|
27
|
+
>
|
|
28
|
+
<template #leading>
|
|
29
|
+
<M3Shape
|
|
30
|
+
:shape="statusLook(order.status).shape"
|
|
31
|
+
class="size-10"
|
|
32
|
+
:class="statusLook(order.status).classes"
|
|
33
|
+
>
|
|
34
|
+
<component :is="statusLook(order.status).icon" class="size-5" />
|
|
35
|
+
</M3Shape>
|
|
36
|
+
</template>
|
|
37
|
+
</M3ListItem>
|
|
38
|
+
</M3List>
|
|
39
|
+
|
|
40
|
+
<EmptyState
|
|
41
|
+
v-else
|
|
42
|
+
shape="sunny"
|
|
43
|
+
:headline="term ? t('demo.noMatchesTitle') : t('demo.emptyTitle')"
|
|
44
|
+
:text="term ? t('demo.noMatches') : t('demo.empty')"
|
|
45
|
+
>
|
|
46
|
+
<template #icon><i-ms-search-rounded class="size-10" /></template>
|
|
47
|
+
</EmptyState>
|
|
48
|
+
</AppPage>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
import type { Router } from "framework7/types";
|
|
53
|
+
import { searchOrders, type OrderRow } from "@/domains/sales/sales.repository";
|
|
54
|
+
import { formatMoney, statusLook } from "@/modules/demo/composables/useOrderStatus";
|
|
55
|
+
import { getDatabase } from "@/shared/database/database";
|
|
56
|
+
import { useReactiveQuery } from "@/shared/database/queries";
|
|
57
|
+
|
|
58
|
+
const props = defineProps<{ f7route: Router.Route; f7router: Router.Router }>();
|
|
59
|
+
const { t } = useI18n();
|
|
60
|
+
const { open } = useContainerTransform();
|
|
61
|
+
const term = ref(typeof props.f7route.query.q === "string" ? props.f7route.query.q : "");
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Populated before anything is typed - an empty search screen gives no sense of what is
|
|
65
|
+
* searchable. The term is in the key, so typing re-runs the query through its own debounce.
|
|
66
|
+
*/
|
|
67
|
+
const query = useReactiveQuery(() => searchOrders(getDatabase().db, term.value), {
|
|
68
|
+
tables: ["sales_order", "order_line", "customer"],
|
|
69
|
+
queryKey: ["demo:search", term],
|
|
70
|
+
debounce: 200,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const results = computed<OrderRow[]>(() => query.data.value ?? []);
|
|
74
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="mx-4 mt-3 flex flex-col gap-4 rounded-lg bg-surface-container-low p-4 first:mt-1">
|
|
3
|
+
<header class="flex flex-col gap-0.5">
|
|
4
|
+
<h3 class="type-title-small-emphasized m-0 text-on-surface">{{ props.title }}</h3>
|
|
5
|
+
<p v-if="props.note" class="type-body-small m-0 text-on-surface-variant">{{ props.note }}</p>
|
|
6
|
+
</header>
|
|
7
|
+
<div :class="props.stack ? 'flex flex-col gap-4' : 'flex flex-wrap items-center gap-3'">
|
|
8
|
+
<slot />
|
|
9
|
+
</div>
|
|
10
|
+
</section>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
/** One demonstrated variant family: a title, the spec point it shows, and the live components. */
|
|
15
|
+
const props = defineProps<{ title: string; note?: string; stack?: boolean }>();
|
|
16
|
+
</script>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class="gallery-tile relative block size-full overflow-hidden text-start"
|
|
5
|
+
:style="{
|
|
6
|
+
background: `linear-gradient(135deg, var(--md-sys-color-${props.from}), var(--md-sys-color-${props.to}))`,
|
|
7
|
+
}"
|
|
8
|
+
:aria-label="props.title"
|
|
9
|
+
@click="emit('select')"
|
|
10
|
+
>
|
|
11
|
+
<M3ShapeMorph
|
|
12
|
+
:shape="props.shape"
|
|
13
|
+
class="gallery-tile__shape absolute size-56 opacity-50"
|
|
14
|
+
:style="{ color: `var(--md-sys-color-${props.to})` }"
|
|
15
|
+
/>
|
|
16
|
+
<component
|
|
17
|
+
:is="props.icon"
|
|
18
|
+
class="gallery-tile__icon absolute left-1/2 top-1/2 size-16"
|
|
19
|
+
:style="{ color: `var(--md-sys-color-on-${props.from})` }"
|
|
20
|
+
/>
|
|
21
|
+
<span
|
|
22
|
+
class="gallery-tile__label type-title-medium-emphasized absolute bottom-4 whitespace-nowrap"
|
|
23
|
+
:style="{ color: `var(--md-sys-color-on-${props.from})` }"
|
|
24
|
+
>{{ props.title }}</span
|
|
25
|
+
>
|
|
26
|
+
</button>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import type { MaterialShapeName } from "@cavulsqa/m3e";
|
|
31
|
+
import type { Component } from "vue";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A carousel tile drawn from the theme: a tonal gradient, a shape and an icon centred on the full
|
|
35
|
+
* item so they parallax under the mask, and a label that slides with the mask's start edge and
|
|
36
|
+
* fades as the item compresses - Compose's FadingMultiBrowse sample.
|
|
37
|
+
*/
|
|
38
|
+
const props = defineProps<{
|
|
39
|
+
title: string;
|
|
40
|
+
icon: Component;
|
|
41
|
+
shape: MaterialShapeName;
|
|
42
|
+
from: string;
|
|
43
|
+
to: string;
|
|
44
|
+
}>();
|
|
45
|
+
|
|
46
|
+
const emit = defineEmits<{ select: [] }>();
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<style scoped>
|
|
50
|
+
.gallery-tile {
|
|
51
|
+
border: 0;
|
|
52
|
+
padding: 0;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.gallery-tile__shape {
|
|
57
|
+
right: -48px;
|
|
58
|
+
top: -40px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.gallery-tile__icon {
|
|
62
|
+
translate: -50% -50%;
|
|
63
|
+
opacity: 0.85;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.gallery-tile__label {
|
|
67
|
+
left: 0;
|
|
68
|
+
transform: translateX(calc(var(--m3-carousel-mask-start, 0px) + 16px));
|
|
69
|
+
opacity: clamp(0, calc((var(--m3-carousel-item-progress, 1) - 0.55) * 3), 1);
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<M3FullScreenDialog
|
|
3
|
+
v-model:open="open"
|
|
4
|
+
:title="t('gallery.overlays.newCustomer')"
|
|
5
|
+
:confirm-label="t('gallery.overlays.save')"
|
|
6
|
+
:confirm-disabled="!form.name.trim()"
|
|
7
|
+
:close-label="t('shell.dismiss')"
|
|
8
|
+
:dismissible="!dirty"
|
|
9
|
+
@confirm="save"
|
|
10
|
+
@close="confirmDiscard"
|
|
11
|
+
>
|
|
12
|
+
<div class="flex flex-col gap-4">
|
|
13
|
+
<p v-if="restored" class="type-body-small m-0 text-on-surface-variant">
|
|
14
|
+
{{ t("gallery.overlays.draftRestored") }}
|
|
15
|
+
</p>
|
|
16
|
+
<M3TextField
|
|
17
|
+
v-model="form.name"
|
|
18
|
+
:label="t('gallery.overlays.customerName')"
|
|
19
|
+
autocomplete="organization"
|
|
20
|
+
/>
|
|
21
|
+
<M3TextField
|
|
22
|
+
v-model="form.phone"
|
|
23
|
+
:label="t('gallery.overlays.phone')"
|
|
24
|
+
type="tel"
|
|
25
|
+
inputmode="tel"
|
|
26
|
+
autocomplete="tel"
|
|
27
|
+
/>
|
|
28
|
+
<M3ExposedDropdown
|
|
29
|
+
v-model="form.wilaya"
|
|
30
|
+
editable
|
|
31
|
+
:label="t('gallery.inputs.wilaya')"
|
|
32
|
+
:options="WILAYA_OPTIONS"
|
|
33
|
+
:no-results-text="t('gallery.inputs.noMatches')"
|
|
34
|
+
/>
|
|
35
|
+
<M3TextField v-model="form.note" :label="t('gallery.overlays.note')" multiline :rows="4" />
|
|
36
|
+
</div>
|
|
37
|
+
</M3FullScreenDialog>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { useFormDraft } from "@cavulsqa/m3e-vue";
|
|
42
|
+
import { WILAYA_OPTIONS } from "@/modules/gallery/composables/wilayas";
|
|
43
|
+
|
|
44
|
+
const open = defineModel<boolean>("open", { default: false });
|
|
45
|
+
const emit = defineEmits<{ saved: [name: string] }>();
|
|
46
|
+
const { t } = useI18n();
|
|
47
|
+
const dialog = useDialog();
|
|
48
|
+
|
|
49
|
+
const blank = () => ({ name: "", phone: "", wilaya: null as number | null, note: "" });
|
|
50
|
+
const form = reactive(blank());
|
|
51
|
+
const dirty = computed(() => Boolean(form.name || form.phone || form.note) || form.wilaya !== null);
|
|
52
|
+
|
|
53
|
+
const { restored, clear } = useFormDraft("gallery.customer", form);
|
|
54
|
+
|
|
55
|
+
function save() {
|
|
56
|
+
emit("saved", form.name.trim());
|
|
57
|
+
Object.assign(form, blank());
|
|
58
|
+
void clear();
|
|
59
|
+
open.value = false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function confirmDiscard() {
|
|
63
|
+
if (!dirty.value) return;
|
|
64
|
+
const discard = await dialog.confirm({
|
|
65
|
+
headline: t("gallery.overlays.discardTitle"),
|
|
66
|
+
text: t("gallery.overlays.discardText"),
|
|
67
|
+
confirmLabel: t("gallery.overlays.discard"),
|
|
68
|
+
dismissLabel: t("gallery.overlays.keepEditing"),
|
|
69
|
+
destructive: true,
|
|
70
|
+
});
|
|
71
|
+
if (!discard) return;
|
|
72
|
+
Object.assign(form, blank());
|
|
73
|
+
void clear();
|
|
74
|
+
open.value = false;
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<GalleryBlock
|
|
3
|
+
:title="t('gallery.inputs.signature')"
|
|
4
|
+
:note="t('gallery.inputs.signatureNote')"
|
|
5
|
+
stack
|
|
6
|
+
>
|
|
7
|
+
<M3SignaturePad
|
|
8
|
+
ref="pad"
|
|
9
|
+
v-model:strokes="proof.strokes"
|
|
10
|
+
:label="t('gallery.inputs.signatureLabel')"
|
|
11
|
+
:placeholder="t('gallery.inputs.signHere')"
|
|
12
|
+
:height="180"
|
|
13
|
+
/>
|
|
14
|
+
<div class="flex flex-wrap gap-2">
|
|
15
|
+
<M3Button variant="text" size="s" :disabled="!proof.strokes.length" @click="pad?.undo()">
|
|
16
|
+
{{ t("gallery.inputs.undo") }}
|
|
17
|
+
</M3Button>
|
|
18
|
+
<M3Button variant="text" size="s" :disabled="!proof.strokes.length" @click="pad?.clear()">
|
|
19
|
+
{{ t("gallery.inputs.clearSignature") }}
|
|
20
|
+
</M3Button>
|
|
21
|
+
</div>
|
|
22
|
+
<M3TextField
|
|
23
|
+
v-model="proof.name"
|
|
24
|
+
:label="t('gallery.inputs.receivedBy')"
|
|
25
|
+
:supporting="t('gallery.inputs.receivedByNote')"
|
|
26
|
+
autocomplete="name"
|
|
27
|
+
/>
|
|
28
|
+
<M3Button :disabled="!ready" @click="confirm">{{
|
|
29
|
+
t("gallery.inputs.confirmDelivery")
|
|
30
|
+
}}</M3Button>
|
|
31
|
+
<img
|
|
32
|
+
v-if="exported"
|
|
33
|
+
:src="exported"
|
|
34
|
+
:alt="t('gallery.inputs.exported')"
|
|
35
|
+
class="w-full rounded-lg border border-outline-variant"
|
|
36
|
+
/>
|
|
37
|
+
</GalleryBlock>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { useFormDraft, type SignatureStroke } from "@cavulsqa/m3e-vue";
|
|
42
|
+
import GalleryBlock from "@/modules/gallery/components/GalleryBlock.vue";
|
|
43
|
+
|
|
44
|
+
interface SignaturePad {
|
|
45
|
+
undo: () => void;
|
|
46
|
+
clear: () => void;
|
|
47
|
+
toDataURL: (type?: string, background?: string) => string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { t } = useI18n();
|
|
51
|
+
const snackbar = useSnackbar();
|
|
52
|
+
const pad = useTemplateRef<SignaturePad>("pad");
|
|
53
|
+
const proof = reactive({ strokes: [] as SignatureStroke[], name: "" });
|
|
54
|
+
const exported = ref("");
|
|
55
|
+
const { clear: clearDraft } = useFormDraft("gallery.proof", proof);
|
|
56
|
+
|
|
57
|
+
const ready = computed(() => proof.strokes.length > 0 && proof.name.trim().length > 0);
|
|
58
|
+
|
|
59
|
+
function confirm() {
|
|
60
|
+
const surface = getComputedStyle(document.documentElement).getPropertyValue(
|
|
61
|
+
"--md-sys-color-surface",
|
|
62
|
+
);
|
|
63
|
+
exported.value = pad.value?.toDataURL("image/png", surface.trim() || "#ffffff") ?? "";
|
|
64
|
+
void snackbar.show(t("gallery.inputs.delivered", { name: proof.name.trim() }));
|
|
65
|
+
proof.strokes = [];
|
|
66
|
+
proof.name = "";
|
|
67
|
+
void clearDraft();
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="swipe" class="agenda-day">
|
|
3
|
+
<h2 class="type-title-medium m-0 px-6 pt-4 text-on-surface">{{ heading }}</h2>
|
|
4
|
+
<M3DayTimeline
|
|
5
|
+
ref="timeline"
|
|
6
|
+
:events="events"
|
|
7
|
+
:start-hour="7"
|
|
8
|
+
:end-hour="19"
|
|
9
|
+
:hour-height="72"
|
|
10
|
+
:locale="locale"
|
|
11
|
+
:current="day === today"
|
|
12
|
+
:label="heading"
|
|
13
|
+
:event-label="eventLabel"
|
|
14
|
+
@select="announce"
|
|
15
|
+
>
|
|
16
|
+
<template #event="{ event, start, end, compact }">
|
|
17
|
+
<span class="type-title-small truncate">{{ event.visit.customer }}</span>
|
|
18
|
+
<span class="type-body-small truncate opacity-80">
|
|
19
|
+
{{ compact ? start : `${start} – ${end} · ${event.visit.city}` }}
|
|
20
|
+
</span>
|
|
21
|
+
</template>
|
|
22
|
+
</M3DayTimeline>
|
|
23
|
+
<EmptyState
|
|
24
|
+
v-if="!events.length"
|
|
25
|
+
shape="sunny"
|
|
26
|
+
:headline="t('gallery.agenda.weekend')"
|
|
27
|
+
:text="t('gallery.agenda.weekendText')"
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
import {
|
|
34
|
+
formatMinutes,
|
|
35
|
+
minutesOfDay,
|
|
36
|
+
useSnackbar,
|
|
37
|
+
useSwipeStep,
|
|
38
|
+
type TimelineEvent,
|
|
39
|
+
type TimelineTone,
|
|
40
|
+
} from "@cavulsqa/m3e-vue";
|
|
41
|
+
import type { Visit, VisitState } from "@/modules/gallery/composables/routePlan";
|
|
42
|
+
|
|
43
|
+
interface VisitEvent extends TimelineEvent {
|
|
44
|
+
visit: Visit;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const emit = defineEmits<{ step: [days: 1 | -1] }>();
|
|
48
|
+
|
|
49
|
+
const props = defineProps<{
|
|
50
|
+
day: string;
|
|
51
|
+
today: string;
|
|
52
|
+
heading: string;
|
|
53
|
+
visits: readonly Visit[];
|
|
54
|
+
}>();
|
|
55
|
+
|
|
56
|
+
const TONE: Record<VisitState, TimelineTone> = {
|
|
57
|
+
done: "surface",
|
|
58
|
+
next: "tertiary",
|
|
59
|
+
planned: "primary",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const { t, locale } = useI18n();
|
|
63
|
+
const snackbar = useSnackbar();
|
|
64
|
+
const swipe = useTemplateRef<HTMLElement>("swipe");
|
|
65
|
+
|
|
66
|
+
useSwipeStep({ target: swipe, onStep: (step) => emit("step", step) });
|
|
67
|
+
const timeline = useTemplateRef<{
|
|
68
|
+
scrollToMinute: (minute: number, behavior?: ScrollBehavior) => void;
|
|
69
|
+
}>("timeline");
|
|
70
|
+
|
|
71
|
+
const events = computed<VisitEvent[]>(() =>
|
|
72
|
+
props.visits.map((visit) => ({
|
|
73
|
+
id: visit.id,
|
|
74
|
+
start: visit.start,
|
|
75
|
+
end: visit.end,
|
|
76
|
+
tone: TONE[visit.state],
|
|
77
|
+
visit,
|
|
78
|
+
})),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
function eventLabel(event: VisitEvent) {
|
|
82
|
+
const { visit } = event;
|
|
83
|
+
return `${visit.customer}, ${visit.city}, ${formatMinutes(visit.start, locale.value)} – ${formatMinutes(visit.end, locale.value)}, ${t(`gallery.agenda.states.${visit.state}`)}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function announce(event: VisitEvent) {
|
|
87
|
+
void snackbar.show(eventLabel(event));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function focusMinute(): number {
|
|
91
|
+
if (props.day === props.today) return minutesOfDay();
|
|
92
|
+
return props.visits[0]?.start ?? 8 * 60;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
onMounted(() => nextTick(() => timeline.value?.scrollToMinute(focusMinute(), "auto")));
|
|
96
|
+
watch(
|
|
97
|
+
() => props.day,
|
|
98
|
+
() => nextTick(() => timeline.value?.scrollToMinute(focusMinute())),
|
|
99
|
+
);
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<style scoped>
|
|
103
|
+
.agenda-day {
|
|
104
|
+
touch-action: pan-y;
|
|
105
|
+
}
|
|
106
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<M3Card variant="filled" class="mx-4 mt-2 overflow-hidden py-2">
|
|
4
|
+
<M3Calendar
|
|
5
|
+
:value="day"
|
|
6
|
+
@update:value="choose"
|
|
7
|
+
:locale="locale"
|
|
8
|
+
:marks="marks"
|
|
9
|
+
:mark-label="markLabel"
|
|
10
|
+
:previous-label="t('gallery.agenda.previousMonth')"
|
|
11
|
+
:next-label="t('gallery.agenda.nextMonth')"
|
|
12
|
+
:year-label="t('gallery.agenda.chooseYear')"
|
|
13
|
+
@month="emit('month', $event)"
|
|
14
|
+
/>
|
|
15
|
+
</M3Card>
|
|
16
|
+
<AgendaVisitList :heading="heading" :visits="visits" />
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
import type { YearMonth } from "@cavulsqa/m3e-vue";
|
|
22
|
+
import AgendaVisitList from "@/modules/gallery/components/agenda/AgendaVisitList.vue";
|
|
23
|
+
import type { Visit } from "@/modules/gallery/composables/routePlan";
|
|
24
|
+
|
|
25
|
+
defineProps<{
|
|
26
|
+
heading: string;
|
|
27
|
+
visits: readonly Visit[];
|
|
28
|
+
marks: Readonly<Record<string, number>>;
|
|
29
|
+
markLabel: (count: number) => string;
|
|
30
|
+
}>();
|
|
31
|
+
|
|
32
|
+
const day = defineModel<string>({ required: true });
|
|
33
|
+
const emit = defineEmits<{ month: [month: YearMonth] }>();
|
|
34
|
+
|
|
35
|
+
const { t, locale } = useI18n();
|
|
36
|
+
|
|
37
|
+
function choose(next: string | null) {
|
|
38
|
+
if (next) day.value = next;
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<h2 class="type-title-medium m-0 px-6 pb-2 pt-4 text-on-surface">{{ heading }}</h2>
|
|
4
|
+
<M3List v-if="visits.length" variant="segmented" inset>
|
|
5
|
+
<M3ListItem
|
|
6
|
+
v-for="visit in visits"
|
|
7
|
+
:key="visit.id"
|
|
8
|
+
:headline="visit.customer"
|
|
9
|
+
:supporting="visit.city"
|
|
10
|
+
:overline="visit.time"
|
|
11
|
+
>
|
|
12
|
+
<template #leading>
|
|
13
|
+
<i-ms-check-circle-rounded v-if="visit.state === 'done'" class="text-primary" />
|
|
14
|
+
<i-ms-near-me-rounded v-else-if="visit.state === 'next'" class="text-tertiary" />
|
|
15
|
+
<i-ms-schedule-outline-rounded v-else class="text-on-surface-variant" />
|
|
16
|
+
</template>
|
|
17
|
+
<template #trailing>
|
|
18
|
+
<span class="type-label-medium" :class="STATE_TONE[visit.state]">
|
|
19
|
+
{{ t(`gallery.agenda.states.${visit.state}`) }}
|
|
20
|
+
</span>
|
|
21
|
+
</template>
|
|
22
|
+
</M3ListItem>
|
|
23
|
+
</M3List>
|
|
24
|
+
<EmptyState
|
|
25
|
+
v-else
|
|
26
|
+
shape="sunny"
|
|
27
|
+
:headline="t('gallery.agenda.weekend')"
|
|
28
|
+
:text="t('gallery.agenda.weekendText')"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import type { Visit, VisitState } from "@/modules/gallery/composables/routePlan";
|
|
35
|
+
|
|
36
|
+
defineProps<{ heading: string; visits: readonly Visit[] }>();
|
|
37
|
+
|
|
38
|
+
const STATE_TONE: Record<VisitState, string> = {
|
|
39
|
+
done: "text-on-surface-variant",
|
|
40
|
+
next: "text-tertiary",
|
|
41
|
+
planned: "text-on-surface-variant",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const { t } = useI18n();
|
|
45
|
+
</script>
|