정리전 로컬 데이터 푸시
This commit is contained in:
SEAN-59 2025-10-28 20:47:58 +09:00
parent 92feb9bbe2
commit 7284dca6f2
38 changed files with 540 additions and 96 deletions

View File

@ -65,7 +65,15 @@ namespace Front.Program.Pages;
- [C# 코드 스타일 권장 사항 (Microsoft Docs)]("https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/coding-style/coding-conventions")을 따른다.
- 클래스, 인터페이스, 메서드, 변수, 상수 등 모든 명명 및 스타일은 위 문서를 기준으로 작성한다.
- **필요**시 `.editorconfig` 파일을 통해 프로젝트 전체에 스타일 자동 적용 도구를 사용할 수 있다.
- 작업자 이외의 제 3자에게 유출되어야 하는 데이터의 이름을 작성시에는 그 의미를 쉽게 유추 할 수 없게 영단어를 줄여서 쓴다.
1. 단어의 조합에서 각 단어의 앞 글자는 `대문자로 시작`하고 `_ , - , 공백` 등은 사용하지 않는다.
- 예) `username`, `user-name`, `user name``UserName`
2. 단어를 줄일때는 자음에 해당하는 영문자만 사용한다. (A, E, I, O, U 는 제거)
- 예) `password``Pswd`, `phone number``PhnNmbr`
3. 단어의 시작이 모음일 경우에는 첫 글자에 한해서만 모음을 허용한다.
- 예) `email``Emil`, `address``Adrss`
4. 단어를 줄일때는 2글자로 하며, 단어의 길이가 2글자 이하인 경우에는 줄이지 않는다.
- 예) `user``Us`, `id``Id`, `username``UsNm`
---
## 3⃣ Git 규칙

View File

@ -0,0 +1,24 @@
# Front 웹 개발 후 서버 배포 방법 (Local)
## 1. .NET 실행 설정 프로파일 동작
- 단순하게 run 하는 방식으로 `./bin/Debug/net8.0/` 밑에 프로젝트 결과가 생성된다.
- `./wwwroot` 내부에 있는 정적리소스들을 활용하여 웹 페이지를 보여준다.
- 해당 방법은 단순하게 서버에도 연결하지 않고 그냥 Front 로컬로 사용하는 방법이다.
## 2. package.json 에서 npm 스크립트 실행
- `dotnet publish -c Release -o ./publish` 를 실행해서 ./publish 밑에 결과물을 생성한다.
- 그런데 npm에서는 dotnet이 실행 할 수 없기에 직접 dotnet의 경로를 지정해서 해야한다.
- ./publish 폴더를 퍼블리시 동작마다 삭제를 안해주면 계속 중첩해서 추가가 되기에 일단 삭제를 하는 동작을 해줘야 한다.
- Front 프로젝트의 퍼블리시를 위해서는 Tailwind CSS 를 사용하기에 `build:css`를 먼저 실행해준다.
- 그리고 `build:publish` 를 실행하면서 `./publish` 폴더를 삭제하고 다시 퍼블리시를 한다.
- 마지막으로 생성된 `./publish/wwwroot` 폴더 안에 내용 전부를 `../AcaMate_API/publish/debug/wwwroot` 폴더에 복사한다.
# Front 웹 개발 후 서버 배포 방법 (SERVER)
## 1. Development, Production 공통
- `build:css` 실행을 먼저 진행을 해주고 나서 Local로 테스트를 진행한다.
- 이상 없음이 판단 되면 gitea 에 push 를 해준다.
- Jenkins 동작을 하게 될 경우 서버 저장소에 클론을 하게 된다.
- 기존에 있는 데이터는 삭제하지 않기에 만약에 충돌이 있는 경우가 있다.
- 기존 디렉터리와 비교해서 맞지 않는 코드의 경우에는 수동으로 삭제를 진행한다.
- `acamate-front-build-debug` 또는 `acamate-front-build-relase` 컨테이너를 수동으로 실행시켜 준다.
- private repository 나 따로 직접 서버에 반영을 해야 하는 리소스가 있는 경우에는 직접 넣어준다.
- `acamate-front-build-debug` 또는 `acamate-front-build-relase` 컨테이너를 수동으로 실행시켜 준다.

