proje-react-panel 1.0.9 → 1.0.11

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 (100) hide show
  1. package/.eslintrc.js +2 -0
  2. package/.idea/vcs.xml +0 -1
  3. package/.idea/watcherTasks.xml +4 -0
  4. package/dist/api/crudApi.d.ts +12 -4
  5. package/dist/components/ErrorBoundary.d.ts +16 -0
  6. package/dist/components/ErrorComponent.d.ts +4 -0
  7. package/dist/components/Form.d.ts +6 -0
  8. package/dist/components/FormField.d.ts +13 -0
  9. package/dist/components/Label.d.ts +8 -0
  10. package/dist/components/Panel.d.ts +3 -1
  11. package/dist/components/layout/Layout.d.ts +9 -2
  12. package/dist/components/layout/SideBar.d.ts +13 -2
  13. package/dist/components/list/List.d.ts +5 -5
  14. package/dist/components/screens/ControllerCreate.d.ts +5 -0
  15. package/dist/components/screens/ControllerDetails.d.ts +5 -0
  16. package/dist/components/screens/ControllerEdit.d.ts +5 -0
  17. package/dist/components/screens/ControllerList.d.ts +5 -0
  18. package/dist/components/screens/Login.d.ts +4 -0
  19. package/dist/decorators/Cell.d.ts +9 -0
  20. package/dist/decorators/Input.d.ts +13 -0
  21. package/dist/hooks/useScreens.d.ts +2 -0
  22. package/dist/index.cjs.js +1 -12
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.esm.js +1 -12
  25. package/dist/initPanel.d.ts +2 -0
  26. package/dist/initPanelOptions.d.ts +8 -0
  27. package/dist/screens/ControllerDetails.d.ts +2 -2
  28. package/dist/screens/ControllerEdit.d.ts +2 -2
  29. package/dist/screens/ControllerList.d.ts +2 -2
  30. package/dist/screens/Form.d.ts +2 -2
  31. package/dist/src/api/crudApi.d.ts +6 -0
  32. package/dist/src/components/Panel.d.ts +9 -0
  33. package/dist/src/components/layout/Layout.d.ts +11 -0
  34. package/dist/src/components/layout/SideBar.d.ts +10 -0
  35. package/dist/src/components/list/List.d.ts +10 -0
  36. package/dist/src/decorators/Crud.d.ts +6 -0
  37. package/dist/src/index.d.ts +8 -0
  38. package/dist/src/screens/ControllerCreate.d.ts +5 -0
  39. package/dist/src/screens/ControllerDetails.d.ts +5 -0
  40. package/dist/src/screens/ControllerEdit.d.ts +5 -0
  41. package/dist/src/screens/ControllerList.d.ts +5 -0
  42. package/dist/src/screens/Form.d.ts +6 -0
  43. package/dist/src/store/store.d.ts +19 -0
  44. package/dist/src/types/Screen.d.ts +4 -0
  45. package/dist/src/types/ScreenCreatorData.d.ts +8 -0
  46. package/dist/src/utils/createScreens.d.ts +1 -0
  47. package/dist/src/utils/getFields.d.ts +2 -0
  48. package/dist/src/utils/getScreens.d.ts +2 -0
  49. package/dist/store/store.d.ts +19 -0
  50. package/dist/types/ScreenCreatorData.d.ts +9 -6
  51. package/dist/types/initPanelOptions.d.ts +8 -0
  52. package/dist/utils/crudScreens.d.ts +2 -0
  53. package/dist/utils/getFields.d.ts +1 -1
  54. package/dist/utils/getScreens.d.ts +2 -0
  55. package/package.json +15 -8
  56. package/src/api/crudApi.ts +31 -27
  57. package/src/components/ErrorBoundary.tsx +64 -0
  58. package/src/components/ErrorComponent.tsx +15 -0
  59. package/src/components/Form.tsx +70 -0
  60. package/src/components/FormField.tsx +60 -0
  61. package/src/components/Label.tsx +15 -0
  62. package/src/components/Panel.tsx +12 -7
  63. package/src/components/layout/Layout.tsx +16 -12
  64. package/src/components/layout/SideBar.tsx +38 -12
  65. package/src/components/list/List.tsx +73 -67
  66. package/src/components/screens/ControllerCreate.tsx +7 -0
  67. package/src/components/screens/ControllerDetails.tsx +40 -0
  68. package/src/components/screens/ControllerEdit.tsx +35 -0
  69. package/src/components/screens/ControllerList.tsx +44 -0
  70. package/src/components/screens/Login.tsx +64 -0
  71. package/src/decorators/Cell.ts +34 -0
  72. package/src/decorators/Input.ts +50 -0
  73. package/src/hooks/useScreens.tsx +36 -0
  74. package/src/index.ts +7 -2
  75. package/src/initPanel.ts +14 -0
  76. package/src/store/store.ts +18 -0
  77. package/src/styles/error-boundary.scss +89 -0
  78. package/src/styles/form.scss +38 -6
  79. package/src/styles/index.scss +22 -4
  80. package/src/styles/layout.scss +49 -0
  81. package/src/styles/list.scss +8 -6
  82. package/src/styles/login.scss +90 -0
  83. package/src/styles/sidebar.scss +17 -22
  84. package/src/types/ScreenCreatorData.ts +10 -7
  85. package/src/types/initPanelOptions.ts +8 -0
  86. package/src/utils/createScreens.ts +2 -2
  87. package/src/utils/getFields.ts +8 -9
  88. package/dist/utils/getScreenForRoutes.d.ts +0 -2
  89. package/dist/utils/storeData.d.ts +0 -4
  90. package/src/declerations/Cell.ts +0 -37
  91. package/src/screens/ControllerCreate.tsx +0 -13
  92. package/src/screens/ControllerDetails.tsx +0 -34
  93. package/src/screens/ControllerEdit.tsx +0 -31
  94. package/src/screens/ControllerList.tsx +0 -40
  95. package/src/screens/Form.tsx +0 -67
  96. package/src/utils/getScreenForRoutes.tsx +0 -44
  97. package/src/utils/storeData.ts +0 -7
  98. /package/dist/{declerations → decorators}/Crud.d.ts +0 -0
  99. /package/dist/{declerations → src/decorators}/Cell.d.ts +0 -0
  100. /package/src/{declerations → decorators}/Crud.ts +0 -0
