Skip to content

Commit

Permalink
refactor: split data transform logic out of model and upgrade sheethu…
Browse files Browse the repository at this point in the history
…ahua to v3
  • Loading branch information
Th1nkK1D committed Nov 20, 2024
1 parent b248616 commit d2ffced
Show file tree
Hide file tree
Showing 39 changed files with 632 additions and 667 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"prettier-plugin-svelte": "^3.2.2",
"prettier-plugin-tailwindcss": "^0.5.12",
"sass": "^1.67.0",
"sheethuahua": "^2.0.0",
"sheethuahua": "^3.1.0",
"svelte": "^4.2.12",
"svelte-check": "^3.6.6",
"tailwind-merge": "^2.1.0",
Expand Down
4 changes: 2 additions & 2 deletions src/components/Assemblies/CabinetChanges/ChangeModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
export let timeLineData: TimeLine[];
export let selectedDate: Date;
export let startedAt: Date | null;
export let endedAt: Date | null;
export let startedAt: Date | undefined;
export let endedAt: Date | undefined;
export let handleSelectDate: (date: Date) => void;
export let open = false;
Expand Down
6 changes: 3 additions & 3 deletions src/components/Assemblies/CabinetChanges/TimeLine.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import { afterUpdate, onMount, tick } from 'svelte';
export let timeLineData: TimeLine[];
export let startedAt: Date | null;
export let endedAt: Date | null;
export let startedAt: Date | undefined;
export let endedAt: Date | undefined;
export let selectedDate: Date;
export let handleSelectDate: (date: Date) => void;
Expand All @@ -18,7 +18,7 @@
$: dateData = getDateData(timeLineData, startedAt, endedAt);
let timelineContainer: HTMLDivElement;
let prevStartedAt: Date | null;
let prevStartedAt: Date | undefined;
let selectedDateElement: HTMLElement;
const handleNext = () => {
Expand Down
6 changes: 5 additions & 1 deletion src/components/Assemblies/CabinetChanges/TimeLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ export const isDateInRange = (date: Date, minDate: Date, maxDate: Date): boolean
return dateOnly >= minDateOnly && dateOnly <= maxDateOnly;
};

export const getDateData = (data: TimeLine[], startedAt: Date | null, endedAt: Date | null) => {
export const getDateData = (
data: TimeLine[],
startedAt: Date | undefined,
endedAt: Date | undefined
) => {
const minDate = startedAt ? startedAt : new Date(Math.min(...data.map((d) => d.date.getTime())));
const maxDate = endedAt ? endedAt : new Date();

Expand Down
4 changes: 2 additions & 2 deletions src/components/Assemblies/CabinetChanges/TimeLineArea.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import TimeLineComponent from './TimeLine.svelte';
export let timeLineData: TimeLine[];
export let startedAt: Date | null;
export let endedAt: Date | null;
export let startedAt: Date | undefined;
export let endedAt: Date | undefined;
export let selectedDate: Date;
export let handleSelectDate: (date: Date) => void;
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
export let timeLineData: TimeLine[];
export let selectedDate: Date;
export let startedAt: Date | null;
export let endedAt: Date | null;
export let startedAt: Date | undefined;
export let endedAt: Date | undefined;
export let handleSelectDate: (date: Date) => void;
$: max = Math.max(...timeLineData.map((d) => Math.max(d.in, d.out)));
Expand Down
14 changes: 5 additions & 9 deletions src/components/Assemblies/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@
import Share from '$components/Share/Share.svelte';
import AssemblyIdRunner, { type AvailableAssembly } from './AssemblyIdRunner.svelte';
import DataPeriodRemark from '$components/DataPeriodRemark/DataPeriodRemark.svelte';
import type { Assembly } from '$models/assembly';
export let availableAssemblies: AvailableAssembly[] = [];
export let assembly: {
id: string;
name: string;
abbreviation?: string | null;
term: number;
startedAt: Date;
endedAt?: Date | null;
origin?: string | null;
};
export let assembly: Pick<
Assembly,
'id' | 'name' | 'abbreviation' | 'term' | 'startedAt' | 'endedAt' | 'origin'
>;
export let postfixLink = '';
export let headerName: string | null = null;
Expand Down
1 change: 0 additions & 1 deletion src/components/Proposer/Proposer.story.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
abbreviation: 'สส.',
term: 26,
startedAt: new Date('01/01/2023'),
endedAt: null,
origin:
'มาจากการเลือกตั้งทั่วไป พ.ศ. 2566 ประกอบด้วยสมาชิก (สส.) 500 คน ตามระบบจัดสรรปันส่วนผสมโดย 400 คนเป็นผู้แทนเขต และอีก 100 คน มาจากระบบบัญชีรายชื่อ',
mainRoles: [
Expand Down
69 changes: 69 additions & 0 deletions src/lib/datasheets/fetchers/assembly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
AssemblyName,
AssemblyPartyGroup,
assemblyStaticInfoMap,
type Assembly
} from '$models/assembly';
import type { Party } from '$models/party';
import { safeFind } from '../processor';
import { fetchParties } from './party';
import { sheets, withCache } from './shared';
import {
asDate,
asNumber,
asOneOf,
asString,
Column,
createTransformer,
Object,
type StaticDecode
} from 'sheethuahua';

export const fetchAssemblies = withCache('Assemblies', async (): Promise<Assembly[]> => {
const parties = await fetchAssemblyPartyGroups();
const groups = await fetchParties();

const assemblySchema = Object({
id: Column('id', asString()),
name: Column('name', asOneOf(global.Object.values(AssemblyName))),
term: Column('term', asNumber()),
startedAt: Column('startedAt', asDate()),
endedAt: Column('endedAt', asDate().optional()),
origin: Column('origin', asString().optional()),
governmentParties: Column('id', asPartyGroup(AssemblyPartyGroup.Government, parties, groups)),
oppositionParties: Column('id', asPartyGroup(AssemblyPartyGroup.Opposition, parties, groups)),
mainRoles: Column('name', asMainRoles()),
abbreviation: Column('name', asAbbreviation())
});

return sheets.get('Assemblies', assemblySchema);
});

export const fetchAssemblyPartyGroups = withCache('AssemblyPartyGroups', () =>
sheets.get('AssemblyPartyGroups', assemblyPartyGroupSchema)
);

const assemblyPartyGroupSchema = Object({
assemblyId: Column('assemblyId', asString()),
partyName: Column('partyName', asString()),
group: Column('group', asOneOf(global.Object.values(AssemblyPartyGroup)))
});

const asPartyGroup = (
groupName: AssemblyPartyGroup,
groups: StaticDecode<typeof assemblyPartyGroupSchema>[],
parties: Party[]
) =>
createTransformer((id: string) =>
groups
.filter(({ assemblyId, group }) => assemblyId === id && group === groupName)
.map(({ partyName }) => safeFind(parties, ({ name }) => name === partyName))
);

const asMainRoles = () =>
createTransformer((name: string) => assemblyStaticInfoMap[name as AssemblyName]?.mainRoles ?? []);

const asAbbreviation = () =>
createTransformer(
(name: string) => assemblyStaticInfoMap[name as AssemblyName]?.abbreviation ?? ''
);
31 changes: 31 additions & 0 deletions src/lib/datasheets/fetchers/bill-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
BillEventActionType,
BillEventType,
eventTypeTitleDescription,
type BillEvent
} from '$models/bill-event';
import { asSlug } from '../transformers';
import { sheets, withCache } from './shared';
import { asDate, asOneOf, asString, Column, Object } from 'sheethuahua';

const billEventSchema = Object({
billId: Column('billId', asSlug()),
date: Column('date', asDate().optional()),
type: Column('type', asOneOf(global.Object.values(BillEventType))),
title: Column('title', asString().optional()),
description: Column('description', asString().optional()),
actionType: Column('actionType', asOneOf(global.Object.values(BillEventActionType)).optional()),
votedInVotingId: Column('votedInVotingId', asSlug().optional()),
mergedIntoBillId: Column('mergedIntoBillId', asSlug().optional()),
enforcementDocumentUrl: Column('enforcementDocumentUrl', asString().optional())
});

export const fetchBillEvents = withCache(
'BillEvents',
async (): Promise<BillEvent[]> =>
(await sheets.get('BillEvents', billEventSchema)).map((e) => ({
...e,
title: e.title ?? eventTypeTitleDescription[e.type].title,
description: e.description ?? eventTypeTitleDescription[e.type].description
}))
);
45 changes: 45 additions & 0 deletions src/lib/datasheets/fetchers/bill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { BillProposerType, BillStatus, type Bill, type PeopleProposer } from '$models/bill';
import type { Link } from '$models/link';
import { asPolitician, asSlug, asValidAssembly } from '../transformers';
import { sheets, withCache } from './shared';
import { asDate, asOneOf, asString, Column, Object, asArray, asNumber } from 'sheethuahua';

export const fetchBills = withCache('Bills', async (): Promise<Bill[]> => {
const billSchema = Object({
id: Column('id', asSlug()),
acceptanceNumber: Column('acceptanceNumber', asString()),
title: Column('title', asString()),
nickname: Column('nickname', asString().optional()),
description: Column('description', asString().optional()),
status: Column('status', asOneOf(global.Object.values(BillStatus))),
categories: Column('categories', asArray(asString()).optional([])),
proposedOn: Column('proposedOn', asDate()),
attachment: Object({
name: Column('attachmentName', asString().optional()),
url: Column('attachmentUrl', asString().optional())
}),
proposedLedByPolitician: Column('proposedLedByPoliticianId', await asPolitician()).optional(),
proposedByAssembly: Column('proposedByAssemblyId', await asValidAssembly()).optional(),
proposedByPeople: Object({
ledBy: Column('proposedLedByPeople', asString().optional()),
signatoryCount: Column('peopleSignatureCount', asNumber().optional(0))
}),
lisUrl: Column('lisUrl', asString())
});

return (await sheets.get('Bills', billSchema)).map((bill) => ({
...bill,
nickname: bill.nickname ?? bill.title.replace('ร่างพระราชบัญญัติ', 'ร่าง พ.ร.บ.'),
proposerType: bill.proposedLedByPolitician
? BillProposerType.Politician
: bill.proposedByAssembly
? BillProposerType.Assembly
: bill.proposedByPeople
? BillProposerType.People
: BillProposerType.Unknown,
proposedByPeople: bill.proposedByPeople.ledBy
? (bill.proposedByPeople as PeopleProposer)
: undefined,
attachment: bill.attachment.name && bill.attachment.url ? (bill.attachment as Link) : undefined
}));
});
16 changes: 16 additions & 0 deletions src/lib/datasheets/fetchers/party.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Party } from '$models/party';
import { asStaticImage } from '../transformers';
import { sheets, withCache } from './shared';
import { asString, Column, Object } from 'sheethuahua';

