Skip to content

Commit 06197b1

Browse files
committed
add file-item in response card, edit in profile, add delete file in file-item component, add modal for user achievements
1 parent f068e75 commit 06197b1

15 files changed

Lines changed: 367 additions & 100 deletions

File tree

projects/social_platform/src/app/auth/models/user.model.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** @format */
22

33
import { Project } from "@models/project.model";
4+
import { FileModel } from "@office/models/file.model";
45
import { Skill } from "@office/models/skill";
56
import { Program } from "@office/program/models/program.model";
67

@@ -25,7 +26,7 @@ export class Achievement {
2526
title!: string;
2627
status!: string;
2728
year!: number;
28-
files!: string[];
29+
files!: string[] | FileModel[];
2930
}
3031

3132
export class Education {

projects/social_platform/src/app/office/features/response-card/response-card.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
@if(response.accompanyingFile){
1616
<app-file-item
17-
class="text__file"
17+
class="response__file"
18+
[canDelete]="true"
1819
[link]="response.accompanyingFile.link"
1920
[name]="response.accompanyingFile.name"
2021
[type]="response.accompanyingFile.mimeType"

projects/social_platform/src/app/office/features/response-card/response-card.component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
border: 0.5px solid var(--medium-grey-for-outline);
1515
border-radius: var(--rounded-lg);
1616
}
17+
18+
&__file {
19+
margin-top: 4px;
20+
}
1721
}
1822

1923
.lists {

projects/social_platform/src/app/office/features/response-card/response-card.component.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,7 @@ import { IconComponent } from "@uilib";
3939
templateUrl: "./response-card.component.html",
4040
styleUrl: "./response-card.component.scss",
4141
standalone: true,
42-
imports: [
43-
RouterLink,
44-
AvatarComponent,
45-
TagComponent,
46-
IconComponent,
47-
ButtonComponent,
48-
UserRolePipe,
49-
AsyncPipe,
50-
FileItemComponent,
51-
ProjectVacancyCardComponent,
52-
],
42+
imports: [IconComponent, FileItemComponent],
5343
})
5444
export class ResponseCardComponent implements OnInit {
5545
constructor(private readonly authService: AuthService) {}

projects/social_platform/src/app/office/feed/feed.component.ts

Lines changed: 80 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import {
44
AfterViewInit,
55
ChangeDetectionStrategy,
6+
ChangeDetectorRef,
67
Component,
78
ElementRef,
89
inject,
@@ -58,10 +59,12 @@ import { OpenVacancyComponent } from "./shared/open-vacancy/open-vacancy.compone
5859
styleUrl: "./feed.component.scss",
5960
})
6061
export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
61-
route = inject(ActivatedRoute);
62-
projectNewsService = inject(ProjectNewsService);
63-
profileNewsService = inject(ProfileNewsService);
64-
feedService = inject(FeedService);
62+
private readonly route = inject(ActivatedRoute);
63+
private readonly projectNewsService = inject(ProjectNewsService);
64+
private readonly profileNewsService = inject(ProfileNewsService);
65+
private readonly feedService = inject(FeedService);
66+
private readonly cdref = inject(ChangeDetectorRef);
67+
private observer?: IntersectionObserver;
6568

6669
/**
6770
* ИНИЦИАЛИЗАЦИЯ КОМПОНЕНТА
@@ -71,49 +74,36 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
7174
* - Настраивает наблюдение за изменениями фильтров
7275
* - Инициализирует отслеживание просмотров элементов
7376
*/
77+
/**
78+
* ИНИЦИАЛИЗАЦИЯ КОМПОНЕНТА
79+
*/
7480
ngOnInit() {
75-
// Получаем предзагруженные данные из резолвера
7681
const routeData$ = this.route.data
7782
.pipe(map(r => r["data"]))
7883
.subscribe((feed: ApiPagination<FeedItem>) => {
7984
this.feedItems.set(feed.results);
8085
this.totalItemsCount.set(feed.count);
8186

82-
// Настраиваем отслеживание просмотров элементов
83-
setTimeout(() => {
84-
const observer = new IntersectionObserver(this.onFeedItemView.bind(this), {
85-
root: document.querySelector(".office__body"),
86-
rootMargin: "0px 0px 0px 0px",
87-
threshold: 0,
88-
});
89-
90-
document.querySelectorAll(".page__item").forEach(e => {
91-
observer.observe(e);
92-
});
93-
});
87+
this.initObserver();
9488
});
9589
this.subscriptions$().push(routeData$);
9690

97-
// Отслеживаем изменения параметров фильтрации
9891
const queryParams$ = this.route.queryParams
9992
.pipe(
100-
map(params => params["includes"]),
101-
tap(includes => {
102-
this.includes.set(includes);
103-
}),
93+
map(p => p["includes"]),
94+
tap(includes => this.includes.set(includes)),
10495
skip(1),
10596
concatMap(includes => {
10697
this.totalItemsCount.set(0);
107-
this.feedPage.set(0);
108-
98+
this.feedPage.set(1);
10999
return this.onFetch(0, this.perFetchTake(), includes ?? ["vacancy", "projects", "news"]);
110100
})
111101
)
112102
.subscribe(feed => {
113103
this.feedItems.set(feed);
114-
115104
setTimeout(() => {
116-
this.feedRoot?.nativeElement.children[0].scrollIntoView({ behavior: "smooth" });
105+
this.feedRoot?.nativeElement.scrollTo({ top: 0 });
106+
this.observeNewElements();
117107
});
118108
});
119109
this.subscriptions$().push(queryParams$);
@@ -132,7 +122,7 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
132122
const scrollEvents$ = fromEvent(target, "scroll")
133123
.pipe(
134124
concatMap(() => this.onScroll()),
135-
throttleTime(500) // Ограничиваем частоту запросов
125+
throttleTime(500)
136126
)
137127
.subscribe(noop);
138128

@@ -141,7 +131,8 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
141131
}
142132

143133
ngOnDestroy() {
144-
this.subscriptions$().forEach($ => $.unsubscribe());
134+
this.subscriptions$().forEach(s => s.unsubscribe());
135+
this.observer?.disconnect();
145136
}
146137

147138
@ViewChild("feedRoot") feedRoot?: ElementRef<HTMLElement>;
@@ -224,7 +215,7 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
224215
* - Отмечает новости как прочитанные при попадании в область видимости
225216
* - Различает новости проектов и профилей
226217
*/
227-
onFeedItemView(entries: IntersectionObserverEntry[]): void {
218+
private onFeedItemView(entries: IntersectionObserverEntry[]): void {
228219
const items = entries
229220
.map(e => {
230221
return Number((e.target as HTMLElement).dataset["id"]);
@@ -266,33 +257,41 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
266257
* - Проверяет, достигнут ли конец списка
267258
* - Загружает следующую порцию элементов при необходимости
268259
*/
269-
onScroll() {
270-
// Проверяем, загружены ли все элементы
271-
if (this.totalItemsCount() && this.feedItems().length >= this.totalItemsCount()) return of({});
260+
/**
261+
* ОБРАБОТКА ПРОКРУТКИ ДЛЯ БЕСКОНЕЧНОЙ ЗАГРУЗКИ
262+
*/
263+
private onScroll() {
264+
const container = document.querySelector(".office__body") as HTMLElement;
265+
if (!container) return of({});
272266

273-
const target = document.querySelector(".office__body");
274-
if (!target || !this.feedRoot) return of({});
267+
const isNearBottom =
268+
container.scrollHeight - container.scrollTop - container.clientHeight < 100;
275269

276-
// Вычисляем, нужно ли загружать новые элементы
277-
const diff =
278-
target.scrollTop -
279-
this.feedRoot.nativeElement.getBoundingClientRect().height +
280-
window.innerHeight;
270+
if (!isNearBottom) return of({});
281271

282-
if (diff > 0) {
283-
return this.onFetch(
284-
this.feedPage() * this.perFetchTake(),
285-
this.perFetchTake(),
286-
this.includes()
287-
).pipe(
288-
tap((feedChunk: FeedItem[]) => {
289-
this.feedPage.update(page => page + 1);
290-
this.feedItems.update(items => [...items, ...feedChunk]);
291-
})
292-
);
272+
// Предотвращаем множественные запросы
273+
if (this.feedItems().length >= this.totalItemsCount()) {
274+
return of([]);
293275
}
294276

295-
return of({});
277+
return this.onFetch(
278+
this.feedPage() * this.perFetchTake(),
279+
this.perFetchTake(),
280+
this.includes()
281+
).pipe(
282+
tap((feedChunk: FeedItem[]) => {
283+
if (feedChunk.length > 0) {
284+
this.feedPage.update(p => p + 1);
285+
this.feedItems.update(items => [...items, ...feedChunk]);
286+
287+
// ВАЖНО: обновляем observer после добавления новых элементов
288+
setTimeout(() => {
289+
this.observeNewElements();
290+
this.cdref.detectChanges();
291+
}, 0);
292+
}
293+
})
294+
);
296295
}
297296

298297
/**
@@ -306,7 +305,7 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
306305
* ЧТО ВОЗВРАЩАЕТ:
307306
* @returns Observable<FeedItem[]> - массив элементов ленты
308307
*/
309-
onFetch(
308+
private onFetch(
310309
offset: number,
311310
limit: number,
312311
includes: FeedItemType[] = ["project", "vacancy", "news"]
@@ -318,4 +317,33 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
318317
map(res => res.results)
319318
);
320319
}
320+
321+
private initObserver() {
322+
if (this.observer) {
323+
this.observer.disconnect();
324+
}
325+
326+
this.observer = new IntersectionObserver(this.onFeedItemView.bind(this), {
327+
root: null,
328+
rootMargin: "0px",
329+
threshold: 0.1,
330+
});
331+
332+
this.observeNewElements();
333+
}
334+
335+
/**
336+
* ДОБАВЛЕНИЕ НОВЫХ ЭЛЕМЕНТОВ
337+
*/
338+
private observeNewElements() {
339+
// Небольшая задержка для рендеринга DOM
340+
setTimeout(() => {
341+
document.querySelectorAll(".page__item").forEach(element => {
342+
if (element && !element.hasAttribute("data-observed")) {
343+
this.observer?.observe(element);
344+
element.setAttribute("data-observed", "true");
345+
}
346+
});
347+
}, 0);
348+
}
321349
}

projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
display: flex;
1212
align-items: center;
1313
text-align: center;
14+
justify-content: center;
1415
}
1516

1617
&__project {

projects/social_platform/src/app/office/profile/edit/edit.component.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,17 @@ <h1 class="profile__title">редактирование профиля</h1>
818818
<p class="text-body-10 education__text">
819819
{{ achievementItem.status }}
820820
</p>
821+
822+
@if (achievementItem.files?.length) { @for (file of achievementItem.files; track
823+
$index) {
824+
<app-file-item
825+
[type]="file.mimeType"
826+
[name]="file.name"
827+
[size]="file.size"
828+
[link]="file.link"
829+
>
830+
</app-file-item>
831+
} }
821832
</div>
822833

823834
<div class="profile__action--icons">

projects/social_platform/src/app/office/profile/edit/edit.component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@
247247
:first-child {
248248
color: var(--black);
249249
}
250+
251+
:last-child & app-file-item {
252+
margin-top: 3px;
253+
}
250254
}
251255

252256
.edit-icon,

0 commit comments

Comments
 (0)