@things-factory/calendar 10.1.202 → 10.1.205

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.
Files changed (114) hide show
  1. package/dist-client/pages/calendar/calendar-list-page.d.ts +1 -9
  2. package/dist-client/pages/calendar/calendar-list-page.js +2 -27
  3. package/dist-client/pages/calendar/calendar-list-page.js.map +1 -1
  4. package/dist-client/pages/event/event-list-page.d.ts +5 -28
  5. package/dist-client/pages/event/event-list-page.js +65 -177
  6. package/dist-client/pages/event/event-list-page.js.map +1 -1
  7. package/dist-client/route.d.ts +1 -1
  8. package/dist-client/route.js +0 -3
  9. package/dist-client/route.js.map +1 -1
  10. package/dist-client/tsconfig.tsbuildinfo +1 -1
  11. package/dist-server/index.d.ts +1 -0
  12. package/dist-server/index.js +5 -0
  13. package/dist-server/index.js.map +1 -1
  14. package/dist-server/role-tasks.d.ts +2 -0
  15. package/dist-server/role-tasks.js +16 -0
  16. package/dist-server/role-tasks.js.map +1 -0
  17. package/dist-server/service/attendee/attendee-query.d.ts +7 -4
  18. package/dist-server/service/attendee/attendee-query.js +34 -25
  19. package/dist-server/service/attendee/attendee-query.js.map +1 -1
  20. package/dist-server/service/attendee/attendee-service.d.ts +41 -0
  21. package/dist-server/service/attendee/attendee-service.js +90 -0
  22. package/dist-server/service/attendee/attendee-service.js.map +1 -0
  23. package/dist-server/service/attendee/attendee-type.d.ts +3 -15
  24. package/dist-server/service/attendee/attendee-type.js +12 -53
  25. package/dist-server/service/attendee/attendee-type.js.map +1 -1
  26. package/dist-server/service/attendee/attendee.d.ts +24 -7
  27. package/dist-server/service/attendee/attendee.js +99 -23
  28. package/dist-server/service/attendee/attendee.js.map +1 -1
  29. package/dist-server/service/attendee/index.d.ts +1 -2
  30. package/dist-server/service/attendee/index.js +1 -2
  31. package/dist-server/service/attendee/index.js.map +1 -1
  32. package/dist-server/service/calendar/calendar-mutation.d.ts +1 -3
  33. package/dist-server/service/calendar/calendar-mutation.js +21 -94
  34. package/dist-server/service/calendar/calendar-mutation.js.map +1 -1
  35. package/dist-server/service/calendar/calendar-query.d.ts +1 -1
  36. package/dist-server/service/calendar/calendar-query.js +15 -17
  37. package/dist-server/service/calendar/calendar-query.js.map +1 -1
  38. package/dist-server/service/calendar/calendar-service.d.ts +10 -0
  39. package/dist-server/service/calendar/calendar-service.js +58 -0
  40. package/dist-server/service/calendar/calendar-service.js.map +1 -0
  41. package/dist-server/service/calendar/calendar-type.d.ts +1 -4
  42. package/dist-server/service/calendar/calendar-type.js +13 -25
  43. package/dist-server/service/calendar/calendar-type.js.map +1 -1
  44. package/dist-server/service/calendar/calendar.d.ts +7 -8
  45. package/dist-server/service/calendar/calendar.js +23 -33
  46. package/dist-server/service/calendar/calendar.js.map +1 -1
  47. package/dist-server/service/event/event-mutation.d.ts +6 -4
  48. package/dist-server/service/event/event-mutation.js +60 -83
  49. package/dist-server/service/event/event-mutation.js.map +1 -1
  50. package/dist-server/service/event/event-query.d.ts +11 -1
  51. package/dist-server/service/event/event-query.js +106 -13
  52. package/dist-server/service/event/event-query.js.map +1 -1
  53. package/dist-server/service/event/event-rules.d.ts +10 -0
  54. package/dist-server/service/event/event-rules.js +38 -0
  55. package/dist-server/service/event/event-rules.js.map +1 -0
  56. package/dist-server/service/event/event-service.d.ts +42 -0
  57. package/dist-server/service/event/event-service.js +159 -0
  58. package/dist-server/service/event/event-service.js.map +1 -0
  59. package/dist-server/service/event/event-type.d.ts +10 -11
  60. package/dist-server/service/event/event-type.js +32 -36
  61. package/dist-server/service/event/event-type.js.map +1 -1
  62. package/dist-server/service/event/event.d.ts +25 -11
  63. package/dist-server/service/event/event.js +89 -36
  64. package/dist-server/service/event/event.js.map +1 -1
  65. package/dist-server/service/event-series/event-series-mutation.d.ts +11 -0
  66. package/dist-server/service/event-series/event-series-mutation.js +100 -0
  67. package/dist-server/service/event-series/event-series-mutation.js.map +1 -0
  68. package/dist-server/service/event-series/event-series-query.d.ts +16 -0
  69. package/dist-server/service/event-series/event-series-query.js +109 -0
  70. package/dist-server/service/event-series/event-series-query.js.map +1 -0
  71. package/dist-server/service/event-series/event-series-service.d.ts +40 -0
  72. package/dist-server/service/event-series/event-series-service.js +253 -0
  73. package/dist-server/service/event-series/event-series-service.js.map +1 -0
  74. package/dist-server/service/event-series/event-series-type.d.ts +41 -0
  75. package/dist-server/service/event-series/event-series-type.js +155 -0
  76. package/dist-server/service/event-series/event-series-type.js.map +1 -0
  77. package/dist-server/service/event-series/event-series.d.ts +32 -0
  78. package/dist-server/service/event-series/event-series.js +134 -0
  79. package/dist-server/service/event-series/event-series.js.map +1 -0
  80. package/dist-server/service/event-series/index.d.ts +6 -0
  81. package/dist-server/service/event-series/index.js +10 -0
  82. package/dist-server/service/event-series/index.js.map +1 -0
  83. package/dist-server/service/event-series/recurrence.d.ts +19 -0
  84. package/dist-server/service/event-series/recurrence.js +95 -0
  85. package/dist-server/service/event-series/recurrence.js.map +1 -0
  86. package/dist-server/service/index.d.ts +15 -4
  87. package/dist-server/service/index.js +29 -11
  88. package/dist-server/service/index.js.map +1 -1
  89. package/dist-server/tsconfig.tsbuildinfo +1 -1
  90. package/package.json +8 -5
  91. package/tests/open-doors.test.ts +18 -21
  92. package/things-factory.config.js +0 -4
  93. package/translations/en.json +44 -1
  94. package/translations/es.json +44 -1
  95. package/translations/ja.json +44 -1
  96. package/translations/ko.json +44 -1
  97. package/translations/ms.json +44 -1
  98. package/translations/zh.json +44 -1
  99. package/concept.md +0 -31
  100. package/dist-client/pages/attendee/attendee-importer.d.ts +0 -23
  101. package/dist-client/pages/attendee/attendee-importer.js +0 -93
  102. package/dist-client/pages/attendee/attendee-importer.js.map +0 -1
  103. package/dist-client/pages/attendee/attendee-list-page.d.ts +0 -56
  104. package/dist-client/pages/attendee/attendee-list-page.js +0 -263
  105. package/dist-client/pages/attendee/attendee-list-page.js.map +0 -1
  106. package/dist-client/pages/calendar/calendar-importer.d.ts +0 -23
  107. package/dist-client/pages/calendar/calendar-importer.js +0 -93
  108. package/dist-client/pages/calendar/calendar-importer.js.map +0 -1
  109. package/dist-client/pages/event/event-importer.d.ts +0 -23
  110. package/dist-client/pages/event/event-importer.js +0 -93
  111. package/dist-client/pages/event/event-importer.js.map +0 -1
  112. package/dist-server/service/attendee/attendee-mutation.d.ts +0 -10
  113. package/dist-server/service/attendee/attendee-mutation.js +0 -140
  114. package/dist-server/service/attendee/attendee-mutation.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/calendar",
