Skip to content

Commit 4342cfe

Browse files
committed
refactor(habits-widget): remove currentValue and use history for tracking
- Removed currentValue field from HabitsWidgetItem and related schema definitions - Updated progress calculation to use history length instead of currentValue - Modified client-side habits-widget utils to manage history entries for increments/decrements - Added saveState callback to update widget state when history changes - Applied changes to UI rendering to reflect history-based counts - Ensured unique IDs are generated for items and history entries - Updated widget rendering and initialization to work with state history - Adjusted related widgets typings and schemas for state handling improvements
1 parent 166cc24 commit 4342cfe

File tree

12 files changed

+264
-144
lines changed

12 files changed

+264
-144
lines changed

web/WIDGETS_DOCUMENTATION.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ interface HabitsWidgetItem {
7070
color: string;
7171
minValue: number;
7272
maxValue: number;
73-
currentValue: number;
7473
history: Array<{
7574
id: number;
7675
time: string;
@@ -108,7 +107,6 @@ The widget is integrated with the general widget system of the project and uses
108107
"color": "blue",
109108
"minValue": 0,
110109
"maxValue": 8,
111-
"currentValue": 0,
112110
"history": []
113111
}
114112
]

web/WIDGETS_DOCUMENTATION_RU.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@
5858
#### Техническая реализация:
5959

6060
##### Файлы реализации:
61+
6162
- `web/src/server/widgets/habits-widget.ts` - Основной класс виджета и шаблон отображения
6263
- `web/src/server/widgets/habits-widget.utils.ts` - Вспомогательные функции на стороне клиента
6364

6465
##### Структура данных:
66+
6567
```typescript
6668
interface HabitsWidgetItem {
6769
id: string;
@@ -70,7 +72,6 @@ interface HabitsWidgetItem {
7072
color: string;
7173
minValue: number;
7274
maxValue: number;
73-
currentValue: number;
7475
history: Array<{
7576
id: number;
7677
time: string;
@@ -79,6 +80,7 @@ interface HabitsWidgetItem {
7980
```
8081

8182
##### Основные функции:
83+
8284
- `showHabitsModal(modalId)` - Отображение модального окна
8385
- `hideHabitsModal(modalId)` - Скрытие модального окна
8486
- `addItem(itemId)` - Увеличение значения привычки
@@ -89,6 +91,7 @@ interface HabitsWidgetItem {
8991
#### Интеграция с системой:
9092

9193
Виджет интегрирован с общей системой виджетов проекта и использует следующие компоненты:
94+
9295
- Zod схемы для валидации данных
9396
- Formly для конфигурации формы настройки виджета
9497
- Lucide Icons для иконок
@@ -108,7 +111,6 @@ interface HabitsWidgetItem {
108111
"color": "blue",
109112
"minValue": 0,
110113
"maxValue": 8,
111-
"currentValue": 0,
112114
"history": []
113115
}
114116
]
@@ -118,12 +120,14 @@ interface HabitsWidgetItem {
118120
## План развития виджетов
119121

120122
### Ближайшие задачи:
123+
121124
1. Реализация дополнительных виджетов (часы, календарь)
122125
2. Улучшение системы настройки виджетов
123126
3. Добавление возможности сохранения состояния виджетов в базе данных
124127
4. Реализация системы логирования изменений виджетов
125128

126129
### Долгосрочные цели:
130+
127131
1. Создание расширяемой системы виджетов с возможностью добавления новых типов
128132
2. Реализация механизма обмена виджетами между пользователями
129133
3. Добавление системы уведомлений для виджетов
@@ -132,10 +136,12 @@ interface HabitsWidgetItem {
132136
## Решение проблем
133137

134138
### Известные проблемы:
139+
135140
1. Некоторые цветовые классы Tailwind CSS не генерируются автоматически для пользовательских цветов
136141
2. Для корректной работы некоторых функций требуется глобальная область видимости
137142

138143
### Рекомендации:
144+
139145
1. При добавлении новых цветов убедитесь, что они корректно отображаются в Tailwind CSS
140146
2. При разработке новых виджетов следуйте установленным паттернам реализации
141-
3. Используйте предоставленные утилиты для работы с DOM вместо прямого обращения к элементам
147+
3. Используйте предоставленные утилиты для работы с DOM вместо прямого обращения к элементам

web/src/app/pages/dashboards/[dashboardId]/widgets/[widgetId]/index.page.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
12
import { RouteMeta } from '@analogjs/router';
23
import { AsyncPipe, TitleCasePipe } from '@angular/common';
34
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
@@ -11,6 +12,7 @@ import { catchError, first, map, shareReplay, tap } from 'rxjs';
1112
import { ClientValidationErrorType } from '../../../../../../server/types/client-error-type';
1213
import {
1314
WIDGETS_FORMLY_FIELDS,
15+
WIDGETS_RENDERERS,
1416
WidgetsType,
1517
} from '../../../../../../server/widgets/widgets';
1618
import { NoSanitizePipe } from '../../../../../directives/no-sanitize.directive';
@@ -138,7 +140,14 @@ export default class DashboardsWidgetsEditPageComponent {
138140
shareReplay(1)
139141
);
140142

141-
html$ = this.data$.pipe(mapToRenderHtml(true));
143+
html$ = this.data$.pipe(
144+
mapToRenderHtml(true, (state, widget) => {
145+
this.widgetsService
146+
.updateState({ id: widget.id, state })
147+
.pipe(first())
148+
.subscribe();
149+
})
150+
);
142151

143152
private setFormFields(options?: { clientError?: ClientValidationErrorType }) {
144153
this.formHandlerService.updateFormFields(this.formFields$, {
@@ -151,14 +160,19 @@ export default class DashboardsWidgetsEditPageComponent {
151160

152161
onSubmit(data: { type: string; id: string }) {
153162
this.setFormFields({});
163+
164+
const widget = {
165+
...data,
166+
options: {
167+
...this.formModel,
168+
type: data.type,
169+
} as WidgetsType,
170+
};
171+
172+
WIDGETS_RENDERERS[data.type].beforeSave?.(widget);
173+
154174
this.widgetsService
155-
.update({
156-
...data,
157-
options: {
158-
...this.formModel,
159-
type: data.type,
160-
} as WidgetsType,
161-
})
175+
.update(widget)
162176
.pipe(
163177
catchError(err =>
164178
this.errorHandlerService.catchAndProcessServerError({

web/src/app/pages/dashboards/[dashboardId]/widgets/add/[type]/index.page.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import { LucideAngularModule } from 'lucide-angular';
99
import { catchError, first, map, of, shareReplay, switchMap, tap } from 'rxjs';
1010

1111
import { ClientValidationErrorType } from '../../../../../../../server/types/client-error-type';
12+
import { CreateWidgetType } from '../../../../../../../server/types/WidgetSchema';
1213
import {
1314
WIDGETS_FORMLY_FIELDS,
15+
WIDGETS_RENDERERS,
1416
WidgetsType,
1517
} from '../../../../../../../server/widgets/widgets';
1618
import { mapFormlyTypes } from '../../../../../../formly/get-formly-type';
@@ -142,15 +144,20 @@ export default class DashboardsWidgetsAddByTypePageComponent {
142144

143145
onSubmit(data: { type: string; dashboardId: string }) {
144146
this.setFormFields({});
145-
this.widgetsService
146-
.create({
147-
dashboardId: data.dashboardId,
147+
148+
const widget = {
149+
dashboardId: data.dashboardId,
150+
type: data.type,
151+
options: {
152+
...this.formModel,
148153
type: data.type,
149-
options: {
150-
...this.formModel,
151-
type: data.type,
152-
} as unknown as WidgetsType,
153-
})
154+
} as unknown as WidgetsType,
155+
} as CreateWidgetType;
156+
157+
WIDGETS_RENDERERS[data.type].beforeSave?.(widget);
158+
159+
this.widgetsService
160+
.create(widget)
154161
.pipe(
155162
catchError(err =>
156163
this.errorHandlerService.catchAndProcessServerError({

web/src/app/utils/render.utils.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@ import { createIcons, icons } from 'lucide';
33
import { forkJoin, map, mergeMap, of, switchMap, tap } from 'rxjs';
44

55
import { WidgetRender, WidgetType } from '../../server/types/WidgetSchema';
6+
import { isSSR } from '../../server/utils/is-ssr';
7+
import { setHabitItems } from '../../server/widgets/habits-widget.utils';
68
import {
9+
CreateWidgetsStateType,
710
WIDGETS_FORMLY_FIELDS,
811
WIDGETS_RENDERERS,
912
} from '../../server/widgets/widgets';
1013
import { mapFormlyTypes } from '../formly/get-formly-type';
1114
import { DashboardsService } from '../services/dashboards.service';
1215
import { WidgetsService } from '../services/widgets.service';
13-
import { isSSR } from '../../server/utils/is-ssr';
1416

15-
export function mapToRenderHtml(staticMode = true) {
17+
export function mapToRenderHtml(
18+
staticMode = true,
19+
saveState?: (state: CreateWidgetsStateType, widget: WidgetType) => void
20+
) {
1621
return mergeMap(
1722
(data: { render: WidgetRender<unknown>; widget: WidgetType } | null) =>
18-
data?.render?.render(data.widget, { static: staticMode }).pipe(
23+
data?.render?.render(data.widget, { static: staticMode, saveState }).pipe(
1924
tap(() =>
2025
requestAnimationFrame(() => {
2126
requestAnimationFrame(() => {
@@ -44,6 +49,11 @@ export function mapToRenderDataByDashboardIdAndWidgetId() {
4449
WIDGETS_FORMLY_FIELDS[widget.type] || []
4550
);
4651
const render = WIDGETS_RENDERERS[widget.type];
52+
setHabitItems(
53+
widget.id,
54+
widget.options?.items || [],
55+
widget.state?.history || []
56+
);
4757
return { render, widget, fields, model };
4858
})
4959
),

web/src/server/types/WidgetSchema.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { Observable } from 'rxjs';
22
import { z } from 'zod';
33

44
import { WidgetScalarFieldEnum } from '../generated/prisma/internal/prismaNamespace';
5-
import { CreateWidgetsSchema, WidgetsSchema } from '../widgets/widgets';
5+
import {
6+
CreateWidgetsSchema,
7+
CreateWidgetsStateSchema,
8+
CreateWidgetsStateType,
9+
WidgetsSchema,
10+
} from '../widgets/widgets';
611

712
export const WidgetSchema = z.object({
813
[WidgetScalarFieldEnum.id]: z.string().uuid(),
@@ -14,7 +19,7 @@ export const WidgetSchema = z.object({
1419
[WidgetScalarFieldEnum.columnCount]: z.number().nullish(),
1520
[WidgetScalarFieldEnum.rowCount]: z.number().nullish(),
1621
[WidgetScalarFieldEnum.isBlackTheme]: z.boolean().nullish(),
17-
[WidgetScalarFieldEnum.isActive]: z.boolean().nullish(),
22+
[WidgetScalarFieldEnum.isActive]: z.boolean().nullish(),
1823
[WidgetScalarFieldEnum.backgroundColor]: z.string().nullish(),
1924
[WidgetScalarFieldEnum.primaryColor]: z.string().nullish(),
2025
[WidgetScalarFieldEnum.positiveColor]: z.string().nullish(),
@@ -72,29 +77,37 @@ export type UpdateWidgetType = z.infer<typeof UpdateWidgetSchema>;
7277

7378
export const UpdateWidgetStateSchema = z.object({
7479
[WidgetScalarFieldEnum.id]: z.string().uuid(),
75-
[WidgetScalarFieldEnum.state]: z.any().nullish(),
80+
[WidgetScalarFieldEnum.state]: CreateWidgetsStateSchema,
7681
});
7782

7883
export type UpdateWidgetStateType = z.infer<typeof UpdateWidgetStateSchema>;
7984

8085
export type WidgetRenderInitFunctionOptions = {
8186
static?: boolean;
87+
saveState?: (state: CreateWidgetsStateType, widget: WidgetType) => void;
8288
};
8389

8490
export type WidgetRenderRenderFunctionOptions = {
8591
static?: boolean;
8692
init?: boolean;
93+
saveState?: (state: CreateWidgetsStateType, widget: WidgetType) => void;
8794
};
8895

89-
export type WidgetRenderType<T> = WidgetType & { options?: T };
96+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
97+
export interface WidgetRenderType<T, S = any> extends WidgetType {
98+
options?: T;
99+
state?: S;
100+
}
90101

91-
export interface WidgetRender<T> {
102+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
103+
export interface WidgetRender<T, S = any> {
92104
init?(
93-
widget: WidgetRenderType<T>,
105+
widget: WidgetRenderType<T, S>,
94106
options?: WidgetRenderInitFunctionOptions
95107
): void;
96108
render(
97-
widget: WidgetRenderType<T>,
109+
widget: WidgetRenderType<T, S>,
98110
options?: WidgetRenderRenderFunctionOptions
99111
): Observable<string>;
100-
}
112+
beforeSave?(widget: Partial<WidgetRenderType<T, S>>): void;
113+
}

web/src/server/widgets/calendar-widget.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ export const CalendarWidgetSchema = z.object({
5252
.default(CalendarWidgetWeekday.sunday),
5353
});
5454

55+
export const CalendarWidgetStateSchema = z.object({
56+
type: z.literal('calendar'),
57+
});
58+
5559
export type CalendarWidgetType = z.infer<typeof CalendarWidgetSchema>;
5660

5761
export const CALENDAR_FORMLY_FIELDS: FormlyFieldConfig[] = [

0 commit comments

Comments
 (0)