create-devoplus-one-app 0.0.0-stage → 0.1.0-next.158

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 (122) hide show
  1. package/README.md +62 -2
  2. package/dist/cli.mjs +17 -0
  3. package/package.json +35 -3
  4. package/templates/0001_init.sql.tpl +10 -0
  5. package/templates/AGENTS.md.tpl +286 -0
  6. package/templates/AGENT_GUIDE.md.tpl +36 -0
  7. package/templates/README.md.tpl +58 -0
  8. package/templates/_shared/AGENTS.writes.md.tpl +38 -0
  9. package/templates/_shared/README.tail.md.tpl +20 -0
  10. package/templates/_shared/files/src/app.test.ts.tpl +36 -0
  11. package/templates/_shared/files/src/index.ts.tpl +8 -0
  12. package/templates/_shared/files/src/lib/client.ts.tpl +213 -0
  13. package/templates/_shared/files/src/lib/files.ts.tpl +37 -0
  14. package/templates/_shared/files/src/lib/http.ts.tpl +101 -0
  15. package/templates/_shared/files/src/lib/mail.ts.tpl +26 -0
  16. package/templates/_shared/files/src/lib/model.ts.tpl +31 -0
  17. package/templates/_shared/files/src/lib/page.ts.tpl +71 -0
  18. package/templates/_shared/files/src/lib/record.ts.tpl +246 -0
  19. package/templates/_shared/files/src/lib/server.ts.tpl +132 -0
  20. package/templates/_shared/files/src/shell.test.ts.tpl +56 -0
  21. package/templates/agent.ts.tpl +23 -0
  22. package/templates/bakim-takvimi/AGENTS.template.md.tpl +40 -0
  23. package/templates/bakim-takvimi/README.md.tpl +45 -0
  24. package/templates/bakim-takvimi/files/AGENT_GUIDE.md.tpl +52 -0
  25. package/templates/bakim-takvimi/files/one.data.json +45 -0
  26. package/templates/bakim-takvimi/files/src/agent.ts.tpl +22 -0
  27. package/templates/bakim-takvimi/files/src/app.ts.tpl +259 -0
  28. package/templates/bakim-takvimi/files/src/jobs.ts.tpl +170 -0
  29. package/templates/bakim-takvimi/files/src/names.ts.tpl +5 -0
  30. package/templates/bakim-takvimi/files/src/queries.ts.tpl +71 -0
  31. package/templates/bakim-takvimi/files/src/schedule.test.ts.tpl +58 -0
  32. package/templates/bakim-takvimi/files/src/schedule.ts.tpl +106 -0
  33. package/templates/bakim-takvimi/files/src/shell.ts.tpl +299 -0
  34. package/templates/bakim-takvimi/manifest.json.tpl +64 -0
  35. package/templates/egitim-sertifika/AGENTS.template.md.tpl +41 -0
  36. package/templates/egitim-sertifika/README.md.tpl +46 -0
  37. package/templates/egitim-sertifika/files/AGENT_GUIDE.md.tpl +53 -0
  38. package/templates/egitim-sertifika/files/one.data.json +43 -0
  39. package/templates/egitim-sertifika/files/src/agent.ts.tpl +22 -0
  40. package/templates/egitim-sertifika/files/src/app.ts.tpl +312 -0
  41. package/templates/egitim-sertifika/files/src/certificate.test.ts.tpl +83 -0
  42. package/templates/egitim-sertifika/files/src/certificate.ts.tpl +163 -0
  43. package/templates/egitim-sertifika/files/src/jobs.ts.tpl +219 -0
  44. package/templates/egitim-sertifika/files/src/names.ts.tpl +5 -0
  45. package/templates/egitim-sertifika/files/src/queries.ts.tpl +115 -0
  46. package/templates/egitim-sertifika/files/src/shell.ts.tpl +345 -0
  47. package/templates/egitim-sertifika/manifest.json.tpl +57 -0
  48. package/templates/envanter/AGENTS.template.md.tpl +28 -0
  49. package/templates/envanter/README.md.tpl +40 -0
  50. package/templates/envanter/files/AGENT_GUIDE.md.tpl +47 -0
  51. package/templates/envanter/files/one.data.json +62 -0
  52. package/templates/envanter/files/src/agent.ts.tpl +20 -0
  53. package/templates/envanter/files/src/app.ts.tpl +204 -0
  54. package/templates/envanter/files/src/jobs.ts.tpl +30 -0
  55. package/templates/envanter/files/src/queries.ts.tpl +53 -0
  56. package/templates/envanter/files/src/shell.ts.tpl +265 -0
  57. package/templates/envanter/files/src/stock.test.ts.tpl +31 -0
  58. package/templates/envanter/files/src/stock.ts.tpl +33 -0
  59. package/templates/envanter/manifest.json.tpl +56 -0
  60. package/templates/hedef-takibi/AGENTS.template.md.tpl +37 -0
  61. package/templates/hedef-takibi/README.md.tpl +52 -0
  62. package/templates/hedef-takibi/files/AGENT_GUIDE.md.tpl +48 -0
  63. package/templates/hedef-takibi/files/one.data.json +76 -0
  64. package/templates/hedef-takibi/files/src/agent.ts.tpl +26 -0
  65. package/templates/hedef-takibi/files/src/app.ts.tpl +740 -0
  66. package/templates/hedef-takibi/files/src/progress.test.ts.tpl +53 -0
  67. package/templates/hedef-takibi/files/src/progress.ts.tpl +81 -0
  68. package/templates/hedef-takibi/files/src/queries.ts.tpl +83 -0
  69. package/templates/hedef-takibi/files/src/shell.ts.tpl +455 -0
  70. package/templates/hedef-takibi/manifest.json.tpl +38 -0
  71. package/templates/index.ts.tpl +262 -0
  72. package/templates/markdown.d.ts.tpl +6 -0
  73. package/templates/musteri-ziyaret/AGENTS.template.md.tpl +47 -0
  74. package/templates/musteri-ziyaret/README.md.tpl +48 -0
  75. package/templates/musteri-ziyaret/files/AGENT_GUIDE.md.tpl +44 -0
  76. package/templates/musteri-ziyaret/files/one.data.json +53 -0
  77. package/templates/musteri-ziyaret/files/src/agent.ts.tpl +21 -0
  78. package/templates/musteri-ziyaret/files/src/app.ts.tpl +340 -0
  79. package/templates/musteri-ziyaret/files/src/customers.ts.tpl +85 -0
  80. package/templates/musteri-ziyaret/files/src/queries.ts.tpl +67 -0
  81. package/templates/musteri-ziyaret/files/src/shell.ts.tpl +272 -0
  82. package/templates/musteri-ziyaret/files/src/visit.test.ts.tpl +65 -0
  83. package/templates/musteri-ziyaret/files/src/visit.ts.tpl +98 -0
  84. package/templates/musteri-ziyaret/manifest.json.tpl +37 -0
  85. package/templates/saha-denetimi/AGENTS.template.md.tpl +32 -0
  86. package/templates/saha-denetimi/README.md.tpl +44 -0
  87. package/templates/saha-denetimi/files/AGENT_GUIDE.md.tpl +55 -0
  88. package/templates/saha-denetimi/files/one.data.json +87 -0
  89. package/templates/saha-denetimi/files/src/agent.ts.tpl +41 -0
  90. package/templates/saha-denetimi/files/src/app.ts.tpl +444 -0
  91. package/templates/saha-denetimi/files/src/queries.ts.tpl +143 -0
  92. package/templates/saha-denetimi/files/src/score.test.ts.tpl +48 -0
  93. package/templates/saha-denetimi/files/src/score.ts.tpl +102 -0
  94. package/templates/saha-denetimi/files/src/shell.ts.tpl +585 -0
  95. package/templates/saha-denetimi/manifest.json.tpl +49 -0
  96. package/templates/shell.test.ts.tpl +30 -0
  97. package/templates/shell.ts.tpl +201 -0
  98. package/templates/uygunsuzluk-dof/AGENTS.template.md.tpl +40 -0
  99. package/templates/uygunsuzluk-dof/README.md.tpl +46 -0
  100. package/templates/uygunsuzluk-dof/files/AGENT_GUIDE.md.tpl +56 -0
  101. package/templates/uygunsuzluk-dof/files/one.data.json +99 -0
  102. package/templates/uygunsuzluk-dof/files/src/agent.ts.tpl +45 -0
  103. package/templates/uygunsuzluk-dof/files/src/app.ts.tpl +431 -0
  104. package/templates/uygunsuzluk-dof/files/src/capa.test.ts.tpl +53 -0
  105. package/templates/uygunsuzluk-dof/files/src/capa.ts.tpl +114 -0
  106. package/templates/uygunsuzluk-dof/files/src/jobs.ts.tpl +127 -0
  107. package/templates/uygunsuzluk-dof/files/src/queries.ts.tpl +102 -0
  108. package/templates/uygunsuzluk-dof/files/src/shell.ts.tpl +441 -0
  109. package/templates/uygunsuzluk-dof/manifest.json.tpl +56 -0
  110. package/templates/ziyaretci-kaydi/AGENTS.template.md.tpl +35 -0
  111. package/templates/ziyaretci-kaydi/README.md.tpl +46 -0
  112. package/templates/ziyaretci-kaydi/files/AGENT_GUIDE.md.tpl +39 -0
  113. package/templates/ziyaretci-kaydi/files/one.data.json +42 -0
  114. package/templates/ziyaretci-kaydi/files/src/agent.ts.tpl +13 -0
  115. package/templates/ziyaretci-kaydi/files/src/app.ts.tpl +293 -0
  116. package/templates/ziyaretci-kaydi/files/src/jobs.ts.tpl +49 -0
  117. package/templates/ziyaretci-kaydi/files/src/notice.ts.tpl +48 -0
  118. package/templates/ziyaretci-kaydi/files/src/queries.ts.tpl +45 -0
  119. package/templates/ziyaretci-kaydi/files/src/shell.ts.tpl +252 -0
  120. package/templates/ziyaretci-kaydi/files/src/visitor.test.ts.tpl +34 -0
  121. package/templates/ziyaretci-kaydi/files/src/visitor.ts.tpl +57 -0
  122. package/templates/ziyaretci-kaydi/manifest.json.tpl +38 -0
@@ -0,0 +1,455 @@
1
+ // {{nameTr}} — sayfa (dört görünüm: Hedeflerim · Ekip panosu · Dönem panosu · Dönemler) + hedef ayrıntısı. Liste, süzgeç,
2
+ // form, kişi seçici ve grafik hazır bileşenlerdir; seçenekler veri modelinden gelir. İlerleme ve dönem özeti sunucuda
3
+ // hesaplanır; sayfa yalnız gösterir. Hangi hedefin görüneceğine de sunucu karar verir.
4
+ import type { OneNavigationItem } from '@devoplus/one-sdk'
5
+ import { dataModel } from './data/types.ts'
6
+ import { renderClientScript } from './lib/client.ts'
7
+ import { choices, label } from './lib/model.ts'
8
+ import { escapeHtml, i18n, renderPage, translator, view, type Lang, type Strings, type Theme } from './lib/page.ts'
9
+
10
+ export const STRINGS: Strings = {
11
+ tr: {
12
+ title: {{nameTrJs}},
13
+ waiting: "Portal'dan oturum bekleniyor…",
14
+ networkError: 'İstek gönderilemedi (ağ hatası).',
15
+ requestFailed: 'İşlem yapılamadı.',
16
+ readOnly: 'Vekalet ya da Yerine Görüntüle oturumundasınız: bu uygulamada yalnız görüntüleme yapabilirsiniz.',
17
+ myIntro: 'Sorumlusu olduğunuz hedefler. Bir hedefe tıklayınca ölçülebilir sonuçları, aksiyonları ve değer geçmişi açılır.',
18
+ myTable: 'Hedeflerim',
19
+ newGoal: 'Yeni hedef',
20
+ goalSaved: 'Hedef kaydedildi.',
21
+ ownerHelp: 'Boş bırakırsanız sorumlu siz olursunuz. Doğrudan size bağlı bir çalışanı da seçebilirsiniz.',
22
+ period: 'Dönem',
23
+ teamIntro: 'Doğrudan size bağlı çalışanların hedefleri (organizasyon şemasına göre). Dönem seçilmezse en son dönem gösterilir.',
24
+ teamTable: 'Ekip hedefleri',
25
+ boardIntro: 'Dönemin ekip başına ilerlemesi. Gördüğünüz hedefler sizin ve doğrudan size bağlı çalışanlarınkidir; hedef yöneticisi bütün ekipleri görür.',
26
+ boardTable: 'Dönemin hedefleri',
27
+ boardOverall: '{period}: genel ilerleme %{n} ({count} hedef).',
28
+ boardEmpty: 'Henüz dönem tanımlanmadı.',
29
+ chartTitle: 'Ekip başına ilerleme (%)',
30
+ seriesProgress: 'İlerleme',
31
+ noTeam: '(ekipsiz)',
32
+ periodsIntro: 'Hedef dönemleri. Kapanan dönemin hedefleri yalnız görüntülenebilir.',
33
+ periodsTable: 'Dönemler',
34
+ periodForm: 'Dönem bilgileri',
35
+ newPeriod: 'Yeni dönem',
36
+ closePeriod: 'Dönemi kapat',
37
+ reopenPeriod: 'Dönemi yeniden aç',
38
+ periodSaved: 'Dönem kaydedildi.',
39
+ periodClosed: 'Dönem kapatıldı.',
40
+ periodReopened: 'Dönem yeniden açıldı.',
41
+ confirmClose: 'Dönem kapanınca bu dönemin hedefleri, ölçülebilir sonuçları ve aksiyonları değiştirilemez. Kapatılsın mı?',
42
+ managersOnly: 'Bu işlem yalnız hedef yöneticilerine açıktır.',
43
+ closeDetail: 'Kapat',
44
+ goalMeta: 'Sorumlu: {owner} - Ekip: {team} - Dönem: {period} - Ağırlık: {weight} - İlerleme: %{progress}',
45
+ locked: 'Bu hedefin dönemi kapandı: yalnız görüntüleyebilirsiniz.',
46
+ keyResults: 'Ölçülebilir sonuçlar',
47
+ checkInForm: 'Değer güncelle',
48
+ newValue: 'Yeni değer',
49
+ checkedIn: 'Değer güncellendi; ilerleme yeniden hesaplandı.',
50
+ krForm: 'Yeni ölçülebilir sonuç',
51
+ krSaved: 'Ölçülebilir sonuç eklendi.',
52
+ actionsTitle: 'Aksiyonlar',
53
+ actionForm: 'Aksiyon',
54
+ newAction: 'Yeni aksiyon',
55
+ actionSaved: 'Aksiyon kaydedildi.',
56
+ historyTitle: 'Değer geçmişi',
57
+ },
58
+ en: {
59
+ title: {{nameEnJs}},
60
+ waiting: 'Waiting for the session from the Portal…',
61
+ networkError: 'Request could not be sent (network error).',
62
+ requestFailed: 'The action could not be completed.',
63
+ readOnly: 'You are in a Delegation or View as User session: you can only view this app.',
64
+ myIntro: 'Goals you own. Click a goal to open its key results, actions and value history.',
65
+ myTable: 'My goals',
66
+ newGoal: 'New goal',
67
+ goalSaved: 'Goal saved.',
68
+ ownerHelp: 'Leave empty to own it yourself. You can also choose someone who reports directly to you.',
69
+ period: 'Period',
70
+ teamIntro: 'Goals of the people who report directly to you (according to the organization chart). Without a period, the latest one is shown.',
71
+ teamTable: 'Team goals',
72
+ boardIntro: "The period's progress per team. You see your own goals and those of your direct reports; goal managers see every team.",
73
+ boardTable: 'Goals of the period',
74
+ boardOverall: '{period}: overall progress {n}% ({count} goals).',
75
+ boardEmpty: 'No period has been defined yet.',
76
+ chartTitle: 'Progress per team (%)',
77
+ seriesProgress: 'Progress',
78
+ noTeam: '(no team)',
79
+ periodsIntro: 'Goal periods. Goals of a closed period can only be viewed.',
80
+ periodsTable: 'Periods',
81
+ periodForm: 'Period details',
82
+ newPeriod: 'New period',
83
+ closePeriod: 'Close period',
84
+ reopenPeriod: 'Reopen period',
85
+ periodSaved: 'Period saved.',
86
+ periodClosed: 'Period closed.',
87
+ periodReopened: 'Period reopened.',
88
+ confirmClose: 'Once the period is closed, its goals, key results and actions cannot be changed. Close it?',
89
+ managersOnly: 'Only goal managers can do this.',
90
+ closeDetail: 'Close',
91
+ goalMeta: 'Owner: {owner} - Team: {team} - Period: {period} - Weight: {weight} - Progress: {progress}%',
92
+ locked: "This goal's period is closed: you can only view it.",
93
+ keyResults: 'Key results',
94
+ checkInForm: 'Update value',
95
+ newValue: 'New value',
96
+ checkedIn: 'Value updated; progress recalculated.',
97
+ krForm: 'New key result',
98
+ krSaved: 'Key result added.',
99
+ actionsTitle: 'Actions',
100
+ actionForm: 'Action',
101
+ newAction: 'New action',
102
+ actionSaved: 'Action saved.',
103
+ historyTitle: 'Value history',
104
+ },
105
+ }
106
+
107
+ const MODEL = {
108
+ manage: '{{permissionPrefix}}Manage',
109
+ status: choices(dataModel, 'periods', 'status'),
110
+ labels: {
111
+ code: label(dataModel, 'periods', 'code'),
112
+ periodName: label(dataModel, 'periods', 'name'),
113
+ start: label(dataModel, 'periods', 'start_date'),
114
+ end: label(dataModel, 'periods', 'end_date'),
115
+ status: label(dataModel, 'periods', 'status'),
116
+ periodCode: label(dataModel, 'goals', 'period_code'),
117
+ goal: label(dataModel, 'goals', 'title'),
118
+ description: label(dataModel, 'goals', 'description'),
119
+ owner: label(dataModel, 'goals', 'owner_name'),
120
+ team: label(dataModel, 'goals', 'team_name'),
121
+ weight: label(dataModel, 'goals', 'weight'),
122
+ progress: label(dataModel, 'goals', 'progress'),
123
+ metric: label(dataModel, 'key_results', 'name'),
124
+ unit: label(dataModel, 'key_results', 'unit'),
125
+ startValue: label(dataModel, 'key_results', 'start_value'),
126
+ targetValue: label(dataModel, 'key_results', 'target_value'),
127
+ currentValue: label(dataModel, 'key_results', 'current_value'),
128
+ oldValue: label(dataModel, 'check_ins', 'old_value'),
129
+ newValue: label(dataModel, 'check_ins', 'new_value'),
130
+ note: label(dataModel, 'check_ins', 'note'),
131
+ actor: label(dataModel, 'check_ins', 'actor_name'),
132
+ action: label(dataModel, 'actions', 'description'),
133
+ actionOwner: label(dataModel, 'actions', 'owner_name'),
134
+ due: label(dataModel, 'actions', 'due_date'),
135
+ done: label(dataModel, 'actions', 'done'),
136
+ },
137
+ }
138
+
139
+ export function renderShell(lang: Lang, theme: Theme, path: string): string {
140
+ const tx = translator(STRINGS, lang)
141
+ const btn = (id: string, key: string, cls = 'one-btn') => `<button type="button" class="${cls}" id="${id}" data-i18n="${key}">${escapeHtml(tx(key))}</button>`
142
+ const caption = (key: string) => escapeHtml(tx(key))
143
+ const body = [
144
+ view(
145
+ '/',
146
+ path,
147
+ `${i18n(tx, 'myIntro', 'p', 'class="one-muted"')}
148
+ <one-filter id="myFilter" for="myTable"></one-filter>
149
+ <one-table id="myTable" src="/api/goals" paging="page" export="xlsx csv" clickable caption="${caption('myTable')}"></one-table>
150
+ <section id="goalCreate" class="one-card">
151
+ <h2 data-i18n="newGoal">${escapeHtml(tx('newGoal'))}</h2>
152
+ <one-form id="goalForm" no-cancel></one-form>
153
+ </section>`,
154
+ ),
155
+ view(
156
+ '/team',
157
+ path,
158
+ `${i18n(tx, 'teamIntro', 'p', 'class="one-muted"')}
159
+ <one-filter id="teamFilter" for="teamTable"></one-filter>
160
+ <one-table id="teamTable" src="/api/team/goals" paging="page" no-search export="xlsx csv" clickable caption="${caption('teamTable')}"></one-table>`,
161
+ ),
162
+ view(
163
+ '/board',
164
+ path,
165
+ `${i18n(tx, 'boardIntro', 'p', 'class="one-muted"')}
166
+ <one-filter id="boardFilter" for="boardTable"></one-filter>
167
+ <p><strong id="boardOverall"></strong></p>
168
+ <one-chart id="boardChart" type="bar" unit="%"></one-chart>
169
+ <one-table id="boardTable" src="/api/board/goals" paging="page" no-search export="xlsx csv" clickable caption="${caption('boardTable')}"></one-table>`,
170
+ ),
171
+ view(
172
+ '/periods',
173
+ path,
174
+ `${i18n(tx, 'periodsIntro', 'p', 'class="one-muted"')}
175
+ <one-table id="periodsTable" src="/api/periods" paging="page" export="xlsx csv" clickable caption="${caption('periodsTable')}"></one-table>
176
+ ${i18n(tx, 'managersOnly', 'p', 'class="one-muted" id="periodsDenied"')}
177
+ <section id="periodEditor" class="one-card" hidden>
178
+ <h2 data-i18n="periodForm">${escapeHtml(tx('periodForm'))}</h2>
179
+ <div class="app-toolbar">${btn('newPeriod', 'newPeriod')}${btn('closePeriod', 'closePeriod')}${btn('reopenPeriod', 'reopenPeriod')}</div>
180
+ <one-form id="periodForm" action="/api/periods" method="POST"></one-form>
181
+ </section>`,
182
+ ),
183
+ // Hedef ayrıntısı: görünümlerin DIŞINDADIR (her listeden açılır); görünüm değişince kapanır.
184
+ `<section id="goalDetail" class="one-card" hidden>
185
+ <div class="app-toolbar"><h2 id="goalTitle"></h2>${btn('closeDetail', 'closeDetail')}</div>
186
+ <p class="one-muted" id="goalMeta"></p>
187
+ <div id="goalLocked" class="one-message one-message-info" data-i18n="locked" hidden>${escapeHtml(tx('locked'))}</div>
188
+ <h3 data-i18n="keyResults">${escapeHtml(tx('keyResults'))}</h3>
189
+ <one-table id="krTable" no-search caption="${caption('keyResults')}"></one-table>
190
+ <div id="krEdit" class="app-grid" hidden>
191
+ <div><h4 data-i18n="checkInForm">${escapeHtml(tx('checkInForm'))}</h4><one-form id="checkInForm" no-cancel></one-form></div>
192
+ <div><h4 data-i18n="krForm">${escapeHtml(tx('krForm'))}</h4><one-form id="krForm" no-cancel></one-form></div>
193
+ </div>
194
+ <h3 data-i18n="actionsTitle">${escapeHtml(tx('actionsTitle'))}</h3>
195
+ <one-table id="actionsTable" no-search clickable caption="${caption('actionsTitle')}"></one-table>
196
+ <div id="actionEdit" hidden>
197
+ <div class="app-toolbar">${btn('newAction', 'newAction')}</div>
198
+ <one-form id="actionForm" no-cancel></one-form>
199
+ </div>
200
+ <h3 data-i18n="historyTitle">${escapeHtml(tx('historyTitle'))}</h3>
201
+ <one-table id="historyTable" no-search caption="${caption('historyTitle')}"></one-table>
202
+ </section>`,
203
+ ].join('\n')
204
+ return renderPage({ lang, theme, title: tx('title'), body })
205
+ }
206
+
207
+ export function renderShellScript(navigation: readonly OneNavigationItem[], options: { actingWrites?: boolean } = {}): string {
208
+ return renderClientScript({ strings: STRINGS, navigation, ...(options.actingWrites !== undefined ? { actingWrites: options.actingWrites } : {}), body: pageScript() })
209
+ }
210
+
211
+ function pageScript(): string {
212
+ return `
213
+ var M = ${JSON.stringify(MODEL).replace(/<\//g, '<\\/')};
214
+ var myTable = $('myTable'), teamTable = $('teamTable'), boardTable = $('boardTable'), periodsTable = $('periodsTable');
215
+ var goalForm = $('goalForm'), periodForm = $('periodForm'), chart = $('boardChart');
216
+ var krTable = $('krTable'), actionsTable = $('actionsTable'), historyTable = $('historyTable');
217
+ var checkInForm = $('checkInForm'), krForm = $('krForm'), actionForm = $('actionForm');
218
+ var periodOptions = [], openPeriodOptions = [], currentGoal = null, currentAction = null, currentPeriod = null, krOptions = [], boardPeriod = null;
219
+
220
+ function pct(v) { return v == null || v === '' ? '' : fmtNumber(v) + ' %'; }
221
+ function L2(key) { return { tr: S.tr[key], en: S.en[key] }; }
222
+ var goalColumns = [
223
+ { key: 'period_code', label: M.labels.periodCode },
224
+ { key: 'title', label: M.labels.goal },
225
+ { key: 'owner_name', label: M.labels.owner },
226
+ { key: 'team_name', label: M.labels.team },
227
+ { key: 'weight', label: M.labels.weight, type: 'number' },
228
+ { key: 'progress', label: M.labels.progress, type: 'number', format: function (v) { return pct(v); } }
229
+ ];
230
+ myTable.columns = goalColumns; teamTable.columns = goalColumns; boardTable.columns = goalColumns;
231
+ periodsTable.columns = [
232
+ { key: 'code', label: M.labels.code },
233
+ { key: 'name', label: M.labels.periodName },
234
+ { key: 'start_date', label: M.labels.start, type: 'date' },
235
+ { key: 'end_date', label: M.labels.end, type: 'date' },
236
+ { key: 'status', label: M.labels.status, type: 'choice', options: M.status }
237
+ ];
238
+ krTable.columns = [
239
+ { key: 'name', label: M.labels.metric },
240
+ { key: 'start_value', label: M.labels.startValue, type: 'number' },
241
+ { key: 'target_value', label: M.labels.targetValue, type: 'number' },
242
+ { key: 'current_value', label: M.labels.currentValue, type: 'number' },
243
+ { key: 'unit', label: M.labels.unit },
244
+ { key: 'progress', label: M.labels.progress, type: 'number', format: function (v) { return pct(v); } }
245
+ ];
246
+ actionsTable.columns = [
247
+ { key: 'description', label: M.labels.action },
248
+ { key: 'owner_name', label: M.labels.actionOwner },
249
+ { key: 'due_date', label: M.labels.due, type: 'date' },
250
+ { key: 'done', label: M.labels.done, type: 'boolean' }
251
+ ];
252
+ historyTable.columns = [
253
+ { key: 'created_at', label: { tr: 'Zaman', en: 'Time' }, type: 'datetime' },
254
+ { key: 'old_value', label: M.labels.oldValue, type: 'number' },
255
+ { key: 'new_value', label: M.labels.newValue, type: 'number' },
256
+ { key: 'note', label: M.labels.note },
257
+ { key: 'actor_name', label: M.labels.actor }
258
+ ];
259
+ periodForm.fields = [
260
+ { key: 'code', label: M.labels.code, required: true, maxLength: 20 },
261
+ { key: 'name', label: M.labels.periodName, required: true, maxLength: 120 },
262
+ { key: 'start_date', label: M.labels.start, type: 'date', required: true },
263
+ { key: 'end_date', label: M.labels.end, type: 'date', required: true }
264
+ ];
265
+ krForm.fields = [
266
+ { key: 'name', label: M.labels.metric, required: true, maxLength: 160 },
267
+ { key: 'unit', label: M.labels.unit, maxLength: 20 },
268
+ { key: 'start_value', label: M.labels.startValue, type: 'number', required: true },
269
+ { key: 'target_value', label: M.labels.targetValue, type: 'number', required: true }
270
+ ];
271
+ function actionFields(editing) {
272
+ var list = [
273
+ { key: 'description', label: M.labels.action, required: true, maxLength: 200 },
274
+ { key: 'owner', label: M.labels.actionOwner, type: 'person' },
275
+ { key: 'due_date', label: M.labels.due, type: 'date' }
276
+ ];
277
+ if (editing) list.push({ key: 'done', label: M.labels.done, type: 'boolean' });
278
+ actionForm.fields = list;
279
+ }
280
+ function goalFields() {
281
+ goalForm.fields = [
282
+ { key: 'period_id', label: L2('period'), type: 'choice', options: openPeriodOptions, required: true },
283
+ { key: 'title', label: M.labels.goal, required: true, maxLength: 160 },
284
+ { key: 'description', label: M.labels.description, type: 'longText', maxLength: 2000 },
285
+ { key: 'weight', label: M.labels.weight, type: 'number', min: 1, max: 100, required: true },
286
+ { key: 'owner', label: M.labels.owner, type: 'person', help: L2('ownerHelp') }
287
+ ];
288
+ goalForm.value = { weight: 1 };
289
+ }
290
+ function checkInFields() {
291
+ checkInForm.fields = [
292
+ { key: 'kr', label: M.labels.metric, type: 'choice', options: krOptions, required: true },
293
+ { key: 'value', label: L2('newValue'), type: 'number', required: true },
294
+ { key: 'note', label: M.labels.note, maxLength: 200 }
295
+ ];
296
+ }
297
+ function filterFields() {
298
+ var f = [{ key: 'period_id', label: L2('period'), type: 'choice', options: periodOptions }];
299
+ $('myFilter').fields = f; $('teamFilter').fields = f; $('boardFilter').fields = f;
300
+ }
301
+ function loadPeriods() {
302
+ return loadAll('/api/periods?_sort=-start_date').then(function (r) {
303
+ if (!r.ok) { one.toast({ severity: 'error', summary: problem(r) }); return; }
304
+ periodOptions = r.body.items.map(function (p) { return { value: p.id, label: p.code + ' - ' + p.name }; });
305
+ openPeriodOptions = r.body.items.filter(function (p) { return p.status === 'open'; }).map(function (p) { return { value: p.id, label: p.code + ' - ' + p.name }; });
306
+ filterFields(); goalFields();
307
+ });
308
+ }
309
+ function loadBoard(periodId) {
310
+ return api('GET', '/api/board' + (periodId ? '?period_id=' + encodeURIComponent(periodId) : '')).then(function (r) {
311
+ if (!r.ok) { one.toast({ severity: 'error', summary: problem(r) }); return; }
312
+ var s = r.body;
313
+ $('boardOverall').textContent = s.period ? t('boardOverall', { period: s.period.code, n: fmtNumber(s.overall), count: fmtNumber(s.goalCount) }) : t('boardEmpty');
314
+ chart.label = L2('chartTitle');
315
+ chart.data = {
316
+ labels: s.teams.map(function (x) { return x.team_name || t('noTeam'); }),
317
+ series: [{ name: L2('seriesProgress'), values: s.teams.map(function (x) { return x.progress; }) }]
318
+ };
319
+ });
320
+ }
321
+ function reloadLists() { myTable.reload(); teamTable.reload(); boardTable.reload(); }
322
+
323
+ // ── Hedef ayrıntısı ──
324
+ function closeDetail() { currentGoal = null; $('goalDetail').setAttribute('hidden', ''); one.setTitle(null); }
325
+ function newAction() { currentAction = null; actionFields(false); actionForm.value = {}; }
326
+ function openGoal(id) {
327
+ return api('GET', '/api/goals/' + encodeURIComponent(id)).then(function (r) {
328
+ if (!r.ok) { one.toast({ severity: 'error', summary: problem(r) }); return; }
329
+ var g = r.body, base = '/api/goals/' + encodeURIComponent(g.id);
330
+ currentGoal = g;
331
+ $('goalTitle').textContent = g.title;
332
+ $('goalMeta').textContent = t('goalMeta', { owner: g.owner_name || '-', team: g.team_name || '-', period: g.period_code || '-', weight: fmtNumber(g.weight), progress: fmtNumber(g.progress) });
333
+ var editable = g.editable === true && canWrite();
334
+ $('goalLocked').toggleAttribute('hidden', g.period_status !== 'closed');
335
+ $('krEdit').toggleAttribute('hidden', !editable); $('actionEdit').toggleAttribute('hidden', !editable);
336
+ krTable.setAttribute('src', base + '/key-results');
337
+ actionsTable.setAttribute('src', base + '/actions');
338
+ historyTable.setAttribute('src', base + '/check-ins');
339
+ $('goalDetail').removeAttribute('hidden');
340
+ one.setTitle(g.title);
341
+ newAction();
342
+ });
343
+ }
344
+ function rowOpen(e) { openGoal(e.detail.row.id); }
345
+ myTable.addEventListener('rowclick', rowOpen); teamTable.addEventListener('rowclick', rowOpen); boardTable.addEventListener('rowclick', rowOpen);
346
+ $('closeDetail').addEventListener('click', closeDetail);
347
+ krTable.addEventListener('load', function (e) {
348
+ krOptions = e.detail.items.map(function (k) { return { value: k.id, label: k.name }; });
349
+ checkInFields();
350
+ });
351
+ function formFailure(form, r) {
352
+ if (r.body && r.body.errors) form.setErrors(r.body.errors, problem(r));
353
+ else one.toast({ severity: 'error', summary: problem(r) });
354
+ }
355
+ function afterGoalChange() { if (currentGoal) openGoal(currentGoal.id); reloadLists(); }
356
+ checkInForm.addEventListener('save', function (e) {
357
+ e.preventDefault();
358
+ var v = e.detail.value;
359
+ api('POST', '/api/key-results/' + encodeURIComponent(v.kr) + '/check-ins', { value: v.value, note: v.note || '' }).then(function (r) {
360
+ if (!r.ok) { formFailure(checkInForm, r); return; }
361
+ one.toast({ severity: 'success', summary: t('checkedIn') });
362
+ checkInForm.value = {}; krTable.reload(); historyTable.reload(); afterGoalChange();
363
+ });
364
+ });
365
+ krForm.addEventListener('save', function (e) {
366
+ e.preventDefault();
367
+ if (!currentGoal) return;
368
+ var v = e.detail.value;
369
+ api('POST', '/api/goals/' + encodeURIComponent(currentGoal.id) + '/key-results', { name: v.name, unit: v.unit || '', start_value: v.start_value, target_value: v.target_value }).then(function (r) {
370
+ if (!r.ok) { formFailure(krForm, r); return; }
371
+ one.toast({ severity: 'success', summary: t('krSaved') });
372
+ krForm.value = {}; krTable.reload(); afterGoalChange();
373
+ });
374
+ });
375
+ actionsTable.addEventListener('rowclick', function (e) {
376
+ if ($('actionEdit').hasAttribute('hidden')) return;
377
+ var a = e.detail.row;
378
+ currentAction = a; actionFields(true);
379
+ actionForm.value = { description: a.description, due_date: a.due_date, done: a.done === true, owner: a.owner_org_id ? { id: a.owner_org_id, name: a.owner_name || '' } : null };
380
+ });
381
+ $('newAction').addEventListener('click', newAction);
382
+ actionForm.addEventListener('save', function (e) {
383
+ e.preventDefault();
384
+ if (!currentGoal) return;
385
+ var v = e.detail.value, body = { description: v.description, due_date: v.due_date || '' };
386
+ if (v.owner) body.owner = v.owner; else if (currentAction) body.owner = null;
387
+ var req = currentAction
388
+ ? api('PATCH', '/api/actions/' + encodeURIComponent(currentAction.id), Object.assign(body, { done: v.done === true }))
389
+ : api('POST', '/api/goals/' + encodeURIComponent(currentGoal.id) + '/actions', body);
390
+ req.then(function (r) {
391
+ if (!r.ok) { formFailure(actionForm, r); return; }
392
+ one.toast({ severity: 'success', summary: t('actionSaved') });
393
+ newAction(); actionsTable.reload();
394
+ });
395
+ });
396
+
397
+ // ── Yeni hedef ──
398
+ goalForm.addEventListener('save', function (e) {
399
+ e.preventDefault();
400
+ var v = e.detail.value, body = { period_id: v.period_id, title: v.title, description: v.description || '', weight: v.weight };
401
+ if (v.owner) body.owner = v.owner;
402
+ api('POST', '/api/goals', body).then(function (r) {
403
+ if (!r.ok) { formFailure(goalForm, r); return; }
404
+ one.toast({ severity: 'success', summary: t('goalSaved') });
405
+ goalFields(); reloadLists(); loadBoard(boardPeriod);
406
+ });
407
+ });
408
+
409
+ // ── Dönemler ──
410
+ function newPeriod() { currentPeriod = null; periodForm.setAttribute('action', '/api/periods'); periodForm.setAttribute('method', 'POST'); periodForm.value = {}; periodButtons(); }
411
+ function periodButtons() {
412
+ $('closePeriod').toggleAttribute('hidden', !currentPeriod || currentPeriod.status !== 'open');
413
+ $('reopenPeriod').toggleAttribute('hidden', !currentPeriod || currentPeriod.status !== 'closed');
414
+ }
415
+ periodsTable.addEventListener('rowclick', function (e) {
416
+ if (!can(M.manage) || !canWrite()) return;
417
+ var p = e.detail.row;
418
+ currentPeriod = p;
419
+ periodForm.setAttribute('action', '/api/periods/' + encodeURIComponent(p.id));
420
+ periodForm.setAttribute('method', 'PATCH');
421
+ periodForm.value = { code: p.code, name: p.name, start_date: p.start_date, end_date: p.end_date };
422
+ periodButtons();
423
+ });
424
+ function setStatus(to) {
425
+ if (!currentPeriod) return;
426
+ var p = currentPeriod;
427
+ api('POST', '/api/periods/' + encodeURIComponent(p.id) + '/' + to).then(function (r) {
428
+ if (!r.ok) { one.toast({ severity: 'error', summary: problem(r) }); return; }
429
+ one.toast({ severity: 'success', summary: t(to === 'close' ? 'periodClosed' : 'periodReopened') });
430
+ newPeriod(); periodsTable.reload(); loadPeriods(); closeDetail();
431
+ });
432
+ }
433
+ $('newPeriod').addEventListener('click', newPeriod);
434
+ $('closePeriod').addEventListener('click', function () {
435
+ one.confirm({ message: t('confirmClose'), severity: 'warn' }).then(function (ok) { if (ok) setStatus('close'); });
436
+ });
437
+ $('reopenPeriod').addEventListener('click', function () { setStatus('reopen'); });
438
+ periodForm.addEventListener('saved', function (e) {
439
+ e.preventDefault();
440
+ one.toast({ severity: 'success', summary: t('periodSaved') });
441
+ newPeriod(); periodsTable.reload(); loadPeriods();
442
+ });
443
+ $('boardFilter').addEventListener('change', function (e) { boardPeriod = e.detail.params.period_id || null; loadBoard(boardPeriod); });
444
+
445
+ onView(function () { closeDetail(); });
446
+ onSession(function () {
447
+ var manager = can(M.manage) && canWrite();
448
+ $('periodEditor').toggleAttribute('hidden', !manager); $('periodsDenied').toggleAttribute('hidden', manager);
449
+ $('goalCreate').toggleAttribute('hidden', !canWrite());
450
+ });
451
+ newPeriod();
452
+ onLang(function () { loadBoard(boardPeriod); });
453
+ onReady(function () { loadPeriods(); loadBoard(null); });
454
+ `
455
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "scopes": ["orgschema.read"],
3
+ "services": { "database": true, "files": false, "cache": false },
4
+ "menuIcon": "pi pi-flag",
5
+ "navigation": [
6
+ { "path": "/", "label": { "tr": "Hedeflerim", "en": "My goals" } },
7
+ { "path": "/team", "label": { "tr": "Ekip panosu", "en": "Team board" } },
8
+ { "path": "/board", "label": { "tr": "Dönem panosu", "en": "Period board" } },
9
+ { "path": "/periods", "label": { "tr": "Dönemler", "en": "Periods" }, "permission": "{{permissionPrefix}}Manage" }
10
+ ],
11
+ "permissions": [
12
+ { "code": "{{permissionPrefix}}Manage", "name": { "tr": "Dönemleri ve bütün hedefleri yönet", "en": "Manage periods and all goals" } }
13
+ ],
14
+ "agent": {
15
+ "summary": {
16
+ "tr": "Hedef ve aksiyon takibi: dönemler, ekip hedefleri, ölçülebilir sonuçlar ve sunucuda hesaplanan ilerleme.",
17
+ "en": "Goal and action tracking: periods, team goals, key results and progress computed on the server."
18
+ },
19
+ "tools": [
20
+ {
21
+ "name": "period-summary",
22
+ "title": { "tr": "Dönem hedef özeti", "en": "Period goal summary" },
23
+ "description": {
24
+ "tr": "Bir dönemin (verilmezse en son dönemin) genel ilerlemesini, ekip başına hedef sayısını ve ağırlıklı ilerlemeyi ve en geride kalan beş hedefi döndürür. Salt okunur; yalnız hedef yöneticisine açıktır.",
25
+ "en": "Returns a period's (the latest one if not given) overall progress, goal count and weighted progress per team, and the five goals furthest behind. Read-only; only for goal managers."
26
+ },
27
+ "readOnly": true,
28
+ "permission": "{{permissionPrefix}}Manage",
29
+ "input": {
30
+ "type": "object",
31
+ "properties": {
32
+ "period": { "type": "string", "minLength": 1, "maxLength": 20, "description": "Dönem kodu (ör. 2026-Ç4); verilmezse en son başlayan dönem" }
33
+ }
34
+ }
35
+ }
36
+ ]
37
+ }
38
+ }