@@ -0,0 +1,35 @@
1
+ import { Form } from "../Form";
2
+ import React, { useEffect, useState } from "react";
3
+ import { Screen } from "../../types/Screen";
4
+ import { useParams } from "react-router";
5
+ import { CrudApi } from "../../api/crudApi";
6
+ import { useAppStore } from "../../store/store";
7
+ import { ErrorComponent } from "../ErrorComponent";
8
+
9
+ export function ControllerEdit({ screen }: { screen: Screen }) {
10
+ const { fetchSettings } = useAppStore((s) => ({
11
+ fetchSettings: s.fetchSettings,
12
+ }));
13
+ const { id } = useParams();
14
+ const [data, setData] = useState<any>(null);
15
+ const [error, setError] = useState(null);
16
+
17
+ useEffect(() => {
18
+ if (fetchSettings && screen.controller && id) {
19
+ CrudApi.details(fetchSettings, screen.controller, id)
20
+ .then((res) => {
21
+ setData(res);
22
+ })
23
+ .catch((e: any) => {
24
+ setError(e);
25
+ console.error(e);
26
+ });
27
+ }
28
+ }, [fetchSettings, id, screen]);
29
+
30
+ if (error) {
31
+ return <ErrorComponent error={error} />;
32
+ }
33
+
34
+ return <Form data={data} screen={screen} />;
35
+ }
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { Screen } from "../../types/Screen";
3
+ import { useEffect, useState } from "react";
4
+ import { CrudApi } from "../../api/crudApi";
5
+ import { Link } from "react-router";
6
+ import { List } from "../list/List";
7
+ import { useAppStore } from "../../store/store";
8
+ import { ErrorComponent } from "../ErrorComponent";
9
+
10
+ export function ControllerList({ screen }: { screen: Screen }) {
11
+ const { screens, fetchSettings } = useAppStore((s) => ({
12
+ screens: s.screens ?? {},
13
+ fetchSettings: s.fetchSettings,
14
+ }));
15
+ const [page, setPage] = useState(0);
16
+ const [data, setData] = useState<any>(null);
17
+ const [error, setError] = useState(null);
18
+
19
+ useEffect(() => {
20
+ if (screen.controller && fetchSettings) {
21
+ CrudApi.getList(fetchSettings, screen.controller)
22
+ .then((res) => {
23
+ setData(res);
24
+ })
25
+ .catch((e: any) => {
26
+ setError(e);
27
+ console.error(e);
28
+ });
29
+ }
30
+ }, [page, screen.controller, fetchSettings]);
31
+
32
+ if (error) {
33
+ return <ErrorComponent error={error} />;
34
+ }
35
+ return (
36
+ <div>
37
+ <Link to={"create"}>Create</Link>
38
+ {/*
39
+ {error ? <p>Error {error}</p> : <></>}
40
+ */}
41
+ <List screen={screen} cells={screens[screen.key].cells} data={data} />
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+ import { useForm } from "react-hook-form";
3
+ import { FormField } from "../FormField";
4
+ import { Screen } from "../../types/Screen";
5
+ import "../../styles/login.scss";
6
+
7
+ interface LoginFormData {
8
+ email: string;
9
+ password: string;
10
+ }
11
+ function Login() {
12
+ const {
13
+ register,
14
+ handleSubmit,
15
+ formState: { errors },
16
+ } = useForm<LoginFormData>();
17
+
18
+ const onSubmit = async (data: LoginFormData) => {
19
+ // TODO: Implement login logic
20
+ console.log("Login attempt:", data);
21
+ };
22
+
23
+ return (
24
+ <div className="login-container">
25
+ <div className="login-panel">
26
+ <div className="login-header">
27
+ <h1>Welcome Back</h1>
28
+ <p>Please sign in to continue</p>
29
+ </div>
30
+ <form onSubmit={handleSubmit(onSubmit)} className="login-form">
31
+ <FormField
32
+ input={{
33
+ name: "email",
34
+ label: "Email",
35
+ inputType: "email",
36
+ placeholder: "Enter your email",
37
+ }}
38
+ register={register}
39
+ isEditForm={false}
40
+ error={errors.email}
41
+ />
42
+ <FormField
43
+ input={{
44
+ name: "password",
45
+ label: "Password",
46
+ inputType: "password",
47
+ placeholder: "Enter your password",
48
+ }}
49
+ register={register}
50
+ isEditForm={false}
51
+ error={errors.password}
52
+ />
53
+ <div className="form-actions">
54
+ <button type="submit" className="submit-button">
55
+ Sign In
56
+ </button>
57
+ </div>
58
+ </form>
59
+ </div>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ export default Login;
@@ -0,0 +1,34 @@
1
+ import "reflect-metadata";
2
+
3
+ const CELL_KEY = Symbol("cell");
4
+
5
+ export interface CellOptions {
6
+ name?: string;
7
+ title?: string;
8
+ type?: "string" | "number" | "date";
9
+ placeHolder?: string;
10
+ }
11
+
12
+ export function Cell(options?: CellOptions): PropertyDecorator {
13
+ return (target, propertyKey) => {
14
+ const existingCells: string[] = Reflect.getMetadata(CELL_KEY, target) || [];
15
+ Reflect.defineMetadata(CELL_KEY, [...existingCells, propertyKey.toString()], target);
16
+
17
+ if (options) {
18
+ const keyString = `${CELL_KEY.toString()}:${propertyKey.toString()}:options`;
19
+ Reflect.defineMetadata(keyString, options, target);
20
+ }
21
+ };
22
+ }
23
+
24
+ export function getCellFields(entityClass: any): CellOptions[] {
25
+ const prototype = entityClass.prototype;
26
+ const cellFields: string[] = Reflect.getMetadata(CELL_KEY, prototype) || [];
27
+ return cellFields.map((field) => {
28
+ const fields = Reflect.getMetadata(`${CELL_KEY.toString()}:${field}:options`, prototype) || {};
29
+ return {
30
+ ...fields,
31
+ name: fields?.name ?? field,
32
+ };
33
+ });
34
+ }
@@ -0,0 +1,50 @@
1
+ import "reflect-metadata";
2
+
3
+ const INPUT_KEY = Symbol("input");
4
+
5
+ const isFieldSensitive = (fieldName: string): boolean => {
6
+ return ["password"].some((term) => fieldName.toLowerCase().includes(term));
7
+ };
8
+
9
+ export interface InputOptions {
10
+ name?: string;
11
+ label?: string;
12
+ placeholder?: string;
13
+ editable?: boolean;
14
+ inputType?: "text" | "email" | "tel" | "password" | "number" | "date";
15
+ type?: "input" | "select" | "textarea";
16
+ selectOptions?: string[]; //TODO: label/value
17
+ cancelPasswordValidationOnEdit?: boolean;
18
+ }
19
+
20
+ export function Input(options?: InputOptions): PropertyDecorator {
21
+ return (target, propertyKey) => {
22
+ const existingInputs: string[] = Reflect.getMetadata(INPUT_KEY, target) || [];
23
+ Reflect.defineMetadata(INPUT_KEY, [...existingInputs, propertyKey.toString()], target);
24
+
25
+ if (options) {
26
+ const keyString = `${INPUT_KEY.toString()}:${propertyKey.toString()}:options`;
27
+ Reflect.defineMetadata(keyString, options, target);
28
+ }
29
+ };
30
+ }
31
+
32
+ export function getInputFields(entityClass: any): InputOptions[] {
33
+ const prototype = entityClass.prototype;
34
+ const inputFields: string[] = Reflect.getMetadata(INPUT_KEY, prototype) || [];
35
+ return inputFields.map((field) => {
36
+ const fields = Reflect.getMetadata(`${INPUT_KEY.toString()}:${field}:options`, prototype) || {};
37
+ const inputType = fields?.inputType ?? (isFieldSensitive(field) ? "password" : "text");
38
+ return {
39
+ ...fields,
40
+ editable: fields.editable ?? true,
41
+ sensitive: fields.sensitive,
42
+ name: fields?.name ?? field,
43
+ label: fields?.label ?? field,
44
+ placeholder: fields?.placeholder ?? field,
45
+ inputType: inputType,
46
+ selectOptions: fields?.selectOptions ?? [],
47
+ cancelPasswordValidationOnEdit: fields?.cancelPasswordValidationOnEdit ?? inputType === "password",
48
+ };
49
+ });
50
+ }
@@ -0,0 +1,36 @@
1
+ import React, { useMemo } from "react";
2
+ import { Route } from "react-router";
3
+ import { ControllerCreate } from "../components/screens/ControllerCreate";
4
+ import { ControllerDetails } from "../components/screens/ControllerDetails";
5
+ import { ControllerEdit } from "../components/screens/ControllerEdit";
6
+ import { ControllerList } from "../components/screens/ControllerList";
7
+ import { Screen } from "../types/Screen";
8
+ import { useAppStore } from "../store/store";
9
+
10
+ export function useScreens() {
11
+ const screens = useAppStore((s) => s.screens ?? {});
12
+ return useMemo(
13
+ () => (
14
+ <>
15
+ {Object.entries(screens).map(([key, screenData]) => {
16
+ const controllerName = screenData.crud?.controller ?? key;
17
+ let routePath = `${screenData.path}`;
18
+ const screen: Screen = {
19
+ key,
20
+ controller: controllerName,
21
+ };
22
+ return (
23
+ <React.Fragment key={"index"}>
24
+ <Route path={routePath + "/create"} element={<ControllerCreate screen={screen} />} />
25
+ <Route path={routePath + "/details/:id"} element={<ControllerDetails screen={screen} />} />
26
+ <Route path={routePath + "/edit/:id"} element={<ControllerEdit screen={screen} />} />
27
+ <Route path={routePath} element={<ControllerList screen={screen} />} />
28
+ </React.Fragment>
29
+ );
30
+ })}
31
+ <Route path="*" element={<div>404 - Not Found</div>} />
32
+ </>
33
+ ),
34
+ [screens]
35
+ );
36
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,9 @@
1
+ export { type InitPanelOptions } from "./types/initPanelOptions";
2
+ export { type ScreenCreatorData } from "./types/ScreenCreatorData";
1
3
  export { createScreens } from "./utils/createScreens";
2
- export { getScreenForRoutes } from "./utils/getScreenForRoutes";
4
+ export { useScreens } from "./hooks/useScreens";
5
+ export { Crud } from "./decorators/Crud";
6
+ export { Cell } from "./decorators/Cell";
7
+ export { Input } from "./decorators/Input";
3
8
  export { Layout } from "./components/layout/Layout";
4
- export {Panel} from "./components/Panel"
9
+ export { Panel } from "./components/Panel";
@@ -0,0 +1,14 @@
1
+ import { createScreens } from "./utils/createScreens";
2
+ import { getFields } from "./utils/getFields";
3
+ import { ScreenCreatorData } from "./types/ScreenCreatorData";
4
+ import { InitPanelOptions } from "./types/initPanelOptions";
5
+ import { useAppStore } from "./store/store";
6
+
7
+ export function initPanel({ crud, fetch }: InitPanelOptions) {
8
+ const screensCrudOptions: Record<string, ScreenCreatorData> = {};
9
+ Object.entries(crud).forEach(([key, value]) => {
10
+ screensCrudOptions[key] = getFields(key, value);
11
+ });
12
+ createScreens(screensCrudOptions);
13
+ useAppStore.setState({ fetchSettings: { baseUrl: fetch.baseURL } });
14
+ }
@@ -0,0 +1,18 @@
1
+ import { createJSONStorage, persist } from "zustand/middleware";
2
+ import { createWithEqualityFn } from "zustand/traditional";
3
+ import { shallow } from "zustand/vanilla/shallow";
4
+ import { ScreenCreatorData } from "../types/ScreenCreatorData";
5
+
6
+ interface AppState {
7
+ screens: Record<string, ScreenCreatorData> | null;
8
+ fetchSettings: { baseUrl: string } | null;
9
+ }
10
+
11
+ export const useAppStore = createWithEqualityFn<AppState>()(
12
+ persist((_) => ({ screens: null, fetchSettings: null }), {
13
+ name: "app-store-1",
14
+ storage: createJSONStorage(() => localStorage),
15
+ partialize: (_) => ({}),
16
+ }),
17
+ shallow
18
+ );
@@ -0,0 +1,89 @@
1
+ .error-boundary {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ min-height: 100vh;
6
+ padding: 2rem;
7
+ background: linear-gradient(135deg, #1a1d21 0%, #121416 100%);
8
+
9
+ &__content {
10
+ max-width: 600px;
11
+ padding: 3rem;
12
+ text-align: center;
13
+ background: #2c3036;
14
+ border-radius: 1rem;
15
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
16
+ }
17
+
18
+ &__icon {
19
+ width: 80px;
20
+ height: 80px;
21
+ margin: 0 auto 2rem;
22
+ color: #ff6b6b;
23
+
24
+ svg {
25
+ width: 100%;
26
+ height: 100%;
27
+ stroke-width: 1.5;
28
+ }
29
+ }
30
+
31
+ h1 {
32
+ margin: 0 0 1rem;
33
+ font-size: 2rem;
34
+ color: #e9ecef;
35
+ font-weight: 600;
36
+ }
37
+
38
+ &__message {
39
+ margin: 0 0 2rem;
40
+ font-size: 1.1rem;
41
+ color: #adb5bd;
42
+ line-height: 1.6;
43
+ }
44
+
45
+ &__button {
46
+ padding: 0.8rem 2rem;
47
+ font-size: 1rem;
48
+ color: white;
49
+ background: #4dabf7;
50
+ border: none;
51
+ border-radius: 0.5rem;
52
+ cursor: pointer;
53
+ transition: background 0.3s ease;
54
+
55
+ &:hover {
56
+ background: #339af0;
57
+ }
58
+ }
59
+
60
+ &__details {
61
+ margin-top: 2rem;
62
+ padding: 1rem;
63
+ text-align: left;
64
+ background: #212529;
65
+ border-radius: 0.5rem;
66
+
67
+ summary {
68
+ margin-bottom: 1rem;
69
+ color: #ced4da;
70
+ cursor: pointer;
71
+ font-weight: 500;
72
+ }
73
+
74
+ pre {
75
+ margin: 0;
76
+ padding: 1rem;
77
+ background: #1a1d21;
78
+ color: #e9ecef;
79
+ border-radius: 0.3rem;
80
+ overflow-x: auto;
81
+ font-size: 0.9rem;
82
+ line-height: 1.5;
83
+
84
+ & + pre {
85
+ margin-top: 1rem;
86
+ }
87
+ }
88
+ }
89
+ }
@@ -1,9 +1,10 @@
1
1
  .form-wrapper {
2
2
  max-width: 400px;
3
3
  padding: 20px;
4
- background-color: #f9f9f9;
5
4
  border-radius: 10px;
6
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
5
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
6
+ background-color: #1a1a1a;
7
+ color: #f2f2f2;
7
8
 
8
9
  form {
9
10
  display: flex;
@@ -17,21 +18,52 @@
17
18
  font-weight: bold;
18
19
  margin-bottom: 6px;
19
20
  display: block;
20
- color: #333;
21
+ color: #f2f2f2;
21
22
  }
22
23
 
23
- input[type='text'] {
24
+ input[type='text'],
25
+ input[type='password'],
26
+ input[type='email'],
27
+ input[type='tel'],
28
+ input[type='number'],
29
+ input[type='date'],
30
+ textarea,
31
+ select {
24
32
  padding: 10px;
25
33
  font-size: 16px;
26
- border: 1px solid #ccc;
34
+ border: 1px solid #444;
27
35
  border-radius: 5px;
28
36
  width: 100%;
37
+ background-color: #333;
38
+ color: #f2f2f2;
29
39
  transition: border-color 0.2s;
30
40
 
31
41
  &:focus {
32
- border-color: #007bff;
42
+ border-color: #66b2ff;
33
43
  outline: none;
34
44
  }
45
+
46
+ &:disabled {
47
+ background-color: #252525;
48
+ cursor: not-allowed;
49
+ opacity: 0.8;
50
+ border-color: #444;
51
+ color: #999;
52
+ }
53
+ }
54
+
55
+ textarea {
56
+ min-height: 100px;
57
+ resize: vertical;
58
+ }
59
+
60
+ select {
61
+ appearance: none;
62
+ background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
63
+ background-repeat: no-repeat;
64
+ background-position: right 0.7rem top 50%;
65
+ background-size: 0.65rem auto;
66
+ padding-right: 2.5rem;
35
67
  }
36
68
 
37
69
  .error-message {
@@ -1,4 +1,22 @@
1
- @import 'sidebar';
2
- @import 'form';
3
- @import 'layout';
4
- @import 'list';
1
+ @use './sidebar';
2
+ @use './form';
3
+ @use './layout';
4
+ @use './list';
5
+ @use './error-boundary';
6
+
7
+ .layout{
8
+ a {
9
+ color: white;
10
+
11
+ &:hover,
12
+ &:active {
13
+ color: white;
14
+ }
15
+ }
16
+
17
+ *,
18
+ *::before,
19
+ *::after {
20
+ box-sizing: border-box;
21
+ }
22
+ }
@@ -10,4 +10,53 @@
10
10
  overflow-y: auto;
11
11
  transition: margin-left 0.3s ease;
12
12
  }
13
+
14
+ .error-container {
15
+ display: flex;
16
+ padding: 16px;
17
+ margin: 20px 0;
18
+ border-radius: 8px;
19
+ background-color: rgba(255, 77, 79, 0.1);
20
+ border-left: 4px solid #ff4d4f;
21
+ color: #f2f2f2;
22
+ align-items: flex-start;
23
+
24
+ .error-icon {
25
+ margin-right: 16px;
26
+ color: #ff4d4f;
27
+ font-size: 24px;
28
+ display: flex;
29
+ align-items: center;
30
+ }
31
+
32
+ .error-content {
33
+ flex: 1;
34
+
35
+ h3 {
36
+ margin: 0 0 8px;
37
+ font-size: 18px;
38
+ color: #ff4d4f;
39
+ }
40
+
41
+ p {
42
+ margin: 0;
43
+ font-size: 16px;
44
+ }
45
+ }
46
+ }
47
+
48
+ // Additional error feedback styles
49
+ .field-error {
50
+ border-color: #ff4d4f !important;
51
+
52
+ &:focus {
53
+ box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2);
54
+ }
55
+ }
56
+
57
+ .error-message {
58
+ margin-top: 4px;
59
+ font-size: 14px;
60
+ color: #ff4d4f;
61
+ }
13
62
  }
@@ -1,15 +1,17 @@
1
1
  .list-wrapper {
2
2
  width: 100%;
3
3
  padding: 16px;
4
- background-color: #ffffff;
5
4
  border-radius: 8px;
6
5
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
6
+ background-color: #2b2b2b;
7
+ color: #e0e0e0;
7
8
 
8
9
  .header {
9
10
  font-size: 24px;
10
11
  font-weight: bold;
11
12
  text-align: center;
12
13
  margin-bottom: 20px;
14
+ color: #ffffff;
13
15
  }
14
16
 
15
17
  .list-table {
@@ -20,20 +22,20 @@
20
22
  td {
21
23
  padding: 12px 16px;
22
24
  text-align: left;
23
- border-bottom: 1px solid #ddd;
25
+ border-bottom: 1px solid #444444;
24
26
  }
25
27
 
26
28
  th {
27
- background-color: #007bff;
28
- color: white;
29
+ color: #ffffff;
30
+ background-color: #3c3c3c;
29
31
  }
30
32
 
31
33
  tr:nth-child(even) {
32
- background-color: #f9f9f9;
34
+ background-color: #353535;
33
35
  }
34
36
 
35
37
  tr:hover {
36
- background-color: #e8f4ff;
38
+ background-color: #444444;
37
39
  }
38
40
  }
39
41
  }
@@ -0,0 +1,90 @@
1
+ .login-container {
2
+ display: flex;
3
+ justify-content: center;
4
+ align-items: center;
5
+ min-height: 100vh;
6
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
7
+ padding: 20px;
8
+
9
+ .login-panel {
10
+ width: 100%;
11
+ max-width: 400px;
12
+ background: white;
13
+ border-radius: 12px;
14
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
15
+ padding: 32px;
16
+ }
17
+
18
+ .login-header {
19
+ text-align: center;
20
+ margin-bottom: 32px;
21
+
22
+ h1 {
23
+ font-size: 24px;
24
+ color: #333;
25
+ margin-bottom: 8px;
26
+ }
27
+
28
+ p {
29
+ color: #666;
30
+ font-size: 14px;
31
+ }
32
+ }
33
+
34
+ .login-form {
35
+ display: flex;
36
+ flex-direction: column;
37
+ gap: 20px;
38
+
39
+ .form-field {
40
+ margin-bottom: 16px;
41
+
42
+ label {
43
+ display: block;
44
+ margin-bottom: 8px;
45
+ color: #333;
46
+ font-weight: 500;
47
+ }
48
+
49
+ input {
50
+ width: 100%;
51
+ padding: 12px;
52
+ border: 1px solid #e1e1e1;
53
+ border-radius: 8px;
54
+ font-size: 14px;
55
+ transition: border-color 0.2s ease;
56
+
57
+ &:focus {
58
+ outline: none;
59
+ border-color: #007bff;
60
+ box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1);
61
+ }
62
+ }
63
+ }
64
+
65
+ .form-actions {
66
+ margin-top: 24px;
67
+
68
+ .submit-button {
69
+ width: 100%;
70
+ padding: 12px;
71
+ background: #007bff;
72
+ color: white;
73
+ border: none;
74
+ border-radius: 8px;
75
+ font-size: 16px;
76
+ font-weight: 500;
77
+ cursor: pointer;
78
+ transition: background-color 0.2s ease;
79
+
80
+ &:hover {
81
+ background: #0056b3;
82
+ }
83
+
84
+ &:active {
85
+ transform: translateY(1px);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }