Skip to content

Commit a1eedf5

Browse files
kjametnTh1nkK1D
andauthored
feat: add promise detail page (#113)
* feat: update ui and add some components * feat: add progress timeline component * feat: add data period mark * feat: update mobile layout * feat: adjust gap * feat: remove ref button * feat: add form link and promise title * feat: add promise status description * fix: refactor * feat: remove promise clarification blank box --------- Co-authored-by: Th1nkK1D <[email protected]>
1 parent c9558e1 commit a1eedf5

File tree

5 files changed

+315
-1
lines changed

5 files changed

+315
-1
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<script lang="ts">
2+
import VoteCard from '$components/VoteCard/VoteCard.svelte';
3+
import { formatThaiDate } from '$lib/date-parser';
4+
import { PromiseStatus, type Promise } from '$models/promise';
5+
import { Bullhorn, CheckmarkFilled } from 'carbon-icons-svelte';
6+
import { twMerge } from 'tailwind-merge';
7+
8+
export let promise: Promise;
9+
10+
$: timeline = promise.progresses.sort((a, b) => {
11+
return b.date > a.date ? 1 : -1;
12+
});
13+
14+
const sumObj = (obj: Record<string, number>) => {
15+
return Object.values(obj).reduce((acc, value) => acc + value, 0);
16+
};
17+
18+
const highlightedVoteByGroups = (resultsByAffiliation: any[], key: string) => {
19+
return resultsByAffiliation.map((res) => {
20+
return {
21+
name: res.name,
22+
count: res.resultSummary[key],
23+
total: sumObj(res.resultSummary)
24+
};
25+
});
26+
};
27+
</script>
28+
29+
{#each timeline as progress, index}
30+
<div
31+
class={twMerge(
32+
'p-4 pb-0 text-text-01',
33+
progress.type === 'indirect' && 'text-gray-60',
34+
index === 0 && promise.status === PromiseStatus.inProgress && 'bg-yellow-10',
35+
index === 0 && promise.status === PromiseStatus.fulfilled && 'bg-green-10',
36+
index === 0 && promise.status === PromiseStatus.unhonored && 'bg-magenta-10'
37+
)}
38+
>
39+
{#if index === 0 && promise.status !== PromiseStatus.notStarted}
40+
<div class="heading-01 mb-2 text-text-primary">ความเคลื่อนไหวล่าสุด</div>
41+
{/if}
42+
<div class="flex gap-4">
43+
<div class="relative flex flex-col items-center">
44+
<div
45+
class={twMerge(
46+
'absolute inset-0 mx-auto w-[1px] flex-1 border-l border-l-text-primary',
47+
index !== 0 && '-top-4',
48+
progress.type === 'indirect' && 'border-dashed',
49+
index === timeline.length - 1 && 'h-4',
50+
progress.type === 'indirect' && 'border-l-gray-60'
51+
)}
52+
></div>
53+
{#if progress.type === 'checkpoint'}
54+
<CheckmarkFilled
55+
size={24}
56+
class={twMerge(
57+
'relative bg-white',
58+
index === 0 && promise.status === PromiseStatus.inProgress && 'bg-yellow-10',
59+
index === 0 && promise.status === PromiseStatus.fulfilled && 'bg-green-10',
60+
index === 0 && promise.status === PromiseStatus.unhonored && 'bg-magenta-10'
61+
)}
62+
/>
63+
{/if}
64+
{#if progress.type === 'indirect'}
65+
<Bullhorn
66+
size={24}
67+
class={twMerge(
68+
'relative bg-white text-gray-60',
69+
index === 0 && promise.status === PromiseStatus.inProgress && 'bg-yellow-10',
70+
index === 0 && promise.status === PromiseStatus.fulfilled && 'bg-green-10',
71+
index === 0 && promise.status === PromiseStatus.unhonored && 'bg-magenta-10'
72+
)}
73+
/>
74+
{/if}
75+
</div>
76+
<div class="flex flex-1 flex-col gap-6 md:flex-row">
77+
<div class="mb-4 flex flex-1 flex-col gap-2">
78+
<div class="body-01">{formatThaiDate(progress.date, true)}</div>
79+
<div class="heading-02">{progress.title}</div>
80+
{#if progress.description}
81+
<div class="body-01">{progress.description}</div>
82+
{/if}
83+
{#if progress.reference}
84+
<div class="label-01 text-gray-60">
85+
ที่มา: <a
86+
href={progress.reference.url}
87+
target="_blank"
88+
rel="noopener noreferrer"
89+
class="underline">{progress.reference.label}</a
90+
>
91+
{progress.reference.description}
92+
</div>
93+
{/if}
94+
</div>
95+
{#if progress.votingSummary}
96+
<VoteCard
97+
voting={{
98+
id: progress.votingSummary.id,
99+
nickname: progress.votingSummary.title || '',
100+
date: progress.votingSummary.date,
101+
result: progress.votingSummary.result
102+
}}
103+
highlightedVoteByGroups={highlightedVoteByGroups(
104+
progress.votingSummary.resultsByAffiliation,
105+
progress.votingSummary.result
106+
)}
107+
class="mb-4"
108+
/>
109+
{/if}
110+
</div>
111+
</div>
112+
</div>
113+
{/each}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<script lang="ts">
2+
import { Modal } from 'carbon-components-svelte';
3+
import PromiseStatusTag from './PromiseStatusTag.svelte';
4+
import type { PromiseStatus } from '$models/promise';
5+
6+
export let open = false;
7+
export let onClose: () => void;
8+
9+
const statusList = [
10+
{
11+
label: 'รอคำชี้แจงเพิ่มเติม',
12+
text: 'เราพบว่าคำสัญญานี้มีความคลุมเครือและกำลังอยู่ในระหว่างการขอคำชี้แจงเพิ่มเติม'
13+
},
14+
{
15+
label: 'ไม่พบความเคลื่อนไหว',
16+
text: 'เราไม่พบข้อมูลความเคลื่อนไหวที่เกี่ยวกับคำสัญญานี้'
17+
},
18+
{
19+
label: 'กำลังดำเนินการ',
20+
text: 'เราพบข้อมูลความคืบหน้า แต่ยังไม่บรรลุเป้าหมายที่ได้สัญญาไว้'
21+
},
22+
{
23+
label: 'ดำเนินการแล้ว',
24+
text: 'เราพบข้อมูลความคืบหน้า และข้อมูลที่ชี้ว่าได้บรรลุเป้าหมายที่ได้สัญญาไว้แล้ว'
25+
},
26+
{
27+
label: 'เลิกดำเนินการ',
28+
text: 'เราพบข้อมูลความคืบหน้า ที่ชี้ว่ามีการเลิกดำเนินการเพื่อบรรลุเป้าหมายตามคำสัญญานี้แล้ว'
29+
}
30+
] as { label: PromiseStatus; text: string }[];
31+
</script>
32+
33+
<Modal {open} passiveModal hasScrollingContent size="xs" on:close={onClose}>
34+
<div slot="heading">
35+
<div class="heading-03">สถานะคำสัญญา</div>
36+
</div>
37+
<div class="mt-4 flex flex-col gap-4">
38+
{#each statusList as status}
39+
<div>
40+
<PromiseStatusTag status={status.label} />
41+
<div class="body-02 mt-2">{status.text}</div>
42+
</div>
43+
{/each}
44+
</div>
45+
</Modal>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
import { PromiseStatus } from '$models/promise';
3+
import { twMerge } from 'tailwind-merge';
4+
5+
export let status: PromiseStatus;
6+
</script>
7+
8+
<div
9+
class={twMerge(
10+
'heading-02 w-fit flex-none rounded-3xl bg-gray-30 px-2 py-1',
11+
[PromiseStatus.clarifying, PromiseStatus.notStarted].includes(status) && 'bg-gray-30',
12+
status === PromiseStatus.inProgress && 'bg-yellow-20',
13+
status === PromiseStatus.fulfilled && 'bg-green-50 text-white',
14+
status === PromiseStatus.unhonored && 'bg-magenta-50 text-white'
15+
)}
16+
>
17+
{status}
18+
</div>

src/routes/promises/[id]/+page.svelte

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,139 @@
11
<script lang="ts">
2+
import DataPeriodRemark from '$components/DataPeriodRemark.svelte';
3+
import PromiseProgressTimeline from '$components/PromiseDetail/PromiseProgressTimeline.svelte';
4+
import PromiseStatusModal from '$components/PromiseDetail/PromiseStatusModal.svelte';
5+
import PromiseStatusTag from '$components/PromiseDetail/PromiseStatusTag.svelte';
6+
import Share from '$components/Share/Share.svelte';
7+
import { Breadcrumb, BreadcrumbItem, Button } from 'carbon-components-svelte';
8+
import { SendAlt } from 'carbon-icons-svelte';
9+
210
export let data;
3-
console.log(JSON.stringify(data, null, 2));
11+
12+
$: ({ promise } = data);
13+
console.log('promise?.statements?.length:', promise?.statements?.length);
14+
15+
let showStatusListModal = false;
16+
17+
const TITLE_MAX_LENGTH = 45;
18+
19+
$: pageTitle =
20+
promise?.statements?.[0]?.length > TITLE_MAX_LENGTH
21+
? promise.statements[0].slice(0, TITLE_MAX_LENGTH) + '...'
22+
: promise.statements?.[0];
423
</script>
24+
25+
<Breadcrumb
26+
noTrailingSlash
27+
class="body-compact-01 px-4 py-2 [&>.bx--breadcrumb]:flex [&>.bx--breadcrumb]:flex-wrap"
28+
>
29+
<BreadcrumbItem href="/">หน้าหลัก</BreadcrumbItem>
30+
<BreadcrumbItem href="/promises">ติดตามคำสัญญา</BreadcrumbItem>
31+
<BreadcrumbItem>{pageTitle}</BreadcrumbItem>
32+
</Breadcrumb>
33+
{#if promise.coverImageUrl}
34+
<img class="max-h-[300px] w-full object-cover" src={promise.coverImageUrl} alt="coverImageUrl" />
35+
{/if}
36+
<div class="px-4 py-8 md:px-16 md:py-12">
37+
<div class="heading-01 text-text-03">คำสัญญาของพรรคการเมือง</div>
38+
<div class="mt-1 flex items-center gap-2">
39+
<img
40+
src={promise.party.logo}
41+
alt=""
42+
class="h-8 w-8 rounded-full border border-gray-30 object-cover"
43+
/>
44+
<div class="body-01">พรรค{promise.party.name}</div>
45+
</div>
46+
<div class="mt-4 flex flex-nowrap gap-3 overflow-x-auto overflow-y-hidden">
47+
{#each promise.statements as statement}
48+
<div class="flex min-w-[80%] gap-3">
49+
<div class="flex flex-none flex-col items-center gap-2">
50+
<img src="/images/promises/quote.svg" alt="" class="w-6" />
51+
<div class="w-1 flex-1 bg-ui-03"></div>
52+
</div>
53+
<div class="heading-03 text-text-primary">
54+
{statement}
55+
</div>
56+
</div>
57+
{/each}
58+
</div>
59+
<div class="mt-4 flex flex-col gap-2 xl:flex-row xl:items-center">
60+
<PromiseStatusTag status={promise.status} />
61+
<div class="body-01 text-text-01">
62+
[A definition that helps viewers understand the meaning and criteria of the status]
63+
</div>
64+
<button
65+
class="helper-text-01 w-fit flex-none text-link-01 underline"
66+
on:click={() => (showStatusListModal = true)}
67+
>
68+
ดูความหมายสถานะอื่นๆ
69+
</button>
70+
</div>
71+
<div class="mt-8 flex flex-col justify-between gap-8 xl:flex-row">
72+
<div class="flex flex-col gap-2">
73+
<div class="flex flex-wrap gap-1">
74+
<div class="heading-01 mt-1">คีย์เวิร์ด</div>
75+
{#each promise.keywords as keyword}
76+
<span class="label-01 rounded-3xl bg-gray-10 px-2 py-1">{keyword}</span>
77+
{/each}
78+
</div>
79+
<div class="flex flex-wrap gap-1">
80+
<div class="heading-01 mt-1">หมวด</div>
81+
{#each promise.categories as category}
82+
<span class="label-01 rounded-3xl border px-2 py-1">{category}</span>
83+
{/each}
84+
</div>
85+
<div class="flex flex-col gap-1 md:flex-row">
86+
<div class="heading-01">ที่มา</div>
87+
<div>
88+
{#each promise.references as reference}
89+
<div class="leading-4">
90+
<a href={reference.url} target="_blank" class="helper-text-01 text-link-01 underline">
91+
{reference.label}
92+
</a>
93+
<span class="label-01 text-gray-60">{reference.description}</span>
94+
</div>
95+
{/each}
96+
</div>
97+
</div>
98+
</div>
99+
<div class="flex max-w-[224px] flex-col items-start gap-3 md:mt-0">
100+
<DataPeriodRemark />
101+
<Share label="แชร์คำสัญญา" />
102+
</div>
103+
</div>
104+
</div>
105+
<div class="px-4 py-8 md:px-16 md:py-12">
106+
<div class="fluid-heading-04">ความคืบหน้าที่เกี่ยวข้อง</div>
107+
<hr class="mt-4 border-gray-20" />
108+
<div class="mb-4 mt-3 flex justify-between">
109+
<div class="heading-02 flex flex-col gap-1 md:flex-row md:items-center">
110+
สถานะ <PromiseStatusTag status={promise.status} />
111+
</div>
112+
<button
113+
class="helper-text-01 h-fit text-link-01 underline"
114+
on:click={() => (showStatusListModal = true)}
115+
>
116+
คำสัญญามีสถานะอะไรบ้าง?
117+
</button>
118+
</div>
119+
<PromiseProgressTimeline {promise} />
120+
<div class="mt-8 flex flex-col justify-between gap-4 bg-gray-10 p-6 md:flex-row">
121+
<div class="text-01">
122+
<div class="heading-02">
123+
พบความเคลื่อนไหวที่อัพเดตกว่านี้ หรือ มีข้อทักท้วงการจัดสถานะของนโยบายนี้?
124+
</div>
125+
<div class="body-01 mt-1">
126+
ทีมงานยินดีรับฟังเพื่อนำไปปรับปรุงข้อมูลในเว็บไซต์ให้สมบูรณ์และสมเหตุสมผลที่สุด
127+
</div>
128+
</div>
129+
<Button
130+
target="_blank"
131+
href="https://forms.gle/feikqf5TFDqjVKPx6"
132+
icon={SendAlt}
133+
kind="secondary"
134+
>
135+
แจ้ง/ทักท้วงข้อมูล
136+
</Button>
137+
</div>
138+
</div>
139+
<PromiseStatusModal open={showStatusListModal} onClose={() => (showStatusListModal = false)} />

static/images/promises/quote.svg

Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)