const DEFAULT_PARTY_COLOR = '#F4F4F4';

export const fetchParties = withCache('Parties', (): Promise<Party[]> => {
const partySchema = Object({
name: Column('name', asString()),
color: Column('color', asString().optional(DEFAULT_PARTY_COLOR)),
logo: Column('name', asStaticImage('/images/parties'))
});

return sheets.get('Parties', partySchema);
});
87 changes: 87 additions & 0 deletions src/lib/datasheets/fetchers/politician.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { Link } from '$models/link';
import type { AssemblyRoleHistory, PartyRoleHistory, Politician } from '$models/politician';
import { joinMany } from '../processor';
import { asValidAssembly, asMarkdownList, asValidParty, asStaticImage } from '../transformers';
import { sheets, withCache } from './shared';
import { asDate, asNumber, asString, Column, createTransformer, Object, Tuple } from 'sheethuahua';

export const fetchPoliticians = withCache('Politicians', async (): Promise<Politician[]> => {
const politicianSchema = Object({
id: Column('id', asString()),
prefix: Column('prefix', asString().optional()),
firstname: Column('firstname', asString()),
lastname: Column('lastname', asString()),
avatar: Column('id', asStaticImage('/images/politicians')),
sex: Column('sex', asString().optional()),
birthdate: Column('birthdate', asDate().optional()),
educations: Column('educations', asMarkdownList().optional([])),
previousOccupations: Column('previousOccupations', asMarkdownList().optional([])),
contacts: Tuple([
Column('facebook', asSocialContact('Facebook').optional()),
Column('x', asSocialContact('X').optional())
])
});

const partyRoleHistory = await fetchPartyRoleHistory();
const assemblyRoleHistory = await fetchAssemblyRoleHistory();

return (await sheets.get('Politicians', politicianSchema)).map((politician) => {
const partyRoles = joinMany(partyRoleHistory, 'politicianId', politician.id).sort(
(a, b) => b.startedAt.getTime() - a.startedAt.getTime()
);
const assemblyRoles = joinMany(assemblyRoleHistory, 'politicianId', politician.id).sort(
(a, b) => b.startedAt.getTime() - a.startedAt.getTime()
);

return {
...politician,
contacts: politician.contacts.filter((c) => c) as Link[],
partyRoles,
assemblyRoles,
isActive:
assemblyRoles.length > 0 && !assemblyRoles[0].endedAt && !assemblyRoles[0].assembly.endedAt
};
});
});

export const fetchAssemblyRoleHistory = withCache(
'AssemblyRoleHistory',
async (): Promise<AssemblyRoleHistory[]> => {
const assemblyRoleSchema = Object({
politicianId: Column('politicianId', asString()),
assembly: Column('assemblyId', await asValidAssembly()),
role: Column('role', asString().optional('สมาชิก')),
appointmentMethod: Column('appointmentMethod', asString().optional()),
province: Column('province', asString().optional()),
districtNumber: Column('districtNumber', asNumber().optional()),
listNumber: Column('listNumber', asNumber().optional()),
startedAt: Column('startedAt', asDate()),
endedAt: Column('endedAt', asDate().optional())
});

return sheets.get('AssemblyRoleHistory', assemblyRoleSchema);
}
);

export const fetchPartyRoleHistory = withCache(
'PartyRoleHistory',
async (): Promise<PartyRoleHistory[]> => {
const partyRoleSchema = Object({
politicianId: Column('politicianId', asString()),
party: Column('partyName', await asValidParty()),
role: Column('role', asString().optional('สมาชิก')),
startedAt: Column('startedAt', asDate()),
endedAt: Column('endedAt', asDate().optional())
});

return sheets.get('PartyRoleHistory', partyRoleSchema);
}
);

const asSocialContact = (label: string) =>
createTransformer(
(url): Link => ({
label,
url
})
);
Loading

0 comments on commit d2ffced

Please sign in to comment.