9
Documents/Tip.md Normal file
View File

@ -0,0 +1,9 @@
# Tip
## JSON 파싱시
- JSON 파싱시에 소문자로 들어오는데 이를 C#의 클래스에 맞게 넣어주기 위해서는 다음과 같은 구문을 넣어줘야지 안그러면 제대로 파싱이 되지 않는다.
- 둘 다 소문자이면 아무 상관 없을건데 여기서 소문자로 쓰면 경고가 나오니까
```c#
var userData = JsonSerializer.Deserialize<UserData>(userDataJson,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
```

View File

@ -53,6 +53,7 @@ builder.Services.AddScoped<QueryParamService>();
builder.Services.AddScoped<LoadingService>();
builder.Services.AddSingleton<UserStateService>();
builder.Services.AddSingleton<NavigationService>(); // 추가
// builder.Services.AddSingleton<LoggerService>(sp

View File

@ -1,29 +1,44 @@
@inherits LayoutComponentBase
@inherits LayoutComponentBase
@implements IDisposable
<div class="min-h-screen flex flex-col bg-gray-50 text-gray-900">
<div class="min-h-screen flex flex-col text-gray-900">
<!-- Top 영역 -->
@* <TopBanner /> *@
@if(isAcademy)
{
<div class="flex flex-1 flex-col md:flex-row">
<div class="flex flex-1 flex-col md:flex-row ">
@if (!isIntro && UserStateService.isLogin)
{
<div class="hidden md:block w-64 bg-white shadow-lg border-r border-gray-200 fixed top-0 bottom-0">
<LeftSideAcademy/>
</div>
<div class="flex flex-1 flex-col md:ml-64">
<div class="fixed top-0 right-0 md:left-64 left-0 z-10">
@* <div class="fixed flex flex-1 flex-col min-w-0"> *@
<div class="hidden md:block w-64 bg-normal-light shadow-lg fixed top-0 bottom-0 overflow-y-auto">
<SympleProfile/>
<LeftSideAcademy/>
</div>
@* </div> *@
<div class="flex flex-1 flex-col bg-white md:ml-64 min-w-0 min-h-screen">
<!-- TopNav 고정 -->
<div class="h-18 top-0 right-0 md:left-64 left-0 z-20 fixed bg-normal-light">
<TopNavAcademy/>
</div>
<div class="flex-1 mt-16">
@Body
<!-- 빨간 그림자 컨테이너 - TopNav 바로 아래 고정 -->
<div class="fixed bottom-0 right-0 md:left-64 left-0 z-10 bg-red
rounded-tl-2xl rounded-tr-2xl
shadow-[inset_4px_4px_4px_0px_rgba(0,0,0,0.25)] md:p-10 px-4 py-6"
style="top: 4.5rem;">
<!-- Body 컨텐츠 - 스크롤 영역 -->
<div class="h-full overflow-y-auto">
@Body
</div>
</div>
</div>
}
else
{
<main class="flex-1 w-full w-max-960 mx-auto">
<main class="flex-1 w-full max-w-[960px] mx-auto overflow-y-auto">
@Body
</main>
}
@ -36,17 +51,17 @@
<TopProjectNav />
}
<!-- 본문 컨텐츠 -->
<main class="flex-1 w-full w-max-960 mx-auto">
<main class="flex-1 w-full max-w-[960px] mx-auto overflow-y-auto">
@Body
</main>
}
<!-- 플로팅 버튼 -->
<FloatingButton />
<!-- 하단 메뉴 -->
<BottomNav />
<Footer />
@* <!-- 플로팅 버튼 --> *@
@* <FloatingButton /> *@
@* *@
@* <!-- 하단 메뉴 --> *@
@* <BottomNav /> *@
@* <Footer /> *@
@if (LoadingService.IsLoading)
{

View File

@ -0,0 +1,53 @@
using Microsoft.JSInterop;
namespace Front.Program.Services;
public class NavigationService
{
private readonly IJSRuntime _jsRuntime;
public NavigationService(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
/// <summary>
/// 뒤로가기를 완전히 막습니다
/// </summary>
public async Task PreventBackNavigationAsync()
{
await _jsRuntime.InvokeVoidAsync("eval", @"
history.pushState(null, null, location.href);
window.addEventListener('popstate', function(event) {
history.pushState(null, null, location.href);
});
");
}
/// <summary>
/// 확인 대화상자와 함께 뒤로가기를 막습니다
/// </summary>
public async Task PreventBackNavigationWithConfirmAsync(string message = "정말로 페이지를 떠나시겠습니까?")
{
await _jsRuntime.InvokeVoidAsync("eval", $@"
history.pushState(null, null, location.href);
window.addEventListener('popstate', function(event) {{
if (confirm('{message}')) {{
history.back();
}} else {{
history.pushState(null, null, location.href);
}}
}});
");
}
/// <summary>
/// 뒤로가기 방지를 해제합니다
/// </summary>
public async Task AllowBackNavigationAsync()
{
await _jsRuntime.InvokeVoidAsync("eval", @"
window.removeEventListener('popstate', arguments.callee);
");
}
}

View File

@ -1,6 +1,6 @@
@page "/am/intro"
<div class="relative flex size-full min-h-screen flex-col bg-normal-normal group/design-root overflow-x-hidden" style='font-family: "Public Sans", "Noto Sans", sans-serif;'>
<div class="relative flex w-full min-h-screen flex-col bg-normal-normal group/design-root" style='font-family: "Public Sans", "Noto Sans", sans-serif;'>
<div class="layout-container flex h-full grow flex-col">
<div class="px-4 sm:px-6 md:px-10 lg:px-24 xl:px-40 flex flex-1 justify-center py-5">
<div class="layout-content-container flex flex-col max-w-[960px] flex-1">

View File

@ -1,2 +1,13 @@
@page "/am/main"
<h3>AcademyMain</h3>
<!-- 스크롤 가능한 컨텐츠 영역 -->
<div class="h-full overflow-y-auto overflow-x-hidden gap-14 flex flex-col">
<BookMark />
@* <BookMark /> *@
@* <BookMark /> *@
@* <BookMark /> *@
@* <BookMark /> *@
@* <BookMark /> *@
@* <BookMark /> *@
</div>

View File

@ -1,47 +1,144 @@
using Front.Program.Services;
using Front.Program.ViewModels;
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Front.Program.ViewModels;
namespace Front.Program.Views.Academy;
using Front.Program.Views.Academy.Main;
public partial class AcademyMain : ComponentBase
namespace Front.Program.Views.Academy
{
[Inject] NavigationManager Navigation { get; set; } = default!;
[Inject] UserStateService UserStateService { get; set; } = default!;
[Inject] QueryParamService QueryParamService { get; set; } = default!;
[Inject] StorageService StorageService { get; set; } = default!;
[Inject] LoadingService LoadingService { get; set; } = default!;
// [Inject] LoggerService Logger {get; set;} = default!;
protected override async Task OnInitializedAsync()
public partial class AcademyMain : ComponentBase
{
// 초기화 작업
// await UserStateService.GetUserDataAsync();
// if (UserStateService.isFirstCheck)
// {
// // 첫 번째 체크 후에만 Academy 정보를 가져옴
// var academyResult = await UserStateService.GetAcademy();
//
// if (academyResult.success && academyResult.simpleAcademy.Count > 0)
// {
// UserStateService.academyItems = academyResult.simpleAcademy.ToArray();
// }
// }
//
// URL 파라미터 처리
LoggerService.Write("로거 테스트");
[Inject] NavigationManager Navigation { get; set; } = default!;
[Inject] UserStateService UserStateService { get; set; } = default!;
[Inject] QueryParamService QueryParamService { get; set; } = default!;
[Inject] StorageService StorageService { get; set; } = default!;
[Inject] LoadingService LoadingService { get; set; } = default!;
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
LoggerService.Write("쿼리 있나?");
// 쿼리 파라미터가 있는 경우에만 처리
if (!string.IsNullOrEmpty(uri.Query))
{
var queryParam = QueryParamService.ParseQueryParam(uri);
LoggerService.Write($"Parsed Query Parameters: {string.Join(", ", queryParam.Select(kv => $"{kv.Key}={kv.Value}"))}");
}
LoggerService.Write("쿼리 검사");
[Inject] NavigationService NavigationService { get; set; } = default!;
private List<FavoriteItem> Favorites { get; set; } = new();
private AttendanceInfo Attendance { get; set; } = new();
private List<LearningClass> LearningClasses { get; set; } = new();
private List<ScheduleItem> ScheduleItems { get; set; } = new();
private AcademyNewsItem News { get; set; } = new();
private List<UpcomingClassItem> UpcomingClasses { get; set; } = new();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// 뒤로가기를 완전히 막기
await NavigationService.PreventBackNavigationAsync();
// 또는 확인 대화상자와 함께 막기
// await NavigationService.PreventBackNavigationWithConfirmAsync("정말로 이 페이지를 떠나시겠습니까?");
}
}
protected override void OnInitialized()
{
// Sample Data Initialization
Favorites = new List<FavoriteItem>
{
new() { Name = "학습 > 수업" },
new() { Name = "학습 > 과제" },
new() { Name = "커뮤니티 > 공지" },
new() { Name = "커뮤니티 > Q&A" },
new() { Name = "마이페이지" }
};
Attendance = new AttendanceInfo
{
Month = 7,
MonthlyPercentage = 10,
MonthlyAttendedDays = 11,
MonthlyTotalDays = 31,
DailyPercentage = 75,
DailyAttendedHours = 3,
DailyTotalHours = 4
};
LearningClasses = new List<LearningClass>
{
new() { Day = "월", Teacher = "선생님1", ClassName = "선생님1 의 수업", Progress = 20, Homework = "O" },
new() { Day = "월", Teacher = "선생님2", ClassName = "선생님2 의 수업", Progress = 30, Homework = "X" },
new() { Day = "화", Teacher = "선생님3", ClassName = "선생님3 의 수업", Progress = 45, Homework = "O" }
};
ScheduleItems = new List<ScheduleItem>
{
new() { IsImportant = true, Description = "중간고사", Date = new System.DateTime(2025, 12, 31) },
new() { IsImportant = false, Description = "보강수업", Date = new System.DateTime(2025, 12, 31) },
new() { IsImportant = true, Description = "학부모 상담", Date = new System.DateTime(2025, 12, 31) }
};
News = new AcademyNewsItem
{
Title = "School Closure on Friday",
Content = "Due to the upcoming storm, the school will be closed on Friday, March 15th. Please stay safe and monitor your email for updates.",
Date = new System.DateTime(2025, 12, 31),
ImageUrl = "" // or a path to an image
};
UpcomingClasses = new List<UpcomingClassItem>
{
new() { ClassName = "수업 이름이 얼마나 길어질지는 모르겠지만 이정도는?", Date = "2025-07-14 (월)", Time = "09:00 ~ 11:00", Teacher = "이름이다섯 선생님" },
new() { ClassName = "수학", Date = "2025-07-14 (월)", Time = "11:00 ~ 13:00", Teacher = "김수학 선생님" },
new() { ClassName = "영어", Date = "2025-07-15 (화)", Time = "09:00 ~ 11:00", Teacher = "박영어 선생님" },
new() { ClassName = "과학", Date = "2025-07-15 (화)", Time = "11:00 ~ 13:00", Teacher = "최과학 선생님" },
new() { ClassName = "코딩", Date = "2025-07-16 (수)", Time = "14:00 ~ 16:00", Teacher = "이코딩 선생님" }
};
}
// Data Models
public class FavoriteItem
{
public string Name { get; set; } = "";
}
public class AttendanceInfo
{
public int Month { get; set; }
public int MonthlyPercentage { get; set; }
public int MonthlyAttendedDays { get; set; }
public int MonthlyTotalDays { get; set; }
public int DailyPercentage { get; set; }
public int DailyAttendedHours { get; set; }
public int DailyTotalHours { get; set; }
}
public class LearningClass
{
public string Day { get; set; } = "";
public string Teacher { get; set; } = "";
public string ClassName { get; set; } = "";
public int Progress { get; set; }
public string Homework { get; set; } = "";
}
public class ScheduleItem
{
public bool IsImportant { get; set; }
public string Description { get; set; } = "";
public System.DateTime Date { get; set; }
}
public class AcademyNewsItem
{
public string Title { get; set; } = "";
public string Content { get; set; } = "";
public System.DateTime Date { get; set; }
public string? ImageUrl { get; set; }
}
public class UpcomingClassItem
{
public string ClassName { get; set; } = "";
public string Date { get; set; } = "";
public string Time { get; set; } = "";
public string Teacher { get; set; } = "";
}
}
}
}

View File

@ -1,17 +1,3 @@
<div class="h-[72px] p-4 border-b border-gray-200 flex items-center">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-gray-200 overflow-hidden">
<img src="Resources/Images/Logo/Crystal_Icon.png" alt="프로필" class="w-full h-full object-cover" />
</div>
<div>
<!-- 회원 이름이 오는 곳 -->
<div class="text-gray-900 text-base font-medium">@UserName</div>
<!-- 회원 유형이 올 곳 -->
<p class="text-gray-600 text-sm">@UserType</p>
</div>
</div>
</div>
<!-- 메뉴 섹션 -->
<nav class="flex-1 p-4 space-y-2 ">
<a href="/academy/children" class="flex items-center gap-3 px-3 py-2 rounded-lg bg-gray-100">

View File

@ -5,9 +5,4 @@ namespace Front.Program.Views.Academy.Common;
public partial class LeftSideAcademy : ComponentBase
{
[Inject]
private UserStateService UserStateService { get; set; }
private string UserName => UserStateService.UserData?.Name ?? "AcaMate";
private string UserType => UserStateService.UserData?.Type ?? "Parent";
}

View File

@ -0,0 +1,13 @@
<div class="w-64 h-16 p-4 bg-white rounded-tr-2xl rounded-bl-2xl rounded-br-2xl shadow-[inset_-4px_-4px_4px_0px_rgba(0,0,0,0.25)] inline-flex justify-start items-center gap-3 overflow-hidden">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-gray-200 overflow-hidden">
<img src="Resources/Images/Logo/Crystal_Icon.png" alt="프로필" class="w-full h-full object-cover" />
</div>
<div>
<!-- 회원 이름이 오는 곳 -->
<div class="text-gray-900 text-base font-medium">@UserName</div>
<!-- 회원 유형이 올 곳 -->
<p class="text-gray-600 text-sm">@UserType</p>
</div>
</div>
</div>

View File

@ -0,0 +1,13 @@
using Front.Program.ViewModels;
using Microsoft.AspNetCore.Components;
namespace Front.Program.Views.Academy.Common;
public partial class SympleProfile : ComponentBase
{
[Inject]
private UserStateService UserStateService { get; set; }
private string UserName => UserStateService.UserData?.Name ?? "AcaMate";
private string UserType => UserStateService.UserData?.Type ?? "Parent";
}

View File

@ -1,7 +1,5 @@
<div class="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-[#f0f2f5] h-[72px] w-full bg-white px-4">
<div class="flex items-center justify-between whitespace-nowrap h-[72px] w-full bg-white px-4">
<div id="AcademyDrop" class="relative flex items-center gap-4 text-[#111418] flex-1">
<button class="md:hidden mr-4">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-16 6h16"></path>

View File

@ -0,0 +1,19 @@
<div class="self-stretch flex flex-col justify-start items-start gap-4 scrollbar-hide">
<div class="flex flex-col justify-center items-start gap-1">
<div class="justify-center text-text-title text-3xl font-bold font-['Noto_Sans_KR']">즐겨 찾기</div>
<div class="justify-center">
<span class="text-text-detail text-base font-normal font-['Noto_Sans_KR']">즐겨찾기는 최대 </span>
<span class="text-text-detail text-base font-bold font-['Noto_Sans_KR']">5개</span>
<span class="text-text-detail text-base font-normal font-['Noto_Sans_KR']"> 까지 가능합니다.</span>
</div>
</div>
<div class="self-stretch flex justify-start items-center gap-6 overflow-x-auto overflow-y-hidden scrollbar-hide"style="-ms-overflow-style: none; scrollbar-width: none;">
@foreach (var favorite in Favorites)
{
<div class="p-2 rounded-lg outline outline-2 outline-offset-[-2px] outline-text-disabled flex justify-start items-center gap-3 overflow-hidden flex-shrink-0">
<img src="Resources/Images/Icon/Bookmark.png" alt="Home" class="w-8 h-8">
<div class="justify-center text-text-title text-[20px] font-medium font-['Noto_Sans_KR'] whitespace-nowrap">@favorite.Name</div>
</div>
}
</div>
</div>

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Components;
namespace Front.Program.Views.Academy.Main;
public partial class BookMark : ComponentBase
{
public List<FavoriteItem> Favorites { get; set; } = new();
protected override void OnInitialized()
{
Favorites = new List<FavoriteItem>
{
new() { Name = "학습 > 수업" },
new() { Name = "학습 > 과제" },
new() { Name = "커뮤니티 > 공지" },
new() { Name = "커뮤니티 > Q&A" },
new() { Name = "마이페이지" }
};
}
public class FavoriteItem
{
public string Name { get; set; } = "";
}
}

View File

@ -0,0 +1,62 @@
<div class="flex-shrink-0 w-[600px] h-72 p-6 rounded-lg outline outline-2 outline-offset-[-2px] outline-neutral-400 inline-flex flex-col justify-between items-start overflow-hidden">
<div class="self-stretch inline-flex justify-between items-start">
<div class="w-72 inline-flex flex-col justify-start items-start gap-2">
<div class="justify-center text-stone-900 text-2xl font-bold font-['Noto_Sans_KR']">출석 확인</div>
<div class="self-stretch inline-flex justify-start items-start gap-1">
<div class="justify-center text-neutral-600 text-base font-normal font-['Noto_Sans_KR']">최근</div>
<div class="justify-center text-stone-900 text-base font-medium font-['Noto_Sans_KR']">@Attendance.Month 월</div>
<div class="justify-center text-neutral-600 text-base font-normal font-['Noto_Sans_KR']">의 출석을 확인할 수 있습니다.</div>
</div>
</div>
<div class="w-6 h-6 relative overflow-hidden">
<div class="w-2 h-3 left-[8px] top-[6px] absolute bg-stone-500"></div>
</div>
</div>
<div class="self-stretch inline-flex justify-between items-center">
<div class="inline-flex flex-col justify-start items-start gap-2">
<div class="w-20 py-1 inline-flex justify-center items-center gap-1">
<div class="text-center justify-center text-stone-900 text-base font-medium font-['Noto_Sans_KR'] leading-normal">월간</div>
<div class="text-center justify-center text-neutral-600 text-base font-normal font-['Noto_Sans_KR'] leading-normal">출석</div>
</div>
<div class="self-stretch inline-flex justify-start items-center gap-4">
<div class="w-20 h-20 relative">
<div class="w-14 h-14 left-[70px] top-[10px] absolute origin-top-left rotate-90 rounded-full outline outline-[12px] outline-offset-[-6px] outline-stone-400"></div>
<div class="w-14 h-14 left-[10px] top-[10px] absolute rounded-full outline outline-[12px] outline-offset-[-6px] outline-red-200"></div>
<div class="left-[23px] top-[31px] absolute justify-start text-Colors-Red text-base font-bold font-['Noto_Sans_KR']">@Attendance.MonthlyPercentage%</div>
</div>
<div class="min-w-24 flex justify-start items-center gap-2.5">
<div class="w-24 self-stretch py-1 flex justify-between items-center">
<div class="w-8 h-4 text-center justify-center text-Colors-Red text-xl font-medium font-['Noto_Sans_KR'] leading-loose">@Attendance.MonthlyAttendedDays</div>
<div class="w-2 h-4 text-center justify-center text-neutral-600 text-xs font-medium font-['Noto_Sans_KR'] leading-none">/</div>
<div class="w-8 h-4 text-center justify-center text-neutral-600 text-xl font-normal font-['Noto_Sans_KR'] leading-loose">@Attendance.MonthlyTotalDays</div>
<div class="text-center justify-center text-neutral-600 text-base font-normal font-['Noto_Sans_KR'] leading-normal">일</div>
</div>
</div>
</div>
</div>
<div class="w-0.5 h-20 bg-neutral-400 rounded-xs"></div>
<div class="inline-flex flex-col justify-start items-start gap-2">
<div class="w-20 py-1 inline-flex justify-center items-center gap-1">
<div class="text-center justify-center text-stone-900 text-base font-medium font-['Noto_Sans_KR'] leading-normal">일일</div>
<div class="text-center justify-center text-neutral-600 text-base font-normal font-['Noto_Sans_KR'] leading-normal">출석</div>
</div>
<div class="inline-flex justify-start items-center gap-4">
<div class="w-20 h-20 relative">
<div class="w-14 h-14 left-[70px] top-[10px] absolute origin-top-left rotate-90 rounded-full outline outline-[12px] outline-offset-[-6px] outline-slate-400"></div>
<div class="w-14 h-14 left-[10px] top-[10px] absolute rounded-full outline outline-[12px] outline-offset-[-6px] outline-blue-200"></div>
<div class="left-[23px] top-[31px] absolute justify-start text-Colors-Blue text-base font-bold font-['Noto_Sans_KR']">@Attendance.DailyPercentage%</div>
</div>
<div class="min-w-24 flex justify-start items-center gap-2.5">
<div class="self-stretch min-w-24 py-1 flex justify-start items-center gap-0.5">
<div class="w-6 h-4 text-center justify-center text-Colors-Blue text-xl font-medium font-['Noto_Sans_KR'] leading-loose">@Attendance.DailyAttendedHours</div>
<div class="w-2 h-4 text-center justify-center text-neutral-600 text-xs font-medium font-['Noto_Sans_KR'] leading-none">/</div>
<div class="w-6 h-4 text-center justify-center text-neutral-600 text-xl font-normal font-['Noto_Sans_KR'] leading-loose">@Attendance.DailyTotalHours</div>
<div class="text-center justify-center text-neutral-600 text-base font-normal font-['Noto_Sans_KR'] leading-normal">시간</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Components;
namespace Front.Program.Views.Academy.Main;
public partial class CheckAttendance : ComponentBase
{
private AttendanceInfo Attendance { get; set; } = new();
protected override void OnInitialized()
{
Attendance = new AttendanceInfo
{
Month = 7,
MonthlyPercentage = 10,
MonthlyAttendedDays = 11,
MonthlyTotalDays = 31,
DailyPercentage = 75,
DailyAttendedHours = 3,
DailyTotalHours = 4
};
}
}
public class AttendanceInfo
{
public int Month { get; set; }
public int MonthlyPercentage { get; set; }
public int MonthlyAttendedDays { get; set; }
public int MonthlyTotalDays { get; set; }
public int DailyPercentage { get; set; }
public int DailyAttendedHours { get; set; }
public int DailyTotalHours { get; set; }
}

View File

@ -0,0 +1,5 @@
<h3>ClassInfo</h3>
@code {
}

View File

@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Components;
namespace Front.Program.Views.Academy.Main;
public partial class ClassInfo : ComponentBase
{
}

View File

@ -0,0 +1,5 @@
<h3>RecentCalendar</h3>
@code {
}

View File

@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Components;
namespace Front.Program.Views.Academy.Main;
public partial class RecentCalendar : ComponentBase
{
}

View File

@ -0,0 +1,5 @@
<h3>RecentNews</h3>
@code {
}

View File

@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Components;
namespace Front.Program.Views.Academy.Main;
public partial class RecentNews : ComponentBase
{
}

View File

@ -0,0 +1,2 @@
<h3>ScheduledClass</h3>

View File

@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Components;
namespace Front.Program.Views.Academy.Main;
public partial class ScheduledClass : ComponentBase
{
}

View File

@ -16,5 +16,5 @@
@using Front.Program.Views.Academy
@using Front.Program.Views.Academy.Common
@using Front.Program.Views.Academy.Main

View File

@ -4,19 +4,16 @@
"scripts": {
"watch:css": "tailwindcss -i ./wwwroot/css/app.css -o ./wwwroot/css/tailwind.css --watch",
"build:css": "tailwindcss -i ./wwwroot/css/app.css -o ./wwwroot/css/tailwind.css --minify",
"build:publish": "rm -rf ./publish && /Users/tanine/.dotnet/dotnet publish -c Debug -o ./publish",
"build:publish": "rm -rf ./publish && /Users/seankim/.dotnet/dotnet publish -c Debug -o ./publish",
"build:test": "rm -rf ../AcaMate_API/publish/debug/ && /Users/tanine/.dotnet/dotnet publish -c Debug -o ../AcaMate_API/publish/debug",
"build:copy": "mkdir -p ../AcaMate_API/publish/debug/wwwroot && cp -r ./publish/wwwroot/* ../AcaMate_API/publish/debug/wwwroot"
},
"devDependencies": {
"tailwindcss": "^3.4.1",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.21",
"autoprefixer": "^10.4.14"
"tailwindcss": "^3.4.1"
},
"dependencies": {
"tailwind-scrollbar-hide": "^4.0.0"
}
}

View File

@ -47,11 +47,13 @@ module.exports = {
detail: '#545454',
disabled: '#8E8E8E',
white: '#FFFFFF',
black: '#000000',
back: '#D8D8D8',
border: '#C6C6C6'
}
},
fontFamily: {},
},
fontFamily: {
sans: ['"Noto Sans KR"', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', 'sans-serif'],
},
screens: {
'xs': '480px',
'sm': '640px',
@ -62,5 +64,7 @@ module.exports = {
}
},
},
plugins: [],
plugins: [
require('tailwind-scrollbar-hide')
],
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
window.navigationHelper = {
preventBackNavigation: function() {
// 현재 상태를 히스토리에 추가
history.pushState(null, null, location.href);
// popstate 이벤트 리스너 추가
window.addEventListener('popstate', function(event) {
// 뒤로가기 시도 시 다시 현재 페이지로 이동
history.pushState(null, null, location.href);
});
},
allowBackNavigation: function() {
// popstate 이벤트 리스너 제거
window.removeEventListener('popstate', this.popstateHandler);
},
// 확인 대화상자와 함께 뒤로가기 막기
preventBackNavigationWithConfirm: function(message) {
history.pushState(null, null, location.href);
window.addEventListener('popstate', function(event) {
if (confirm(message || '정말로 페이지를 떠나시겠습니까?')) {
// 사용자가 확인을 누르면 뒤로가기 허용
history.back();
} else {
// 취소를 누르면 현재 페이지 유지
history.pushState(null, null, location.href);
}
});
}
};