3
- "version": "10.1.202",
3
+ "version": "10.1.205",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -24,14 +24,17 @@
24
24
  "clean:client": "npx rimraf dist-client",
25
25
  "clean:server": "npx rimraf dist-server",
26
26
  "clean": "npm run clean:server && npm run clean:client",
27
- "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
27
+ "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration",
28
+ "test": "node --test test/*.test.mjs",
29
+ "pretest": "npm run build:server"
28
30
  },
29
31
  "dependencies": {
30
32
  "@operato/event-view": "^10.0.0",
31
33
  "@operato/graphql": "^10.0.0",
32
34
  "@operato/shell": "^10.0.0",
33
- "@things-factory/auth-base": "^10.1.202",
34
- "@things-factory/shell": "^10.1.188"
35
+ "@things-factory/auth-base": "^10.1.205",
36
+ "@things-factory/shell": "^10.1.205",
37
+ "cron-parser": "^4.3.0"
35
38
  },
36
- "gitHead": "3243b81eb1a4d3d903a01929f3a85d5b8df438dd"
39
+ "gitHead": "7311492aa5eaa00993c0325053f5f7ae8b1fb024"
37
40
  }
@@ -1,16 +1,16 @@
1
1
  /*
2
- * Every mutation of calendar declares a privilege, and the domain owner may call it.
2
+ * Every root field of calendar declares a privilege, and the domain owner may call it.
3
3
  *
4
- * Until 2026-10-01 no mutation of calendar declared anything: any member of a domain could call them. Calendars, events and attendees all ask for `calendar:mutation`.
5
- * The queries are not gated yet and are not measured here.
4
+ * Until 2026-10-01 no mutation of calendar declared anything: any member of a domain could call them. Since 2026-10-20
5
+ * the queries ask too: queries `calendar:query`, mutations `calendar:mutation`. One query is open by decision: `myEvents`
6
+ * reads only the caller's own events, and `test/persistence.test.mjs` holds its body to that.
6
7
  */
7
8
  import { gatesOf, openFields } from '../../../test/helpers/open-doors'
8
9
 
9
- /** The category each mutation asks for; anything not named here asks for `calendar`. */
10
- const CATEGORY: Record<string, string> = {}
11
-
12
10
  /** Open on purpose or not decided yet, each with its reason. */
13
- const OPEN: Record<string, string> = {}
11
+ const OPEN: Record<string, string> = {
12
+ myEvents: "the caller's own events - the body keeps only events the caller is named on or responsible for, in their domain"
13
+ }
14
14
 
15
15
  let gates: Awaited<ReturnType<typeof gatesOf>>
16
16
 
@@ -18,24 +18,21 @@ beforeAll(async () => {
18
18
  gates = await gatesOf('calendar')
19
19
  })
20
20
 
21
- it('every open mutation has a written reason', () => {
22
- expect(openFields(gates.Mutation)).toEqual(Object.keys(OPEN).sort())
21
+ it('every open root field has a written reason', () => {
22
+ expect([...openFields(gates.Query), ...openFields(gates.Mutation)].sort()).toEqual(Object.keys(OPEN).sort())
23
23
  })
24
24
 
25
- it('every declared mutation asks for its category and admits the domain owner', () => {
26
- for (const [field, gate] of Object.entries(gates.Mutation)) {
27
- if (field in OPEN) continue
28
- expect({ field, ...gate }).toEqual({
29
- field,
30
- category: CATEGORY[field] ?? 'calendar',
31
- privilege: 'mutation',
32
- owner: true,
33
- super: false
34
- })
25
+ it('every declared root field asks for its axis of calendar and admits the domain owner', () => {
26
+ for (const root of ['Query', 'Mutation'] as const) {
27
+ for (const [field, gate] of Object.entries(gates[root])) {
28
+ if (field in OPEN) continue
29
+ expect({ field, ...gate }).toEqual({ field, category: 'calendar', privilege: root === 'Query' ? 'query' : 'mutation', owner: true, super: false })
30
+ }
35
31
  }
36
32
  })
37
33
 
38
- it('the walk reaches every mutation', () => {
34
+ it('the walk reaches every root field', () => {
39
35
  /* If the resolver list shrank, the checks above would pass by having nothing to look at. */
40
- expect(Object.keys(gates.Mutation)).toHaveLength(18)
36
+ expect(Object.keys(gates.Query)).toHaveLength(8)
37
+ expect(Object.keys(gates.Mutation)).toHaveLength(16)
41
38
  })
@@ -15,10 +15,6 @@ export default {
15
15
  {
16
16
  tagname: 'event-list-page',
17
17
  page: 'event-list'
18
- },
19
- {
20
- tagname: 'attendee-list-page',
21
- page: 'attendee-list'
22
18
  }
23
19
  ],
24
20
  bootstrap
@@ -1 +1,44 @@
1
- {}
1
+ {
2
+ "title.calendar list": "calendars",
3
+ "title.event list": "events",
4
+ "field.start_at": "start",
5
+ "field.end_at": "end",
6
+ "field.location": "location",
7
+ "field.organizer": "organizer",
8
+ "field.event-series": "series",
9
+ "label.event-status.SCHEDULED": "scheduled",
10
+ "label.event-status.POSTPONED": "postponed",
11
+ "label.event-status.CANCELLED": "cancelled",
12
+ "label.event-status.HELD": "held",
13
+ "task.calendar.reading.name": "Calendar reading",
14
+ "task.calendar.reading.description": "Reads calendars, events and series.",
15
+ "task.calendar.keeping.name": "Calendar keeping",
16
+ "task.calendar.keeping.description": "Reads and changes calendars, events and series.",
17
+ "error.attendee-duplicate": "Name each attendee once",
18
+ "error.attendee-email-invalid": "Give a valid e-mail address",
19
+ "error.attendee-user-invalid": "Name a user of this domain",
20
+ "error.attendee-user-or-email": "Name an attendee by a user or by an e-mail address, not both",
21
+ "error.calendar-in-use": "Move or delete the events and series of the calendar first",
22
+ "error.calendar-name-duplicate": "Another calendar of this domain has that name",
23
+ "error.calendar-name-required": "Name the calendar",
24
+ "error.calendar-not-found": "Choose a calendar of this domain",
25
+ "error.event-end-before-start": "The event ends after it starts",
26
+ "error.event-name-required": "Name the event",
27
+ "error.event-not-found": "Event not found",
28
+ "error.event-not-started": "An event is held only once it has started",
29
+ "error.event-occurrence-not-deleted": "Cancel an occurrence of a series instead of deleting it",
30
+ "error.event-organizer-invalid": "Put it in the charge of a user of this domain",
31
+ "error.event-range-invalid": "The range ends after it starts",
32
+ "error.event-series-duration-invalid": "Give the length in whole minutes, more than zero",
33
+ "error.event-series-ends-before-start": "The series ends after it starts",
34
+ "error.event-series-inactive": "The series is not active",
35
+ "error.event-series-not-found": "Series not found",
36
+ "error.event-series-rule-invalid": "Give the rule as five cron fields: minute hour day-of-month month day-of-week",
37
+ "error.event-series-starts-at-required": "Give when the series starts",
38
+ "error.event-series-timezone-invalid": "Give an IANA timezone such as Asia/Seoul",
39
+ "error.event-series-too-many-occurrences": "Too many occurrences at once; give a shorter horizon",
40
+ "error.event-series-until-required": "Give the time to create occurrences up to",
41
+ "error.event-status-move-invalid": "The event cannot move to that status",
42
+ "error.event-time-required": "Give when the event starts and ends",
43
+ "error.event-day-invalid": "Give the day as YYYY-MM-DD"
44
+ }
@@ -1 +1,44 @@
1
- {}
1
+ {
2
+ "title.calendar list": "calendarios",
3
+ "title.event list": "eventos",
4
+ "field.start_at": "inicio",
5
+ "field.end_at": "fin",
6
+ "field.location": "lugar",
7
+ "field.organizer": "responsable",
8
+ "field.event-series": "serie",
9
+ "label.event-status.SCHEDULED": "programado",
10
+ "label.event-status.POSTPONED": "aplazado",
11
+ "label.event-status.CANCELLED": "cancelado",
12
+ "label.event-status.HELD": "celebrado",
13
+ "task.calendar.reading.name": "Consulta de calendario",
14
+ "task.calendar.reading.description": "Consulta calendarios, eventos y series.",
15
+ "task.calendar.keeping.name": "Gestión de calendario",
16
+ "task.calendar.keeping.description": "Consulta y modifica calendarios, eventos y series.",
17
+ "error.attendee-duplicate": "Indique cada asistente una sola vez",
18
+ "error.attendee-email-invalid": "Indique una dirección de correo válida",
19
+ "error.attendee-user-invalid": "Indique un usuario de este dominio",
20
+ "error.attendee-user-or-email": "Indique al asistente por usuario o por correo, no ambos",
21
+ "error.calendar-in-use": "Mueva o elimine primero los eventos y series del calendario",
22
+ "error.calendar-name-duplicate": "Ya existe un calendario con ese nombre",
23
+ "error.calendar-name-required": "Indique el nombre del calendario",
24
+ "error.calendar-not-found": "Elija un calendario de este dominio",
25
+ "error.event-end-before-start": "El evento termina después de empezar",
26
+ "error.event-name-required": "Indique el nombre del evento",
27
+ "error.event-not-found": "Evento no encontrado",
28
+ "error.event-not-started": "Un evento se marca como celebrado solo después de empezar",
29
+ "error.event-occurrence-not-deleted": "Una sesión de una serie se cancela en lugar de eliminarse",
30
+ "error.event-organizer-invalid": "El responsable debe ser un usuario de este dominio",
31
+ "error.event-range-invalid": "El periodo termina después de empezar",
32
+ "error.event-series-duration-invalid": "Indique la duración en minutos enteros, mayor que cero",
33
+ "error.event-series-ends-before-start": "La serie termina después de empezar",
34
+ "error.event-series-inactive": "La serie no está activa",
35
+ "error.event-series-not-found": "Serie no encontrada",
36
+ "error.event-series-rule-invalid": "Indique la regla con cinco campos cron: minuto hora día mes día-de-semana",
37
+ "error.event-series-starts-at-required": "Indique cuándo empieza la serie",
38
+ "error.event-series-timezone-invalid": "Indique una zona horaria IANA, como Asia/Seoul",
39
+ "error.event-series-too-many-occurrences": "Demasiadas sesiones a la vez; indique un periodo más corto",
40
+ "error.event-series-until-required": "Indique hasta cuándo crear sesiones",
41
+ "error.event-status-move-invalid": "El evento no puede pasar a ese estado",
42
+ "error.event-time-required": "Indique cuándo empieza y termina el evento",
43
+ "error.event-day-invalid": "Indique el día como AAAA-MM-DD"
44
+ }
@@ -1 +1,44 @@
1
- {}
1
+ {
2
+ "title.calendar list": "カレンダー",
3
+ "title.event list": "予定",
4
+ "field.start_at": "開始",
5
+ "field.end_at": "終了",
6
+ "field.location": "場所",
7
+ "field.organizer": "責任者",
8
+ "field.event-series": "繰り返し予定",
9
+ "label.event-status.SCHEDULED": "予定",
10
+ "label.event-status.POSTPONED": "延期",
11
+ "label.event-status.CANCELLED": "中止",
12
+ "label.event-status.HELD": "開催済み",
13
+ "task.calendar.reading.name": "カレンダー閲覧",
14
+ "task.calendar.reading.description": "カレンダー・予定・繰り返し予定を閲覧する。",
15
+ "task.calendar.keeping.name": "カレンダー管理",
16
+ "task.calendar.keeping.description": "カレンダー・予定・繰り返し予定を閲覧・変更する。",
17
+ "error.attendee-duplicate": "参加者は一度ずつ指定してください",
18
+ "error.attendee-email-invalid": "正しいメールアドレスを入力してください",
19
+ "error.attendee-user-invalid": "このドメインのユーザーを指定してください",
20
+ "error.attendee-user-or-email": "参加者はユーザーかメールアドレスのどちらかで指定してください",
21
+ "error.calendar-in-use": "先にカレンダーの予定と繰り返し予定を移動または削除してください",
22
+ "error.calendar-name-duplicate": "同じ名前のカレンダーがあります",
23
+ "error.calendar-name-required": "カレンダー名を入力してください",
24
+ "error.calendar-not-found": "このドメインのカレンダーを選んでください",
25
+ "error.event-end-before-start": "終了は開始より後にしてください",
26
+ "error.event-name-required": "予定名を入力してください",
27
+ "error.event-not-found": "予定が見つかりません",
28
+ "error.event-not-started": "開始した予定だけを開催済みにできます",
29
+ "error.event-occurrence-not-deleted": "繰り返し予定の回は削除せず中止にします",
30
+ "error.event-organizer-invalid": "責任者はこのドメインのユーザーにしてください",
31
+ "error.event-range-invalid": "期間の終わりは開始より後にしてください",
32
+ "error.event-series-duration-invalid": "長さは0より大きい分単位で入力してください",
33
+ "error.event-series-ends-before-start": "繰り返しの終了は開始より後にしてください",
34
+ "error.event-series-inactive": "使用していない繰り返し予定です",
35
+ "error.event-series-not-found": "繰り返し予定が見つかりません",
36
+ "error.event-series-rule-invalid": "繰り返しルールはcronの5項目(分 時 日 月 曜日)で入力してください",
37
+ "error.event-series-starts-at-required": "繰り返しの開始時刻を入力してください",
38
+ "error.event-series-timezone-invalid": "Asia/SeoulのようなIANAタイムゾーンを入力してください",
39
+ "error.event-series-too-many-occurrences": "一度に作る回が多すぎます。期間を短くしてください",
40
+ "error.event-series-until-required": "回を作る最後の時刻を入力してください",
41
+ "error.event-status-move-invalid": "この予定はその状態に変更できません",
42
+ "error.event-time-required": "予定の開始と終了を入力してください",
43
+ "error.event-day-invalid": "日付はYYYY-MM-DD形式で入力してください"
44
+ }
@@ -1 +1,44 @@
1
- {}
1
+ {
2
+ "title.calendar list": "캘린더",
3
+ "title.event list": "일정",
4
+ "field.start_at": "시작",
5
+ "field.end_at": "종료",
6
+ "field.location": "장소",
7
+ "field.organizer": "책임자",
8
+ "field.event-series": "반복 일정",
9
+ "label.event-status.SCHEDULED": "예정",
10
+ "label.event-status.POSTPONED": "연기",
11
+ "label.event-status.CANCELLED": "취소",
12
+ "label.event-status.HELD": "개최",
13
+ "task.calendar.reading.name": "캘린더 열람",
14
+ "task.calendar.reading.description": "캘린더·일정·반복 일정을 읽는다.",
15
+ "task.calendar.keeping.name": "캘린더 관리",
16
+ "task.calendar.keeping.description": "캘린더·일정·반복 일정을 읽고 바꾼다.",
17
+ "error.attendee-duplicate": "참석자는 한 번씩만 지정하세요",
18
+ "error.attendee-email-invalid": "올바른 이메일 주소를 입력하세요",
19
+ "error.attendee-user-invalid": "이 사업장의 사용자를 지정하세요",
20
+ "error.attendee-user-or-email": "참석자는 사용자나 이메일 주소 중 하나로 지정하세요",
21
+ "error.calendar-in-use": "캘린더의 일정과 반복 일정을 먼저 옮기거나 지우세요",
22
+ "error.calendar-name-duplicate": "같은 이름의 캘린더가 있습니다",
23
+ "error.calendar-name-required": "캘린더 이름을 입력하세요",
24
+ "error.calendar-not-found": "이 사업장의 캘린더를 고르세요",
25
+ "error.event-end-before-start": "종료는 시작보다 뒤여야 합니다",
26
+ "error.event-name-required": "일정 이름을 입력하세요",
27
+ "error.event-not-found": "일정을 찾을 수 없습니다",
28
+ "error.event-not-started": "시작한 일정만 개최로 기록할 수 있습니다",
29
+ "error.event-occurrence-not-deleted": "반복 일정의 회차는 지우지 않고 취소합니다",
30
+ "error.event-organizer-invalid": "책임자는 이 사업장의 사용자로 지정하세요",
31
+ "error.event-range-invalid": "기간의 끝은 시작보다 뒤여야 합니다",
32
+ "error.event-series-duration-invalid": "길이는 0보다 큰 분 단위로 입력하세요",
33
+ "error.event-series-ends-before-start": "반복 종료는 시작보다 뒤여야 합니다",
34
+ "error.event-series-inactive": "사용하지 않는 반복 일정입니다",
35
+ "error.event-series-not-found": "반복 일정을 찾을 수 없습니다",
36
+ "error.event-series-rule-invalid": "반복 규칙은 cron 다섯 칸(분 시 일 월 요일)으로 입력하세요",
37
+ "error.event-series-starts-at-required": "반복 시작 시각을 입력하세요",
38
+ "error.event-series-timezone-invalid": "Asia/Seoul 같은 IANA 시간대를 입력하세요",
39
+ "error.event-series-too-many-occurrences": "한 번에 만들 회차가 너무 많습니다. 기간을 줄이세요",
40
+ "error.event-series-until-required": "회차를 만들 마지막 시각을 입력하세요",
41
+ "error.event-status-move-invalid": "이 일정은 그 상태로 바꿀 수 없습니다",
42
+ "error.event-time-required": "일정의 시작과 종료를 입력하세요",
43
+ "error.event-day-invalid": "날짜는 YYYY-MM-DD 형식으로 입력하세요"
44
+ }
@@ -1 +1,44 @@
1
- {}
1
+ {
2
+ "title.calendar list": "kalendar",
3
+ "title.event list": "acara",
4
+ "field.start_at": "mula",
5
+ "field.end_at": "tamat",
6
+ "field.location": "lokasi",
7
+ "field.organizer": "penganjur",
8
+ "field.event-series": "siri",
9
+ "label.event-status.SCHEDULED": "dijadualkan",
10
+ "label.event-status.POSTPONED": "ditangguhkan",
11
+ "label.event-status.CANCELLED": "dibatalkan",
12
+ "label.event-status.HELD": "diadakan",
13
+ "task.calendar.reading.name": "Membaca kalendar",
14
+ "task.calendar.reading.description": "Membaca kalendar, acara dan siri.",
15
+ "task.calendar.keeping.name": "Mengurus kalendar",
16
+ "task.calendar.keeping.description": "Membaca dan mengubah kalendar, acara dan siri.",
17
+ "error.attendee-duplicate": "Namakan setiap peserta sekali sahaja",
18
+ "error.attendee-email-invalid": "Berikan alamat e-mel yang sah",
19
+ "error.attendee-user-invalid": "Namakan pengguna domain ini",
20
+ "error.attendee-user-or-email": "Namakan peserta melalui pengguna atau alamat e-mel, bukan kedua-duanya",
21
+ "error.calendar-in-use": "Alihkan atau padam acara dan siri kalendar dahulu",
22
+ "error.calendar-name-duplicate": "Kalendar lain dalam domain ini mempunyai nama itu",
23
+ "error.calendar-name-required": "Namakan kalendar",
24
+ "error.calendar-not-found": "Pilih kalendar domain ini",
25
+ "error.event-end-before-start": "Acara tamat selepas ia bermula",
26
+ "error.event-name-required": "Namakan acara",
27
+ "error.event-not-found": "Acara tidak ditemui",
28
+ "error.event-not-started": "Acara hanya boleh direkod sebagai diadakan selepas ia bermula",
29
+ "error.event-occurrence-not-deleted": "Batalkan kejadian siri dan bukannya memadamnya",
30
+ "error.event-organizer-invalid": "Penganjur mesti pengguna domain ini",
31
+ "error.event-range-invalid": "Julat tamat selepas ia bermula",
32
+ "error.event-series-duration-invalid": "Berikan tempoh dalam minit penuh, lebih daripada sifar",
33
+ "error.event-series-ends-before-start": "Siri tamat selepas ia bermula",
34
+ "error.event-series-inactive": "Siri tidak aktif",
35
+ "error.event-series-not-found": "Siri tidak ditemui",
36
+ "error.event-series-rule-invalid": "Berikan peraturan sebagai lima medan cron: minit jam hari bulan hari-minggu",
37
+ "error.event-series-starts-at-required": "Berikan masa siri bermula",
38
+ "error.event-series-timezone-invalid": "Berikan zon waktu IANA seperti Asia/Seoul",
39
+ "error.event-series-too-many-occurrences": "Terlalu banyak kejadian sekali gus; berikan julat yang lebih pendek",
40
+ "error.event-series-until-required": "Berikan masa akhir untuk mencipta kejadian",
41
+ "error.event-status-move-invalid": "Acara tidak boleh ditukar ke status itu",
42
+ "error.event-time-required": "Berikan masa acara bermula dan tamat",
43
+ "error.event-day-invalid": "Berikan hari sebagai YYYY-MM-DD"
44
+ }
@@ -1 +1,44 @@
1
- {}
1
+ {
2
+ "title.calendar list": "日历",
3
+ "title.event list": "日程",
4
+ "field.start_at": "开始",
5
+ "field.end_at": "结束",
6
+ "field.location": "地点",
7
+ "field.organizer": "负责人",
8
+ "field.event-series": "重复日程",
9
+ "label.event-status.SCHEDULED": "已安排",
10
+ "label.event-status.POSTPONED": "已推迟",
11
+ "label.event-status.CANCELLED": "已取消",
12
+ "label.event-status.HELD": "已举行",
13
+ "task.calendar.reading.name": "日历查看",
14
+ "task.calendar.reading.description": "查看日历、日程和重复日程。",
15
+ "task.calendar.keeping.name": "日历管理",
16
+ "task.calendar.keeping.description": "查看并修改日历、日程和重复日程。",
17
+ "error.attendee-duplicate": "每位参与者只能指定一次",
18
+ "error.attendee-email-invalid": "请输入有效的电子邮件地址",
19
+ "error.attendee-user-invalid": "请指定此域的用户",
20
+ "error.attendee-user-or-email": "请用用户或电子邮件地址之一指定参与者",
21
+ "error.calendar-in-use": "请先移动或删除该日历的日程和重复日程",
22
+ "error.calendar-name-duplicate": "已有同名日历",
23
+ "error.calendar-name-required": "请输入日历名称",
24
+ "error.calendar-not-found": "请选择此域的日历",
25
+ "error.event-end-before-start": "结束时间必须晚于开始时间",
26
+ "error.event-name-required": "请输入日程名称",
27
+ "error.event-not-found": "找不到日程",
28
+ "error.event-not-started": "只有已开始的日程才能记为已举行",
29
+ "error.event-occurrence-not-deleted": "重复日程的场次不删除,请取消",
30
+ "error.event-organizer-invalid": "负责人必须是此域的用户",
31
+ "error.event-range-invalid": "期间结束必须晚于开始",
32
+ "error.event-series-duration-invalid": "请以大于0的整数分钟输入时长",
33
+ "error.event-series-ends-before-start": "重复结束必须晚于开始",
34
+ "error.event-series-inactive": "该重复日程未启用",
35
+ "error.event-series-not-found": "找不到重复日程",
36
+ "error.event-series-rule-invalid": "重复规则请输入5个cron字段(分 时 日 月 星期)",
37
+ "error.event-series-starts-at-required": "请输入重复开始时间",
38
+ "error.event-series-timezone-invalid": "请输入IANA时区,如Asia/Seoul",
39
+ "error.event-series-too-many-occurrences": "一次生成的场次过多,请缩短期间",
40
+ "error.event-series-until-required": "请输入生成场次的截止时间",
41
+ "error.event-status-move-invalid": "该日程不能改为此状态",
42
+ "error.event-time-required": "请输入日程的开始和结束时间",
43
+ "error.event-day-invalid": "请按 YYYY-MM-DD 格式输入日期"
44
+ }
package/concept.md DELETED
@@ -1,31 +0,0 @@
1
- # Calendar
2
-
3
- 일반적으로 캘린더 기능을 구현하는 데는 몇 가지 필수 데이터와 함께 다른 유용한 데이터도 포함되어야 합니다. 이러한 데이터를 저장하기 위해 데이터베이스 스키마를 다음과 같이 설계할 수 있습니다.
4
-
5
- ## 이벤트 (Event) 테이블
6
-
7
- - 이벤트 ID (Event ID)
8
- - 이벤트 이름 (Event Name)
9
- - 이벤트 설명 (Event Description)
10
- - 시작 일시 (Start Date-Time)
11
- - 종료 일시 (End Date-Time)
12
- - 이벤트의 위치 (Event Location)
13
-
14
- ## 참가자 (Attendee) 테이블
15
-
16
- - 참가자 ID (Attendee ID)
17
- - 참가자 이름 (Attendee Name)
18
- - 참가자 이메일 (Attendee Email)
19
- - 이벤트-참가자 (Event-Attendee) 테이블
20
- - 이벤트 ID (Event ID)
21
- - 참가자 ID (Attendee ID)
22
-
23
- ## 알림 (Notification) 테이블
24
-
25
- - 알림 ID (Notification ID)
26
- - 이벤트 ID (Event ID)
27
- - 알림 종류 (Notification Type) (예: 이메일, SMS 등)
28
- - 알림 일시 (Notification Date-Time)
29
- - 알림 내용 (Notification Content)
30
-
31
- 위와 같은 스키마로 구성된 데이터베이스는 이벤트, 참가자, 알림을 관리하고 추적하는 데 적합합니다. 이렇게 구성된 데이터베이스는 다양한 기능을 추가하고 확장하기에도 용이합니다.
@@ -1,23 +0,0 @@
1
- import '@material/web/icon/icon.js';
2
- import '@operato/data-grist';
3
- import { LitElement } from 'lit';
4
- export declare class AttendeeImporter extends LitElement {
5
- static styles: import("lit").CSSResult[];
6
- attendees: any[];
7
- columns: {
8
- list: {
9
- fields: string[];
10
- };
11
- pagination: {
12
- infinite: boolean;
13
- };
14
- columns: {
15
- type: string;
16
- name: string;
17
- header: string;
18
- width: number;
19
- }[];
20
- };
21
- render(): import("lit-html").TemplateResult<1>;
22
- save(): Promise<void>;
23
- }
@@ -1,93 +0,0 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import '@material/web/icon/icon.js';
3
- import '@operato/data-grist';
4
- import gql from 'graphql-tag';
5
- import { css, html, LitElement } from 'lit';
6
- import { property } from 'lit/decorators.js';
7
- import { client } from '@operato/graphql';
8
- import { i18next } from '@operato/i18n';
9
- import { isMobileDevice } from '@operato/utils';
10
- import { ButtonContainerStyles } from '@operato/styles';
11
- export class AttendeeImporter extends LitElement {
12
- constructor() {
13
- super(...arguments);
14
- this.attendees = [];
15
- this.columns = {
16
- list: { fields: ['name', 'description'] },
17
- pagination: { infinite: true },
18
- columns: [
19
- {
20
- type: 'string',
21
- name: 'name',
22
- header: i18next.t('field.name'),
23
- width: 150
24
- },
25
- {
26
- type: 'string',
27
- name: 'description',
28
- header: i18next.t('field.description'),
29
- width: 200
30
- },
31
- {
32
- type: 'checkbox',
33
- name: 'active',
34
- header: i18next.t('field.active'),
35
- width: 60
36
- }
37
- ]
38
- };
39
- }
40
- static { this.styles = [
41
- ButtonContainerStyles,
42
- css `
43
- :host {
44
- display: flex;
45
- flex-direction: column;
46
-
47
- background-color: var(--md-sys-color-surface);
48
- }
49
-
50
- ox-grist {
51
- flex: 1;
52
- }
53
- `
54
- ]; }
55
- render() {
56
- return html `
57
- <ox-grist
58
- .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
59
- .config=${this.columns}
60
- .data=${{
61
- records: this.attendees
62
- }}
63
- ></ox-grist>
64
-
65
- <div class="footer">
66
- <div filler></div>
67
- <button @click=${this.save.bind(this)} done><md-icon>save</md-icon>${i18next.t('button.save')}</button>
68
- </div>
69
- `;
70
- }
71
- async save() {
72
- const response = await client.mutate({
73
- mutation: gql `
74
- mutation importAttendees($attendees: [AttendeePatch!]!) {
75
- importAttendees(attendees: $attendees)
76
- }
77
- `,
78
- variables: { attendees: this.attendees }
79
- });
80
- if (response.errors?.length)
81
- return;
82
- this.dispatchEvent(new CustomEvent('imported'));
83
- }
84
- }
85
- __decorate([
86
- property({ type: Array }),
87
- __metadata("design:type", Array)
88
- ], AttendeeImporter.prototype, "attendees", void 0);
89
- __decorate([
90
- property({ type: Object }),
91
- __metadata("design:type", Object)
92
- ], AttendeeImporter.prototype, "columns", void 0);
93
- //# sourceMappingURL=attendee-importer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"attendee-importer.js","sourceRoot":"","sources":["../../../client/pages/attendee/attendee-importer.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,qBAAqB,CAAA;AAE5B,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IAAhD;;QAiB6B,cAAS,GAAU,EAAE,CAAA;QACpB,YAAO,GAAG;YACpC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE;YACzC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC9B,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,MAAM;oBACZ,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;oBAC/B,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,aAAa;oBACnB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;oBACtC,KAAK,EAAE,GAAG;iBACX;gBACD;oBACE,IAAI,EAAE,UAAU;oBAChB,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;oBACjC,KAAK,EAAE,EAAE;iBACV;aACF;SACF,CAAA;IAiCH,CAAC;aAzEQ,WAAM,GAAG;QACd,qBAAqB;QACrB,GAAG,CAAA;;;;;;;;;;;KAWF;KACF,AAdY,CAcZ;IA4BD,MAAM;QACJ,OAAO,IAAI,CAAA;;gBAEC,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;kBAChC,IAAI,CAAC,OAAO;gBACd;YACN,OAAO,EAAE,IAAI,CAAC,SAAS;SACxB;;;;;yBAKgB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gCAAgC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;;KAEhG,CAAA;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC,QAAQ,EAAE,GAAG,CAAA;;;;OAIZ;YACD,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE;SACzC,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;YAAE,OAAM;QAEnC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,UAAU,CAAC,CAAC,CAAA;IACjD,CAAC;;AAxD0B;IAA1B,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;mDAAsB;AACpB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;iDAuB1B","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/data-grist'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { property } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport { isMobileDevice } from '@operato/utils'\nimport { ButtonContainerStyles } from '@operato/styles'\n\nexport class AttendeeImporter extends LitElement {\n static styles = [\n ButtonContainerStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n\n background-color: var(--md-sys-color-surface);\n }\n\n ox-grist {\n flex: 1;\n }\n `\n ]\n\n @property({ type: Array }) attendees: any[] = []\n @property({ type: Object }) columns = {\n list: { fields: ['name', 'description'] },\n pagination: { infinite: true },\n columns: [\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n width: 150\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n width: 200\n },\n {\n type: 'checkbox',\n name: 'active',\n header: i18next.t('field.active'),\n width: 60\n }\n ]\n }\n\n render() {\n return html`\n <ox-grist\n .mode=${isMobileDevice() ? 'LIST' : 'GRID'}\n .config=${this.columns}\n .data=${{\n records: this.attendees\n }}\n ></ox-grist>\n\n <div class=\"footer\">\n <div filler></div>\n <button @click=${this.save.bind(this)} done><md-icon>save</md-icon>${i18next.t('button.save')}</button>\n </div>\n `\n }\n\n async save() {\n const response = await client.mutate({\n mutation: gql`\n mutation importAttendees($attendees: [AttendeePatch!]!) {\n importAttendees(attendees: $attendees)\n }\n `,\n variables: { attendees: this.attendees }\n })\n\n if (response.errors?.length) return\n\n this.dispatchEvent(new CustomEvent('imported'))\n }\n}\n"]}
@@ -1,56 +0,0 @@
1
- import '@operato/data-grist';
2
- import { PageView } from '@operato/shell';
3
- import { FetchOption } from '@operato/data-grist';
4
- import { AttendeeImporter } from './attendee-importer';
5
- declare const AttendeeListPage_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types/src/types").ScopedElementsHost>;
6
- export declare class AttendeeListPage extends AttendeeListPage_base {
7
- static styles: import("lit").CSSResult[];
8
- static get scopedElements(): {
9
- 'attendee-importer': typeof AttendeeImporter;
10
- };
11
- gristConfig: any;
12
- mode: 'CARD' | 'GRID' | 'LIST';
13
- private grist;
14
- get context(): {
15
- title: string;
16
- search: {
17
- handler: (search: string) => void;
18
- value: string;
19
- autofocus: boolean;
20
- };
21
- filter: {
22
- handler: () => void;
23
- };
24
- help: string;
25
- actions: {
26
- icon: string;
27
- emphasis: {
28
- raised: boolean;
29
- outlined: boolean;
30
- dense: boolean;
31
- danger: boolean;
32
- };
33
- title: string;
34
- action: () => Promise<void>;
35
- }[];
36
- exportable: {
37
- name: string;
38
- data: () => Promise<{}[]>;
39
- };
40
- importable: {
41
- handler: (records: any) => Promise<void>;
42
- };
43
- };
44
- render(): import("lit-html").TemplateResult<1>;
45
- pageInitialized(lifecycle: any): Promise<void>;
46
- pageUpdated(changes: any, lifecycle: any): Promise<void>;
47
- fetchHandler({ page, limit, sortings, filters }: FetchOption): Promise<{
48
- total: any;
49
- records: any;
50
- }>;
51
- _deleteAttendee(): Promise<void>;
52
- _updateAttendee(): Promise<void>;
53
- exportHandler(): Promise<{}[]>;
54
- importHandler(records: any): Promise<void>;
55
- }
56
- export {};